diff --git a/packages/nodes-base/nodes/Ftp/__test__/listParser.test.ts b/packages/nodes-base/nodes/Ftp/__test__/listParser.test.ts
new file mode 100644
index 00000000000..d51413aaff2
--- /dev/null
+++ b/packages/nodes-base/nodes/Ftp/__test__/listParser.test.ts
@@ -0,0 +1,51 @@
+import { createRequire } from 'node:module';
+
+// promise-ftp parses LIST output with @icetee/ftp. A pnpm patch makes that parser use native RegExp.
+const requireFromPromiseFtp = createRequire(createRequire(__filename).resolve('promise-ftp'));
+const { parseListEntry } = requireFromPromiseFtp('@icetee/ftp/lib/parser') as {
+ parseListEntry: (line: string) => unknown;
+};
+
+describe('FTP LIST parser', () => {
+ it('parses a Unix file entry', () => {
+ expect(
+ parseListEntry('-rw-r--r--+ 1 ftp ftp 12345678 Mar 3 2021 archive 2021.tar.gz'),
+ ).toEqual({
+ type: '-',
+ name: 'archive 2021.tar.gz',
+ target: undefined,
+ sticky: false,
+ rights: { user: 'rw', group: 'r', other: 'r' },
+ acl: true,
+ owner: 'ftp',
+ group: 'ftp',
+ size: 12345678,
+ date: new Date('2021-03-03'),
+ });
+ });
+
+ it('parses a Unix symbolic link with a sticky bit', () => {
+ expect(
+ parseListEntry('lrwxrwxrwt 1 root root 7 Feb 1 2024 latest -> v2.1.0'),
+ ).toMatchObject({
+ type: 'l',
+ name: 'latest',
+ target: 'v2.1.0',
+ sticky: true,
+ rights: { user: 'rwx', group: 'rwx', other: 'rwx' },
+ });
+ });
+
+ it('parses an MS-DOS directory entry', () => {
+ expect(parseListEntry('10-07-26 02:15PM
Reports')).toEqual({
+ name: 'Reports',
+ type: 'd',
+ size: 0,
+ date: new Date(2026, 9, 7, 14, 15),
+ });
+ });
+
+ it('returns a line that it cannot parse unchanged', () => {
+ expect(parseListEntry('not a listing line')).toBe('not a listing line');
+ });
+});
diff --git a/patches/@icetee__ftp@0.3.15.patch b/patches/@icetee__ftp@0.3.15.patch
new file mode 100644
index 00000000000..94d419de3d4
--- /dev/null
+++ b/patches/@icetee__ftp@0.3.15.patch
@@ -0,0 +1,69 @@
+diff --git a/lib/connection.js b/lib/connection.js
+index 0afc364e7fcf955b2d373ede71d77acdeddeabf7..807542043645cb0b85db0610731f481b3c60c524 100644
+--- a/lib/connection.js
++++ b/lib/connection.js
+@@ -8,9 +8,13 @@ var fs = require('fs'),
+ StringDecoder = require('string_decoder').StringDecoder;
+
+ var Parser = require('./parser');
+-var XRegExp = require('xregexp').XRegExp;
++// Run a regex with named groups and return the groups, or null when it does not match.
++function execNamed(re, str) {
++ var m = re.exec(str);
++ return m ? m.groups : null;
++}
+
+-var REX_TIMEVAL = XRegExp.cache('^(?\\d{4})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d+)(?:.\\d+)?$'),
++var REX_TIMEVAL = new RegExp('^(?\\d{4})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d+)(?:.\\d+)?$'),
+ RE_PASV = /([\d]+),([\d]+),([\d]+),([\d]+),([-\d]+),([-\d]+)/,
+ RE_EPSV = /([\d]+)/,
+ RE_EOL = /\r?\n/g,
+@@ -865,7 +869,7 @@ FTP.prototype.lastMod = function(path, cb) {
+ }, true);
+ } else if (err)
+ return cb(err);
+- var val = XRegExp.exec(text, REX_TIMEVAL), ret;
++ var val = execNamed(REX_TIMEVAL, text), ret;
+ if (!val)
+ return cb(new Error('Invalid date/time format from server'));
+ ret = new Date(val.year + '-' + val.month + '-' + val.date + 'T' + val.hour
+diff --git a/lib/parser.js b/lib/parser.js
+index 3eae00a4ded905c9aef34c5cda743ec63b18b521..2505bc7a17445052f9deda395f90514cf3405f79 100644
+--- a/lib/parser.js
++++ b/lib/parser.js
+@@ -3,10 +3,14 @@ var WritableStream = require('stream').Writable
+ inherits = require('util').inherits,
+ inspect = require('util').inspect;
+
+-var XRegExp = require('xregexp').XRegExp;
++// Run a regex with named groups and return the groups, or null when it does not match.
++function execNamed(re, str) {
++ var m = re.exec(str);
++ return m ? m.groups : null;
++}
+
+-var REX_LISTUNIX = XRegExp.cache('^(?[\\-ld])(?([\\-r][\\-w][\\-xstT]){3})(?(\\+))?\\s+(?\\d+)\\s+(?\\S+)\\s+(?\\S+)\\s+(?\\d+)\\s+(?((?\\w{3})\\s+(?\\d{1,2})\\s+(?\\d{1,2}):(?\\d{2}))|((?\\w{3})\\s+(?\\d{1,2})\\s+(?\\d{4})))\\s+(?.+)$'),
+- REX_LISTMSDOS = XRegExp.cache('^(?\\d{2})(?:\\-|\\/)(?\\d{2})(?:\\-|\\/)(?\\d{2,4})\\s+(?\\d{2}):(?\\d{2})\\s{0,1}(?[AaMmPp]{1,2})\\s+(?:(?\\d+)|(?\\))\\s+(?.+)$'),
++var REX_LISTUNIX = new RegExp('^(?[\\-ld])(?([\\-r][\\-w][\\-xstT]){3})(?(\\+))?\\s+(?\\d+)\\s+(?\\S+)\\s+(?\\S+)\\s+(?\\d+)\\s+(?((?\\w{3})\\s+(?\\d{1,2})\\s+(?\\d{1,2}):(?\\d{2}))|((?\\w{3})\\s+(?\\d{1,2})\\s+(?\\d{4})))\\s+(?.+)$'),
++ REX_LISTMSDOS = new RegExp('^(?\\d{2})(?:\\-|\\/)(?\\d{2})(?:\\-|\\/)(?\\d{2,4})\\s+(?\\d{2}):(?\\d{2})\\s{0,1}(?[AaMmPp]{1,2})\\s+(?:(?\\d+)|(?\\))\\s+(?.+)$'),
+ RE_ENTRY_TOTAL = /^total/,
+ RE_RES_END = /(?:^|\r?\n)(\d{3}) [^\r\n]*\r?\n/,
+ RE_EOL = /\r?\n/g,
+@@ -82,7 +86,7 @@ Parser.parseListEntry = function(line) {
+ month, day, year,
+ hour, mins;
+
+- if (ret = XRegExp.exec(line, REX_LISTUNIX)) {
++ if (ret = execNamed(REX_LISTUNIX, line)) {
+ info = {
+ type: ret.type,
+ name: undefined,
+@@ -182,7 +186,7 @@ Parser.parseListEntry = function(line) {
+ } else
+ info.name = ret.name;
+ ret = info;
+- } else if (ret = XRegExp.exec(line, REX_LISTMSDOS)) {
++ } else if (ret = execNamed(REX_LISTMSDOS, line)) {
+ info = {
+ name: ret.name,
+ type: (ret.isdir ? 'd' : '-'),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a032984fe4b..2da832adaf2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1061,11 +1061,13 @@ overrides:
source-map-js: 1.2.2
'@grpc/grpc-js@<1.14.5': 1.14.5
prosemirror-view@<1.42.3: 1.42.3
+ '@icetee/ftp>xregexp': npm:empty-npm-package@1.0.0
patchedDependencies:
'@chat-adapter/teams@4.37.0': af5fb1d38a2af55399bd8b6934e77348a9b061fec6177c101dd9d7a787ae8a6c
'@chat-adapter/whatsapp@4.37.0': 950356dafb986b5cc9bd53069f7653b47b790a60b3590495cf53392ea4df2de4
'@confluentinc/kafka-javascript@1.9.1': c597d3e3d9d6d0d33588b7dc1e88d55f270ba155721aad464bd7fb03e28f5003
+ '@icetee/ftp@0.3.15': 19c547038e6400d80f724f77c3fad97093f9233d7596bf2dba38d3e0adb7dab9
'@langchain/google-common@2.1.24': b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d
'@langchain/openai@1.4.4': e16bcc93cf9037198faacf494fb81afba6e9abba749dc9a09c3abfb33e68d5c2
'@lezer/highlight': 97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c
@@ -25014,9 +25016,6 @@ packages:
resolution: {integrity: sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==}
engines: {node: '>=0.6.0'}
- xregexp@2.0.0:
- resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==}
-
xss@1.0.15:
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
engines: {node: '>= 0.10.0'}
@@ -28596,10 +28595,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@icetee/ftp@0.3.15':
+ '@icetee/ftp@0.3.15(patch_hash=19c547038e6400d80f724f77c3fad97093f9233d7596bf2dba38d3e0adb7dab9)':
dependencies:
readable-stream: 1.1.14
- xregexp: 2.0.0
+ xregexp: empty-npm-package@1.0.0
'@iconify-json/lucide@1.2.114':
dependencies:
@@ -41831,7 +41830,7 @@ snapshots:
promise-ftp@1.3.5(promise-ftp-common@1.1.5):
dependencies:
- '@icetee/ftp': 0.3.15
+ '@icetee/ftp': 0.3.15(patch_hash=19c547038e6400d80f724f77c3fad97093f9233d7596bf2dba38d3e0adb7dab9)
bluebird: 2.11.0
promise-ftp-common: 1.1.5
@@ -45407,8 +45406,6 @@ snapshots:
xpath@0.0.34: {}
- xregexp@2.0.0: {}
-
xss@1.0.15:
dependencies:
commander: 2.20.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 7acdb1aafe3..2664695d5d0 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -484,6 +484,8 @@ overrides:
source-map-js: 1.2.2
'@grpc/grpc-js@<1.14.5': 1.14.5
'prosemirror-view@<1.42.3': 1.42.3
+ # The @icetee/ftp patch replaces xregexp with native RegExp.
+ '@icetee/ftp>xregexp': npm:empty-npm-package@1.0.0
patchedDependencies:
bull@4.16.4: patches/bull@4.16.4.patch
@@ -508,6 +510,7 @@ patchedDependencies:
imapflow@1.7.1: patches/imapflow@1.7.1.patch
'@chat-adapter/teams@4.37.0': patches/@chat-adapter__teams@4.37.0.patch
'@chat-adapter/whatsapp@4.37.0': patches/@chat-adapter__whatsapp@4.37.0.patch
+ '@icetee/ftp@0.3.15': patches/@icetee__ftp@0.3.15.patch
allowBuilds:
'@biomejs/biome': false