@zero-transfer/ftps 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/index.cjs +348 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +228 -24
- package/dist/index.d.ts +228 -24
- package/dist/index.mjs +345 -102
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(ftps_exports, {
|
|
|
60
60
|
copyBetween: () => copyBetween,
|
|
61
61
|
createAtomicDeployPlan: () => createAtomicDeployPlan,
|
|
62
62
|
createBandwidthThrottle: () => createBandwidthThrottle,
|
|
63
|
+
createDefaultRetryPolicy: () => createDefaultRetryPolicy,
|
|
63
64
|
createFtpsProviderFactory: () => createFtpsProviderFactory,
|
|
64
65
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
65
66
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
@@ -96,8 +97,10 @@ __export(ftps_exports, {
|
|
|
96
97
|
parseRemoteManifest: () => parseRemoteManifest,
|
|
97
98
|
redactCommand: () => redactCommand,
|
|
98
99
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
100
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
99
101
|
redactObject: () => redactObject,
|
|
100
102
|
redactSecretSource: () => redactSecretSource,
|
|
103
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
101
104
|
redactValue: () => redactValue,
|
|
102
105
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
103
106
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -118,6 +121,68 @@ module.exports = __toCommonJS(ftps_exports);
|
|
|
118
121
|
// src/client/ZeroTransfer.ts
|
|
119
122
|
var import_node_events = require("events");
|
|
120
123
|
|
|
124
|
+
// src/logging/redaction.ts
|
|
125
|
+
var REDACTED = "[REDACTED]";
|
|
126
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
127
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
128
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
129
|
+
function isSensitiveKey(key) {
|
|
130
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
131
|
+
}
|
|
132
|
+
function redactCommand(command) {
|
|
133
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
134
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function redactValue(value) {
|
|
138
|
+
if (typeof value === "string") {
|
|
139
|
+
return redactCommand(value);
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return value.map((item) => redactValue(item));
|
|
143
|
+
}
|
|
144
|
+
if (value !== null && typeof value === "object") {
|
|
145
|
+
return redactObject(value);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
function redactObject(input) {
|
|
150
|
+
return Object.fromEntries(
|
|
151
|
+
Object.entries(input).map(([key, value]) => {
|
|
152
|
+
if (isSensitiveKey(key)) {
|
|
153
|
+
return [key, REDACTED];
|
|
154
|
+
}
|
|
155
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
156
|
+
return [key, redactUrlForLogging(value)];
|
|
157
|
+
}
|
|
158
|
+
return [key, redactValue(value)];
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function redactUrlForLogging(url) {
|
|
163
|
+
let parsed;
|
|
164
|
+
try {
|
|
165
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
166
|
+
} catch {
|
|
167
|
+
return REDACTED;
|
|
168
|
+
}
|
|
169
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
170
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
171
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
172
|
+
}
|
|
173
|
+
function redactErrorForLogging(error) {
|
|
174
|
+
if (error !== null && typeof error === "object") {
|
|
175
|
+
const candidate = error;
|
|
176
|
+
if (typeof candidate.toJSON === "function") {
|
|
177
|
+
return redactObject(candidate.toJSON());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (error instanceof Error) {
|
|
181
|
+
return redactObject({ message: error.message, name: error.name });
|
|
182
|
+
}
|
|
183
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
184
|
+
}
|
|
185
|
+
|
|
121
186
|
// src/errors/ZeroTransferError.ts
|
|
122
187
|
var ZeroTransferError = class extends Error {
|
|
123
188
|
/** Stable machine-readable error code. */
|
|
@@ -159,6 +224,11 @@ var ZeroTransferError = class extends Error {
|
|
|
159
224
|
/**
|
|
160
225
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
161
226
|
*
|
|
227
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
228
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
229
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
230
|
+
* for programmatic consumers.
|
|
231
|
+
*
|
|
162
232
|
* @returns A JSON-safe object containing public structured error fields.
|
|
163
233
|
*/
|
|
164
234
|
toJSON() {
|
|
@@ -168,12 +238,12 @@ var ZeroTransferError = class extends Error {
|
|
|
168
238
|
message: this.message,
|
|
169
239
|
protocol: this.protocol,
|
|
170
240
|
host: this.host,
|
|
171
|
-
command: this.command,
|
|
241
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
172
242
|
ftpCode: this.ftpCode,
|
|
173
243
|
sftpCode: this.sftpCode,
|
|
174
244
|
path: this.path,
|
|
175
245
|
retryable: this.retryable,
|
|
176
|
-
details: this.details
|
|
246
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
177
247
|
};
|
|
178
248
|
}
|
|
179
249
|
};
|
|
@@ -696,15 +766,20 @@ var ProviderRegistry = class {
|
|
|
696
766
|
var TransferClient = class {
|
|
697
767
|
/** Provider registry used by this client. */
|
|
698
768
|
registry;
|
|
769
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
770
|
+
defaults;
|
|
699
771
|
logger;
|
|
700
772
|
/**
|
|
701
773
|
* Creates a transfer client without opening any provider connections.
|
|
702
774
|
*
|
|
703
|
-
* @param options - Optional registry, provider factories, and
|
|
775
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
704
776
|
*/
|
|
705
777
|
constructor(options = {}) {
|
|
706
778
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
707
779
|
this.logger = options.logger ?? noopLogger;
|
|
780
|
+
if (options.defaults !== void 0) {
|
|
781
|
+
this.defaults = { ...options.defaults };
|
|
782
|
+
}
|
|
708
783
|
for (const provider of options.providers ?? []) {
|
|
709
784
|
this.registry.register(provider);
|
|
710
785
|
}
|
|
@@ -1277,18 +1352,25 @@ var TransferEngine = class {
|
|
|
1277
1352
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1278
1353
|
this.throwIfAborted(abortScope.signal, job);
|
|
1279
1354
|
const attemptStartedAt = this.now();
|
|
1355
|
+
const attemptScope = createAttemptScope(
|
|
1356
|
+
abortScope.signal,
|
|
1357
|
+
options.timeout,
|
|
1358
|
+
job,
|
|
1359
|
+
attemptNumber
|
|
1360
|
+
);
|
|
1280
1361
|
const context = this.createExecutionContext(
|
|
1281
1362
|
job,
|
|
1282
1363
|
attemptNumber,
|
|
1283
1364
|
attemptStartedAt,
|
|
1284
1365
|
options,
|
|
1285
|
-
|
|
1366
|
+
attemptScope.signal,
|
|
1286
1367
|
(bytesTransferred) => {
|
|
1287
1368
|
latestBytesTransferred = bytesTransferred;
|
|
1288
|
-
}
|
|
1369
|
+
},
|
|
1370
|
+
attemptScope.notifyProgress
|
|
1289
1371
|
);
|
|
1290
1372
|
try {
|
|
1291
|
-
const result = await runExecutor(executor, context,
|
|
1373
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1292
1374
|
context.throwIfAborted();
|
|
1293
1375
|
latestBytesTransferred = result.bytesTransferred;
|
|
1294
1376
|
const completedAt = this.now();
|
|
@@ -1306,16 +1388,27 @@ var TransferEngine = class {
|
|
|
1306
1388
|
summarizeError(error)
|
|
1307
1389
|
);
|
|
1308
1390
|
attempts.push(attempt);
|
|
1309
|
-
if (error instanceof AbortError ||
|
|
1391
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1310
1392
|
throw error;
|
|
1311
1393
|
}
|
|
1312
|
-
const retryInput = {
|
|
1394
|
+
const retryInput = {
|
|
1395
|
+
attempt: attemptNumber,
|
|
1396
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1397
|
+
error,
|
|
1398
|
+
job
|
|
1399
|
+
};
|
|
1313
1400
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1314
1401
|
if (shouldRetry) {
|
|
1315
1402
|
options.retry?.onRetry?.(retryInput);
|
|
1403
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1404
|
+
if (delayMs > 0) {
|
|
1405
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1406
|
+
}
|
|
1316
1407
|
continue;
|
|
1317
1408
|
}
|
|
1318
1409
|
throw createTransferFailure(job, error, attempts);
|
|
1410
|
+
} finally {
|
|
1411
|
+
attemptScope.dispose();
|
|
1319
1412
|
}
|
|
1320
1413
|
}
|
|
1321
1414
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1323,12 +1416,13 @@ var TransferEngine = class {
|
|
|
1323
1416
|
abortScope.dispose();
|
|
1324
1417
|
}
|
|
1325
1418
|
}
|
|
1326
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1419
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1327
1420
|
const context = {
|
|
1328
1421
|
attempt,
|
|
1329
1422
|
job,
|
|
1330
1423
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1331
1424
|
this.throwIfAborted(signal, job);
|
|
1425
|
+
notifyProgress();
|
|
1332
1426
|
updateBytesTransferred(bytesTransferred);
|
|
1333
1427
|
const progressInput = {
|
|
1334
1428
|
bytesTransferred,
|
|
@@ -1397,6 +1491,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1397
1491
|
signal: controller.signal
|
|
1398
1492
|
};
|
|
1399
1493
|
}
|
|
1494
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1495
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1496
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1497
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1498
|
+
const scope = {
|
|
1499
|
+
dispose: () => void 0,
|
|
1500
|
+
notifyProgress: () => void 0
|
|
1501
|
+
};
|
|
1502
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1503
|
+
return scope;
|
|
1504
|
+
}
|
|
1505
|
+
const controller = new AbortController();
|
|
1506
|
+
const retryable = timeout?.retryable ?? true;
|
|
1507
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1508
|
+
if (parentSignal?.aborted === true) {
|
|
1509
|
+
abortFromParent();
|
|
1510
|
+
} else {
|
|
1511
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1512
|
+
}
|
|
1513
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1514
|
+
controller.abort(
|
|
1515
|
+
new TimeoutError({
|
|
1516
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1517
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1518
|
+
retryable
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
}, attemptTimeoutMs);
|
|
1522
|
+
let stallTimer;
|
|
1523
|
+
const armStallWatchdog = () => {
|
|
1524
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1525
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1526
|
+
stallTimer = setTimeout(() => {
|
|
1527
|
+
controller.abort(
|
|
1528
|
+
new TimeoutError({
|
|
1529
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1530
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1531
|
+
retryable
|
|
1532
|
+
})
|
|
1533
|
+
);
|
|
1534
|
+
}, stallTimeoutMs);
|
|
1535
|
+
};
|
|
1536
|
+
armStallWatchdog();
|
|
1537
|
+
return {
|
|
1538
|
+
dispose: () => {
|
|
1539
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1540
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1541
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1542
|
+
},
|
|
1543
|
+
notifyProgress: armStallWatchdog,
|
|
1544
|
+
signal: controller.signal
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1548
|
+
return new Promise((resolve, reject) => {
|
|
1549
|
+
if (signal === void 0) {
|
|
1550
|
+
setTimeout(resolve, delayMs);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (signal.aborted) {
|
|
1554
|
+
reject(toAbortFailure(signal, job));
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const rejectAbort = () => {
|
|
1558
|
+
clearTimeout(timer);
|
|
1559
|
+
reject(toAbortFailure(signal, job));
|
|
1560
|
+
};
|
|
1561
|
+
const timer = setTimeout(() => {
|
|
1562
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1563
|
+
resolve();
|
|
1564
|
+
}, delayMs);
|
|
1565
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
function toAbortFailure(signal, job) {
|
|
1569
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1570
|
+
return signal.reason;
|
|
1571
|
+
}
|
|
1572
|
+
return new AbortError({
|
|
1573
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1574
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1575
|
+
retryable: false
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
function normalizeDelayMs(value) {
|
|
1579
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1580
|
+
return 0;
|
|
1581
|
+
}
|
|
1582
|
+
return Math.floor(value);
|
|
1583
|
+
}
|
|
1400
1584
|
function normalizeTimeoutMs(value) {
|
|
1401
1585
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1402
1586
|
return void 0;
|
|
@@ -1565,7 +1749,7 @@ async function runRoute(options) {
|
|
|
1565
1749
|
const executor = createProviderTransferExecutor({
|
|
1566
1750
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1567
1751
|
});
|
|
1568
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1752
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1569
1753
|
} finally {
|
|
1570
1754
|
if (destinationSession !== void 0) {
|
|
1571
1755
|
await destinationSession.disconnect();
|
|
@@ -1602,12 +1786,14 @@ function defaultJobId(route, now) {
|
|
|
1602
1786
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1603
1787
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1604
1788
|
}
|
|
1605
|
-
function buildExecuteOptions(options) {
|
|
1789
|
+
function buildExecuteOptions(options, client) {
|
|
1606
1790
|
const execute = {};
|
|
1791
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1792
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1607
1793
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1608
|
-
if (
|
|
1794
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1609
1795
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1610
|
-
if (
|
|
1796
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1611
1797
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1612
1798
|
return execute;
|
|
1613
1799
|
}
|
|
@@ -1664,41 +1850,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1664
1850
|
return `${source}->${destination}`;
|
|
1665
1851
|
}
|
|
1666
1852
|
|
|
1667
|
-
// src/logging/redaction.ts
|
|
1668
|
-
var REDACTED = "[REDACTED]";
|
|
1669
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1670
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1671
|
-
function isSensitiveKey(key) {
|
|
1672
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1673
|
-
}
|
|
1674
|
-
function redactCommand(command) {
|
|
1675
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1676
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
function redactValue(value) {
|
|
1680
|
-
if (typeof value === "string") {
|
|
1681
|
-
return redactCommand(value);
|
|
1682
|
-
}
|
|
1683
|
-
if (Array.isArray(value)) {
|
|
1684
|
-
return value.map((item) => redactValue(item));
|
|
1685
|
-
}
|
|
1686
|
-
if (value !== null && typeof value === "object") {
|
|
1687
|
-
return redactObject(value);
|
|
1688
|
-
}
|
|
1689
|
-
return value;
|
|
1690
|
-
}
|
|
1691
|
-
function redactObject(input) {
|
|
1692
|
-
return Object.fromEntries(
|
|
1693
|
-
Object.entries(input).map(([key, value]) => {
|
|
1694
|
-
if (isSensitiveKey(key)) {
|
|
1695
|
-
return [key, REDACTED];
|
|
1696
|
-
}
|
|
1697
|
-
return [key, redactValue(value)];
|
|
1698
|
-
})
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
1853
|
// src/profiles/SecretSource.ts
|
|
1703
1854
|
var import_node_buffer2 = require("buffer");
|
|
1704
1855
|
var import_promises = require("fs/promises");
|
|
@@ -2070,11 +2221,11 @@ var import_promises2 = require("fs/promises");
|
|
|
2070
2221
|
var import_node_path2 = __toESM(require("path"));
|
|
2071
2222
|
|
|
2072
2223
|
// src/utils/path.ts
|
|
2073
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2224
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2074
2225
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2075
2226
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2076
2227
|
throw new ConfigurationError({
|
|
2077
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2228
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2078
2229
|
retryable: false,
|
|
2079
2230
|
details: {
|
|
2080
2231
|
label
|
|
@@ -3376,7 +3527,6 @@ function expandAlgorithms(values) {
|
|
|
3376
3527
|
}
|
|
3377
3528
|
|
|
3378
3529
|
// src/profiles/importers/FileZillaImporter.ts
|
|
3379
|
-
var import_node_buffer5 = require("buffer");
|
|
3380
3530
|
function importFileZillaSites(xml) {
|
|
3381
3531
|
const events = tokenizeXml(xml);
|
|
3382
3532
|
if (events.length === 0) {
|
|
@@ -3392,7 +3542,6 @@ function importFileZillaSites(xml) {
|
|
|
3392
3542
|
const folderNamePending = [];
|
|
3393
3543
|
let inServer = false;
|
|
3394
3544
|
let serverFields = {};
|
|
3395
|
-
let serverPasswordEncoding;
|
|
3396
3545
|
let activeTag;
|
|
3397
3546
|
let captureFolderName = false;
|
|
3398
3547
|
for (const event of events) {
|
|
@@ -3405,13 +3554,9 @@ function importFileZillaSites(xml) {
|
|
|
3405
3554
|
if (event.name === "Server") {
|
|
3406
3555
|
inServer = true;
|
|
3407
3556
|
serverFields = {};
|
|
3408
|
-
serverPasswordEncoding = void 0;
|
|
3409
3557
|
continue;
|
|
3410
3558
|
}
|
|
3411
3559
|
activeTag = event.name;
|
|
3412
|
-
if (event.name === "Pass" && inServer) {
|
|
3413
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
3414
|
-
}
|
|
3415
3560
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
3416
3561
|
captureFolderName = true;
|
|
3417
3562
|
}
|
|
@@ -3437,7 +3582,7 @@ function importFileZillaSites(xml) {
|
|
|
3437
3582
|
}
|
|
3438
3583
|
if (event.name === "Server") {
|
|
3439
3584
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
3440
|
-
const result = buildSiteFromFields(serverFields
|
|
3585
|
+
const result = buildSiteFromFields(serverFields);
|
|
3441
3586
|
if (result.kind === "site") {
|
|
3442
3587
|
sites.push({ ...result.site, folder });
|
|
3443
3588
|
} else {
|
|
@@ -3449,7 +3594,6 @@ function importFileZillaSites(xml) {
|
|
|
3449
3594
|
}
|
|
3450
3595
|
inServer = false;
|
|
3451
3596
|
serverFields = {};
|
|
3452
|
-
serverPasswordEncoding = void 0;
|
|
3453
3597
|
activeTag = void 0;
|
|
3454
3598
|
continue;
|
|
3455
3599
|
}
|
|
@@ -3458,7 +3602,7 @@ function importFileZillaSites(xml) {
|
|
|
3458
3602
|
}
|
|
3459
3603
|
return { sites, skipped };
|
|
3460
3604
|
}
|
|
3461
|
-
function buildSiteFromFields(fields
|
|
3605
|
+
function buildSiteFromFields(fields) {
|
|
3462
3606
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
3463
3607
|
const host = (fields["Host"] ?? "").trim();
|
|
3464
3608
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -3477,18 +3621,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
3477
3621
|
}
|
|
3478
3622
|
const user = fields["User"]?.trim();
|
|
3479
3623
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
3480
|
-
let password;
|
|
3481
3624
|
const rawPass = fields["Pass"];
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
|
|
3485
|
-
} else {
|
|
3486
|
-
password = rawPass;
|
|
3487
|
-
}
|
|
3488
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
3489
|
-
}
|
|
3490
|
-
const site = { name, profile };
|
|
3491
|
-
if (password !== void 0) site.password = password;
|
|
3625
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
3626
|
+
const site = { hasStoredPassword, name, profile };
|
|
3492
3627
|
const logonText = fields["Logontype"];
|
|
3493
3628
|
if (logonText !== void 0) {
|
|
3494
3629
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -3731,6 +3866,62 @@ function mapFtp550(details) {
|
|
|
3731
3866
|
return new PermissionDeniedError(details);
|
|
3732
3867
|
}
|
|
3733
3868
|
|
|
3869
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
3870
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
3871
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
3872
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3873
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
3874
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
3875
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
3876
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
3877
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
3878
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
3879
|
+
const random = options.random ?? Math.random;
|
|
3880
|
+
return {
|
|
3881
|
+
getDelayMs(input) {
|
|
3882
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3883
|
+
if (retryAfterMs !== void 0) {
|
|
3884
|
+
return retryAfterMs;
|
|
3885
|
+
}
|
|
3886
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
3887
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
3888
|
+
return Math.floor(random() * cappedMs);
|
|
3889
|
+
},
|
|
3890
|
+
maxAttempts,
|
|
3891
|
+
shouldRetry(input) {
|
|
3892
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
3893
|
+
return false;
|
|
3894
|
+
}
|
|
3895
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
3896
|
+
return false;
|
|
3897
|
+
}
|
|
3898
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3899
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
3900
|
+
return false;
|
|
3901
|
+
}
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
function readRetryAfterMs(error) {
|
|
3907
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
3908
|
+
const value = error.details?.["retryAfterMs"];
|
|
3909
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
3910
|
+
return Math.floor(value);
|
|
3911
|
+
}
|
|
3912
|
+
function normalizePositiveInteger(value, fallback) {
|
|
3913
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
3914
|
+
return fallback;
|
|
3915
|
+
}
|
|
3916
|
+
return Math.floor(value);
|
|
3917
|
+
}
|
|
3918
|
+
function normalizeNonNegative(value, fallback) {
|
|
3919
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
3920
|
+
return fallback;
|
|
3921
|
+
}
|
|
3922
|
+
return Math.floor(value);
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3734
3925
|
// src/transfers/TransferPlan.ts
|
|
3735
3926
|
function createTransferPlan(input) {
|
|
3736
3927
|
const plan = {
|
|
@@ -3828,8 +4019,8 @@ var TransferQueue = class {
|
|
|
3828
4019
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
3829
4020
|
this.defaultExecutor = options.executor;
|
|
3830
4021
|
this.resolveExecutor = options.resolveExecutor;
|
|
3831
|
-
this.retry = options.retry;
|
|
3832
|
-
this.timeout = options.timeout;
|
|
4022
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
4023
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
3833
4024
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
3834
4025
|
this.onProgress = options.onProgress;
|
|
3835
4026
|
this.onReceipt = options.onReceipt;
|
|
@@ -4906,7 +5097,7 @@ function isMainModule(importMetaUrl) {
|
|
|
4906
5097
|
}
|
|
4907
5098
|
|
|
4908
5099
|
// src/providers/classic/ftp/FtpProvider.ts
|
|
4909
|
-
var
|
|
5100
|
+
var import_node_buffer5 = require("buffer");
|
|
4910
5101
|
var import_node_net = require("net");
|
|
4911
5102
|
var import_node_tls = require("tls");
|
|
4912
5103
|
|
|
@@ -4916,12 +5107,6 @@ var UNIX_LIST_MONTHS = new Map(
|
|
|
4916
5107
|
(month, index) => [month, index]
|
|
4917
5108
|
)
|
|
4918
5109
|
);
|
|
4919
|
-
function parseMlsdList(input, directory = ".") {
|
|
4920
|
-
return input.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0).map((line) => parseMlsdLine(line, directory)).filter((entry) => entry.name !== "." && entry.name !== "..");
|
|
4921
|
-
}
|
|
4922
|
-
function parseUnixList(input, directory = ".", now = /* @__PURE__ */ new Date()) {
|
|
4923
|
-
return input.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0 && !line.toLowerCase().startsWith("total ")).map((line) => parseUnixListLine(line, directory, now)).filter((entry) => entry.name !== "." && entry.name !== "..");
|
|
4924
|
-
}
|
|
4925
5110
|
function parseUnixListLine(line, directory = ".", now = /* @__PURE__ */ new Date()) {
|
|
4926
5111
|
const match = /^(\S{10})\s+\d+\s+(\S+)\s+(\S+)\s+(\d+)\s+([A-Za-z]{3})\s+(\d{1,2})\s+(\d{4}|\d{1,2}:\d{2})\s+(.+)$/.exec(
|
|
4927
5112
|
line
|
|
@@ -5797,38 +5982,53 @@ async function expectCompletion(control, command, path2) {
|
|
|
5797
5982
|
const response = await control.sendCommand(command);
|
|
5798
5983
|
assertPathCommandSucceeded(response, command, path2, control.providerId);
|
|
5799
5984
|
}
|
|
5800
|
-
async function
|
|
5801
|
-
const dataConnection = await openPassiveDataCommand(control, command, path2
|
|
5985
|
+
async function readPassiveLinesCommand(control, command, path2, onLine) {
|
|
5986
|
+
const dataConnection = await openPassiveDataCommand(control, command, path2);
|
|
5802
5987
|
try {
|
|
5803
|
-
const
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
path2,
|
|
5807
|
-
control.providerId
|
|
5808
|
-
);
|
|
5988
|
+
const failure = await consumePassiveLines(dataConnection, control.operationTimeoutMs, {
|
|
5989
|
+
command,
|
|
5990
|
+
onLine,
|
|
5991
|
+
path: path2,
|
|
5992
|
+
providerId: control.providerId
|
|
5993
|
+
});
|
|
5809
5994
|
const finalResponse = await control.readFinalResponse({
|
|
5810
5995
|
command,
|
|
5811
5996
|
operation: "data command completion",
|
|
5812
5997
|
path: path2
|
|
5813
5998
|
});
|
|
5814
5999
|
assertPathCommandSucceeded(finalResponse, command, path2, control.providerId);
|
|
5815
|
-
|
|
6000
|
+
if (failure !== void 0) throw failure;
|
|
5816
6001
|
} catch (error) {
|
|
5817
6002
|
dataConnection.close();
|
|
5818
6003
|
throw error;
|
|
5819
6004
|
}
|
|
5820
6005
|
}
|
|
5821
6006
|
async function readDirectoryEntries(control, path2) {
|
|
6007
|
+
const entries = [];
|
|
6008
|
+
const collectEntry = (entry) => {
|
|
6009
|
+
if (entry.name === "." || entry.name === "..") return;
|
|
6010
|
+
entries.push(entry);
|
|
6011
|
+
};
|
|
5822
6012
|
try {
|
|
5823
|
-
|
|
5824
|
-
|
|
6013
|
+
await readPassiveLinesCommand(control, `MLSD ${path2}`, path2, (rawLine) => {
|
|
6014
|
+
const line = rawLine.trimEnd();
|
|
6015
|
+
if (line.length === 0) return;
|
|
6016
|
+
collectEntry(parseMlsdLine(line, path2));
|
|
6017
|
+
});
|
|
6018
|
+
return entries;
|
|
5825
6019
|
} catch (error) {
|
|
5826
6020
|
if (!isUnsupportedFtpCommandError(error, "MLSD")) {
|
|
5827
6021
|
throw error;
|
|
5828
6022
|
}
|
|
5829
6023
|
}
|
|
5830
|
-
|
|
5831
|
-
|
|
6024
|
+
entries.length = 0;
|
|
6025
|
+
const now = /* @__PURE__ */ new Date();
|
|
6026
|
+
await readPassiveLinesCommand(control, `LIST ${path2}`, path2, (rawLine) => {
|
|
6027
|
+
const line = rawLine.trimEnd();
|
|
6028
|
+
if (line.length === 0 || line.toLowerCase().startsWith("total ")) return;
|
|
6029
|
+
collectEntry(parseUnixListLine(line, path2, now));
|
|
6030
|
+
});
|
|
6031
|
+
return entries;
|
|
5832
6032
|
}
|
|
5833
6033
|
async function openPassiveDataCommand(control, command, path2, options = {}) {
|
|
5834
6034
|
const offset = normalizeOptionalByteCount3(options.offset, "offset", path2);
|
|
@@ -6001,22 +6201,58 @@ function openPassiveDataConnection(endpoint, timeoutMs, path2, control) {
|
|
|
6001
6201
|
}
|
|
6002
6202
|
};
|
|
6003
6203
|
}
|
|
6004
|
-
|
|
6005
|
-
|
|
6204
|
+
var MAX_LIST_LINE_BYTES = 64 * 1024;
|
|
6205
|
+
async function consumePassiveLines(dataConnection, timeoutMs, input) {
|
|
6206
|
+
let carry = import_node_buffer5.Buffer.alloc(0);
|
|
6207
|
+
let failure;
|
|
6006
6208
|
const clearIdleTimeout = setSocketTimeout(dataConnection.socket, timeoutMs, {
|
|
6007
6209
|
host: dataConnection.endpoint.host,
|
|
6008
6210
|
operation: "passive data transfer",
|
|
6009
|
-
path:
|
|
6010
|
-
providerId
|
|
6211
|
+
path: input.path,
|
|
6212
|
+
providerId: input.providerId
|
|
6011
6213
|
});
|
|
6214
|
+
const overlongLineFailure = () => new ParseError({
|
|
6215
|
+
details: { command: input.command, limitBytes: MAX_LIST_LINE_BYTES, path: input.path },
|
|
6216
|
+
message: `FTP listing line exceeded ${String(MAX_LIST_LINE_BYTES)} bytes for ${input.command}`,
|
|
6217
|
+
retryable: false
|
|
6218
|
+
});
|
|
6219
|
+
const emit = (lineBytes) => {
|
|
6220
|
+
if (failure !== void 0) return;
|
|
6221
|
+
let end = lineBytes.length;
|
|
6222
|
+
if (end > 0 && lineBytes[end - 1] === 13) end -= 1;
|
|
6223
|
+
if (end === 0) return;
|
|
6224
|
+
if (end > MAX_LIST_LINE_BYTES) {
|
|
6225
|
+
failure = overlongLineFailure();
|
|
6226
|
+
return;
|
|
6227
|
+
}
|
|
6228
|
+
try {
|
|
6229
|
+
input.onLine(lineBytes.toString("utf8", 0, end));
|
|
6230
|
+
} catch (error) {
|
|
6231
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
6232
|
+
}
|
|
6233
|
+
};
|
|
6012
6234
|
try {
|
|
6013
6235
|
for await (const chunk of dataConnection.socket) {
|
|
6014
|
-
|
|
6236
|
+
if (failure !== void 0) continue;
|
|
6237
|
+
const data = carry.length > 0 ? import_node_buffer5.Buffer.concat([carry, chunk]) : chunk;
|
|
6238
|
+
let start = 0;
|
|
6239
|
+
let newline = data.indexOf(10, start);
|
|
6240
|
+
while (newline !== -1) {
|
|
6241
|
+
emit(data.subarray(start, newline));
|
|
6242
|
+
start = newline + 1;
|
|
6243
|
+
newline = data.indexOf(10, start);
|
|
6244
|
+
}
|
|
6245
|
+
carry = import_node_buffer5.Buffer.from(data.subarray(start));
|
|
6246
|
+
if (carry.length > MAX_LIST_LINE_BYTES && failure === void 0) {
|
|
6247
|
+
failure = overlongLineFailure();
|
|
6248
|
+
}
|
|
6249
|
+
if (failure !== void 0) carry = import_node_buffer5.Buffer.alloc(0);
|
|
6015
6250
|
}
|
|
6251
|
+
if (carry.length > 0) emit(carry);
|
|
6016
6252
|
} finally {
|
|
6017
6253
|
clearIdleTimeout();
|
|
6018
6254
|
}
|
|
6019
|
-
return
|
|
6255
|
+
return failure;
|
|
6020
6256
|
}
|
|
6021
6257
|
async function* createPassiveReadSource(control, dataConnection, command, path2, range, request) {
|
|
6022
6258
|
let bytesEmitted = 0;
|
|
@@ -6033,7 +6269,7 @@ async function* createPassiveReadSource(control, dataConnection, command, path2,
|
|
|
6033
6269
|
});
|
|
6034
6270
|
for await (const chunk of dataConnection.socket) {
|
|
6035
6271
|
request.throwIfAborted();
|
|
6036
|
-
const buffer =
|
|
6272
|
+
const buffer = import_node_buffer5.Buffer.from(chunk);
|
|
6037
6273
|
if (range.length === void 0) {
|
|
6038
6274
|
bytesEmitted += buffer.byteLength;
|
|
6039
6275
|
yield new Uint8Array(buffer);
|
|
@@ -6265,6 +6501,13 @@ function createTlsPinnedFingerprints(profile) {
|
|
|
6265
6501
|
if (pinnedFingerprint256 === void 0) {
|
|
6266
6502
|
return void 0;
|
|
6267
6503
|
}
|
|
6504
|
+
if (profile.tls?.rejectUnauthorized === false) {
|
|
6505
|
+
throw new ConfigurationError({
|
|
6506
|
+
message: "FTPS tls.pinnedFingerprint256 cannot be combined with rejectUnauthorized: false; pin verification runs after the TLS handshake, so chain validation must stay enabled. For self-signed certificates supply tls.ca instead of disabling validation.",
|
|
6507
|
+
protocol: FTPS_PROVIDER_ID,
|
|
6508
|
+
retryable: false
|
|
6509
|
+
});
|
|
6510
|
+
}
|
|
6268
6511
|
const fingerprints = Array.isArray(pinnedFingerprint256) ? pinnedFingerprint256 : [pinnedFingerprint256];
|
|
6269
6512
|
if (fingerprints.length === 0) {
|
|
6270
6513
|
throw new ConfigurationError({
|
|
@@ -6319,9 +6562,9 @@ function normalizeCertificateFingerprint256(certificate) {
|
|
|
6319
6562
|
}
|
|
6320
6563
|
function normalizeTlsSecretValue(value) {
|
|
6321
6564
|
if (Array.isArray(value)) {
|
|
6322
|
-
return value.map((item) =>
|
|
6565
|
+
return value.map((item) => import_node_buffer5.Buffer.isBuffer(item) ? import_node_buffer5.Buffer.from(item) : item);
|
|
6323
6566
|
}
|
|
6324
|
-
return
|
|
6567
|
+
return import_node_buffer5.Buffer.isBuffer(value) ? import_node_buffer5.Buffer.from(value) : value;
|
|
6325
6568
|
}
|
|
6326
6569
|
async function authenticateFtpSession(control, username, password, host) {
|
|
6327
6570
|
const safeUsername = assertSafeFtpArgument(username, "username");
|
|
@@ -6500,7 +6743,7 @@ function compareEntries5(left, right) {
|
|
|
6500
6743
|
return left.path.localeCompare(right.path);
|
|
6501
6744
|
}
|
|
6502
6745
|
function secretToString(value) {
|
|
6503
|
-
return
|
|
6746
|
+
return import_node_buffer5.Buffer.isBuffer(value) ? value.toString("utf8") : value;
|
|
6504
6747
|
}
|
|
6505
6748
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6506
6749
|
0 && (module.exports = {
|
|
@@ -6534,6 +6777,7 @@ function secretToString(value) {
|
|
|
6534
6777
|
copyBetween,
|
|
6535
6778
|
createAtomicDeployPlan,
|
|
6536
6779
|
createBandwidthThrottle,
|
|
6780
|
+
createDefaultRetryPolicy,
|
|
6537
6781
|
createFtpsProviderFactory,
|
|
6538
6782
|
createLocalProviderFactory,
|
|
6539
6783
|
createMemoryProviderFactory,
|
|
@@ -6570,8 +6814,10 @@ function secretToString(value) {
|
|
|
6570
6814
|
parseRemoteManifest,
|
|
6571
6815
|
redactCommand,
|
|
6572
6816
|
redactConnectionProfile,
|
|
6817
|
+
redactErrorForLogging,
|
|
6573
6818
|
redactObject,
|
|
6574
6819
|
redactSecretSource,
|
|
6820
|
+
redactUrlForLogging,
|
|
6575
6821
|
redactValue,
|
|
6576
6822
|
resolveConnectionProfileSecrets,
|
|
6577
6823
|
resolveOpenSshHost,
|