@zero-transfer/sdk 0.4.7 → 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 +23 -11
- package/assets/zero-transfer-icon.svg +117 -0
- package/dist/index.cjs +638 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +297 -44
- package/dist/index.d.ts +297 -44
- package/dist/index.mjs +634 -161
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
AbortError: () => AbortError,
|
|
34
34
|
ApprovalRegistry: () => ApprovalRegistry,
|
|
35
35
|
ApprovalRejectedError: () => ApprovalRejectedError,
|
|
36
|
+
ApprovalTimeoutError: () => ApprovalTimeoutError,
|
|
36
37
|
AuthenticationError: () => AuthenticationError,
|
|
37
38
|
AuthorizationError: () => AuthorizationError,
|
|
38
39
|
CLASSIC_PROVIDER_IDS: () => CLASSIC_PROVIDER_IDS,
|
|
@@ -82,6 +83,7 @@ __export(index_exports, {
|
|
|
82
83
|
createAtomicDeployPlan: () => createAtomicDeployPlan,
|
|
83
84
|
createAzureBlobProviderFactory: () => createAzureBlobProviderFactory,
|
|
84
85
|
createBandwidthThrottle: () => createBandwidthThrottle,
|
|
86
|
+
createDefaultRetryPolicy: () => createDefaultRetryPolicy,
|
|
85
87
|
createDropboxProviderFactory: () => createDropboxProviderFactory,
|
|
86
88
|
createFileSystemS3MultipartResumeStore: () => createFileSystemS3MultipartResumeStore,
|
|
87
89
|
createFtpProviderFactory: () => createFtpProviderFactory,
|
|
@@ -151,8 +153,10 @@ __export(index_exports, {
|
|
|
151
153
|
parseUnixListLine: () => parseUnixListLine,
|
|
152
154
|
redactCommand: () => redactCommand,
|
|
153
155
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
156
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
154
157
|
redactObject: () => redactObject,
|
|
155
158
|
redactSecretSource: () => redactSecretSource,
|
|
159
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
156
160
|
redactValue: () => redactValue,
|
|
157
161
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
158
162
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -178,6 +182,68 @@ module.exports = __toCommonJS(index_exports);
|
|
|
178
182
|
// src/client/ZeroTransfer.ts
|
|
179
183
|
var import_node_events = require("events");
|
|
180
184
|
|
|
185
|
+
// src/logging/redaction.ts
|
|
186
|
+
var REDACTED = "[REDACTED]";
|
|
187
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
188
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
189
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
190
|
+
function isSensitiveKey(key) {
|
|
191
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
192
|
+
}
|
|
193
|
+
function redactCommand(command) {
|
|
194
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
195
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function redactValue(value) {
|
|
199
|
+
if (typeof value === "string") {
|
|
200
|
+
return redactCommand(value);
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
return value.map((item) => redactValue(item));
|
|
204
|
+
}
|
|
205
|
+
if (value !== null && typeof value === "object") {
|
|
206
|
+
return redactObject(value);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
function redactObject(input) {
|
|
211
|
+
return Object.fromEntries(
|
|
212
|
+
Object.entries(input).map(([key, value]) => {
|
|
213
|
+
if (isSensitiveKey(key)) {
|
|
214
|
+
return [key, REDACTED];
|
|
215
|
+
}
|
|
216
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
217
|
+
return [key, redactUrlForLogging(value)];
|
|
218
|
+
}
|
|
219
|
+
return [key, redactValue(value)];
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function redactUrlForLogging(url) {
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
227
|
+
} catch {
|
|
228
|
+
return REDACTED;
|
|
229
|
+
}
|
|
230
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
231
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
232
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
233
|
+
}
|
|
234
|
+
function redactErrorForLogging(error) {
|
|
235
|
+
if (error !== null && typeof error === "object") {
|
|
236
|
+
const candidate = error;
|
|
237
|
+
if (typeof candidate.toJSON === "function") {
|
|
238
|
+
return redactObject(candidate.toJSON());
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (error instanceof Error) {
|
|
242
|
+
return redactObject({ message: error.message, name: error.name });
|
|
243
|
+
}
|
|
244
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
245
|
+
}
|
|
246
|
+
|
|
181
247
|
// src/errors/ZeroTransferError.ts
|
|
182
248
|
var ZeroTransferError = class extends Error {
|
|
183
249
|
/** Stable machine-readable error code. */
|
|
@@ -219,6 +285,11 @@ var ZeroTransferError = class extends Error {
|
|
|
219
285
|
/**
|
|
220
286
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
221
287
|
*
|
|
288
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
289
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
290
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
291
|
+
* for programmatic consumers.
|
|
292
|
+
*
|
|
222
293
|
* @returns A JSON-safe object containing public structured error fields.
|
|
223
294
|
*/
|
|
224
295
|
toJSON() {
|
|
@@ -228,12 +299,12 @@ var ZeroTransferError = class extends Error {
|
|
|
228
299
|
message: this.message,
|
|
229
300
|
protocol: this.protocol,
|
|
230
301
|
host: this.host,
|
|
231
|
-
command: this.command,
|
|
302
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
232
303
|
ftpCode: this.ftpCode,
|
|
233
304
|
sftpCode: this.sftpCode,
|
|
234
305
|
path: this.path,
|
|
235
306
|
retryable: this.retryable,
|
|
236
|
-
details: this.details
|
|
307
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
237
308
|
};
|
|
238
309
|
}
|
|
239
310
|
};
|
|
@@ -756,15 +827,20 @@ var ProviderRegistry = class {
|
|
|
756
827
|
var TransferClient = class {
|
|
757
828
|
/** Provider registry used by this client. */
|
|
758
829
|
registry;
|
|
830
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
831
|
+
defaults;
|
|
759
832
|
logger;
|
|
760
833
|
/**
|
|
761
834
|
* Creates a transfer client without opening any provider connections.
|
|
762
835
|
*
|
|
763
|
-
* @param options - Optional registry, provider factories, and
|
|
836
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
764
837
|
*/
|
|
765
838
|
constructor(options = {}) {
|
|
766
839
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
767
840
|
this.logger = options.logger ?? noopLogger;
|
|
841
|
+
if (options.defaults !== void 0) {
|
|
842
|
+
this.defaults = { ...options.defaults };
|
|
843
|
+
}
|
|
768
844
|
for (const provider of options.providers ?? []) {
|
|
769
845
|
this.registry.register(provider);
|
|
770
846
|
}
|
|
@@ -1337,18 +1413,25 @@ var TransferEngine = class {
|
|
|
1337
1413
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1338
1414
|
this.throwIfAborted(abortScope.signal, job);
|
|
1339
1415
|
const attemptStartedAt = this.now();
|
|
1416
|
+
const attemptScope = createAttemptScope(
|
|
1417
|
+
abortScope.signal,
|
|
1418
|
+
options.timeout,
|
|
1419
|
+
job,
|
|
1420
|
+
attemptNumber
|
|
1421
|
+
);
|
|
1340
1422
|
const context = this.createExecutionContext(
|
|
1341
1423
|
job,
|
|
1342
1424
|
attemptNumber,
|
|
1343
1425
|
attemptStartedAt,
|
|
1344
1426
|
options,
|
|
1345
|
-
|
|
1427
|
+
attemptScope.signal,
|
|
1346
1428
|
(bytesTransferred) => {
|
|
1347
1429
|
latestBytesTransferred = bytesTransferred;
|
|
1348
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
attemptScope.notifyProgress
|
|
1349
1432
|
);
|
|
1350
1433
|
try {
|
|
1351
|
-
const result = await runExecutor(executor, context,
|
|
1434
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1352
1435
|
context.throwIfAborted();
|
|
1353
1436
|
latestBytesTransferred = result.bytesTransferred;
|
|
1354
1437
|
const completedAt = this.now();
|
|
@@ -1366,16 +1449,27 @@ var TransferEngine = class {
|
|
|
1366
1449
|
summarizeError(error)
|
|
1367
1450
|
);
|
|
1368
1451
|
attempts.push(attempt);
|
|
1369
|
-
if (error instanceof AbortError ||
|
|
1452
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1370
1453
|
throw error;
|
|
1371
1454
|
}
|
|
1372
|
-
const retryInput = {
|
|
1455
|
+
const retryInput = {
|
|
1456
|
+
attempt: attemptNumber,
|
|
1457
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1458
|
+
error,
|
|
1459
|
+
job
|
|
1460
|
+
};
|
|
1373
1461
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1374
1462
|
if (shouldRetry) {
|
|
1375
1463
|
options.retry?.onRetry?.(retryInput);
|
|
1464
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1465
|
+
if (delayMs > 0) {
|
|
1466
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1467
|
+
}
|
|
1376
1468
|
continue;
|
|
1377
1469
|
}
|
|
1378
1470
|
throw createTransferFailure(job, error, attempts);
|
|
1471
|
+
} finally {
|
|
1472
|
+
attemptScope.dispose();
|
|
1379
1473
|
}
|
|
1380
1474
|
}
|
|
1381
1475
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1383,12 +1477,13 @@ var TransferEngine = class {
|
|
|
1383
1477
|
abortScope.dispose();
|
|
1384
1478
|
}
|
|
1385
1479
|
}
|
|
1386
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1480
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1387
1481
|
const context = {
|
|
1388
1482
|
attempt,
|
|
1389
1483
|
job,
|
|
1390
1484
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1391
1485
|
this.throwIfAborted(signal, job);
|
|
1486
|
+
notifyProgress();
|
|
1392
1487
|
updateBytesTransferred(bytesTransferred);
|
|
1393
1488
|
const progressInput = {
|
|
1394
1489
|
bytesTransferred,
|
|
@@ -1457,6 +1552,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1457
1552
|
signal: controller.signal
|
|
1458
1553
|
};
|
|
1459
1554
|
}
|
|
1555
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1556
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1557
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1558
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1559
|
+
const scope = {
|
|
1560
|
+
dispose: () => void 0,
|
|
1561
|
+
notifyProgress: () => void 0
|
|
1562
|
+
};
|
|
1563
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1564
|
+
return scope;
|
|
1565
|
+
}
|
|
1566
|
+
const controller = new AbortController();
|
|
1567
|
+
const retryable = timeout?.retryable ?? true;
|
|
1568
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1569
|
+
if (parentSignal?.aborted === true) {
|
|
1570
|
+
abortFromParent();
|
|
1571
|
+
} else {
|
|
1572
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1573
|
+
}
|
|
1574
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1575
|
+
controller.abort(
|
|
1576
|
+
new TimeoutError({
|
|
1577
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1578
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1579
|
+
retryable
|
|
1580
|
+
})
|
|
1581
|
+
);
|
|
1582
|
+
}, attemptTimeoutMs);
|
|
1583
|
+
let stallTimer;
|
|
1584
|
+
const armStallWatchdog = () => {
|
|
1585
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1586
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1587
|
+
stallTimer = setTimeout(() => {
|
|
1588
|
+
controller.abort(
|
|
1589
|
+
new TimeoutError({
|
|
1590
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1591
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1592
|
+
retryable
|
|
1593
|
+
})
|
|
1594
|
+
);
|
|
1595
|
+
}, stallTimeoutMs);
|
|
1596
|
+
};
|
|
1597
|
+
armStallWatchdog();
|
|
1598
|
+
return {
|
|
1599
|
+
dispose: () => {
|
|
1600
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1601
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1602
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1603
|
+
},
|
|
1604
|
+
notifyProgress: armStallWatchdog,
|
|
1605
|
+
signal: controller.signal
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1609
|
+
return new Promise((resolve, reject) => {
|
|
1610
|
+
if (signal === void 0) {
|
|
1611
|
+
setTimeout(resolve, delayMs);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (signal.aborted) {
|
|
1615
|
+
reject(toAbortFailure(signal, job));
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
const rejectAbort = () => {
|
|
1619
|
+
clearTimeout(timer);
|
|
1620
|
+
reject(toAbortFailure(signal, job));
|
|
1621
|
+
};
|
|
1622
|
+
const timer = setTimeout(() => {
|
|
1623
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1624
|
+
resolve();
|
|
1625
|
+
}, delayMs);
|
|
1626
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
function toAbortFailure(signal, job) {
|
|
1630
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1631
|
+
return signal.reason;
|
|
1632
|
+
}
|
|
1633
|
+
return new AbortError({
|
|
1634
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1635
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1636
|
+
retryable: false
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
function normalizeDelayMs(value) {
|
|
1640
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1641
|
+
return 0;
|
|
1642
|
+
}
|
|
1643
|
+
return Math.floor(value);
|
|
1644
|
+
}
|
|
1460
1645
|
function normalizeTimeoutMs(value) {
|
|
1461
1646
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1462
1647
|
return void 0;
|
|
@@ -1625,7 +1810,7 @@ async function runRoute(options) {
|
|
|
1625
1810
|
const executor = createProviderTransferExecutor({
|
|
1626
1811
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1627
1812
|
});
|
|
1628
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1813
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1629
1814
|
} finally {
|
|
1630
1815
|
if (destinationSession !== void 0) {
|
|
1631
1816
|
await destinationSession.disconnect();
|
|
@@ -1662,12 +1847,14 @@ function defaultJobId(route, now) {
|
|
|
1662
1847
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1663
1848
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1664
1849
|
}
|
|
1665
|
-
function buildExecuteOptions(options) {
|
|
1850
|
+
function buildExecuteOptions(options, client) {
|
|
1666
1851
|
const execute = {};
|
|
1852
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1853
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1667
1854
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1668
|
-
if (
|
|
1855
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1669
1856
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1670
|
-
if (
|
|
1857
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1671
1858
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1672
1859
|
return execute;
|
|
1673
1860
|
}
|
|
@@ -1724,41 +1911,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1724
1911
|
return `${source}->${destination}`;
|
|
1725
1912
|
}
|
|
1726
1913
|
|
|
1727
|
-
// src/logging/redaction.ts
|
|
1728
|
-
var REDACTED = "[REDACTED]";
|
|
1729
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1730
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1731
|
-
function isSensitiveKey(key) {
|
|
1732
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1733
|
-
}
|
|
1734
|
-
function redactCommand(command) {
|
|
1735
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1736
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
function redactValue(value) {
|
|
1740
|
-
if (typeof value === "string") {
|
|
1741
|
-
return redactCommand(value);
|
|
1742
|
-
}
|
|
1743
|
-
if (Array.isArray(value)) {
|
|
1744
|
-
return value.map((item) => redactValue(item));
|
|
1745
|
-
}
|
|
1746
|
-
if (value !== null && typeof value === "object") {
|
|
1747
|
-
return redactObject(value);
|
|
1748
|
-
}
|
|
1749
|
-
return value;
|
|
1750
|
-
}
|
|
1751
|
-
function redactObject(input) {
|
|
1752
|
-
return Object.fromEntries(
|
|
1753
|
-
Object.entries(input).map(([key, value]) => {
|
|
1754
|
-
if (isSensitiveKey(key)) {
|
|
1755
|
-
return [key, REDACTED];
|
|
1756
|
-
}
|
|
1757
|
-
return [key, redactValue(value)];
|
|
1758
|
-
})
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
1914
|
// src/profiles/SecretSource.ts
|
|
1763
1915
|
var import_node_buffer2 = require("buffer");
|
|
1764
1916
|
var import_promises = require("fs/promises");
|
|
@@ -2180,11 +2332,11 @@ async function resolveTlsSecretSource(source, options) {
|
|
|
2180
2332
|
}
|
|
2181
2333
|
|
|
2182
2334
|
// src/utils/path.ts
|
|
2183
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2335
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2184
2336
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2185
2337
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2186
2338
|
throw new ConfigurationError({
|
|
2187
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2339
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2188
2340
|
retryable: false,
|
|
2189
2341
|
details: {
|
|
2190
2342
|
label
|
|
@@ -3146,38 +3298,53 @@ async function expectCompletion(control, command, path2) {
|
|
|
3146
3298
|
const response = await control.sendCommand(command);
|
|
3147
3299
|
assertPathCommandSucceeded(response, command, path2, control.providerId);
|
|
3148
3300
|
}
|
|
3149
|
-
async function
|
|
3150
|
-
const dataConnection = await openPassiveDataCommand(control, command, path2
|
|
3301
|
+
async function readPassiveLinesCommand(control, command, path2, onLine) {
|
|
3302
|
+
const dataConnection = await openPassiveDataCommand(control, command, path2);
|
|
3151
3303
|
try {
|
|
3152
|
-
const
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
path2,
|
|
3156
|
-
control.providerId
|
|
3157
|
-
);
|
|
3304
|
+
const failure = await consumePassiveLines(dataConnection, control.operationTimeoutMs, {
|
|
3305
|
+
command,
|
|
3306
|
+
onLine,
|
|
3307
|
+
path: path2,
|
|
3308
|
+
providerId: control.providerId
|
|
3309
|
+
});
|
|
3158
3310
|
const finalResponse = await control.readFinalResponse({
|
|
3159
3311
|
command,
|
|
3160
3312
|
operation: "data command completion",
|
|
3161
3313
|
path: path2
|
|
3162
3314
|
});
|
|
3163
3315
|
assertPathCommandSucceeded(finalResponse, command, path2, control.providerId);
|
|
3164
|
-
|
|
3316
|
+
if (failure !== void 0) throw failure;
|
|
3165
3317
|
} catch (error) {
|
|
3166
3318
|
dataConnection.close();
|
|
3167
3319
|
throw error;
|
|
3168
3320
|
}
|
|
3169
3321
|
}
|
|
3170
3322
|
async function readDirectoryEntries(control, path2) {
|
|
3323
|
+
const entries = [];
|
|
3324
|
+
const collectEntry = (entry) => {
|
|
3325
|
+
if (entry.name === "." || entry.name === "..") return;
|
|
3326
|
+
entries.push(entry);
|
|
3327
|
+
};
|
|
3171
3328
|
try {
|
|
3172
|
-
|
|
3173
|
-
|
|
3329
|
+
await readPassiveLinesCommand(control, `MLSD ${path2}`, path2, (rawLine) => {
|
|
3330
|
+
const line = rawLine.trimEnd();
|
|
3331
|
+
if (line.length === 0) return;
|
|
3332
|
+
collectEntry(parseMlsdLine(line, path2));
|
|
3333
|
+
});
|
|
3334
|
+
return entries;
|
|
3174
3335
|
} catch (error) {
|
|
3175
3336
|
if (!isUnsupportedFtpCommandError(error, "MLSD")) {
|
|
3176
3337
|
throw error;
|
|
3177
3338
|
}
|
|
3178
3339
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3340
|
+
entries.length = 0;
|
|
3341
|
+
const now = /* @__PURE__ */ new Date();
|
|
3342
|
+
await readPassiveLinesCommand(control, `LIST ${path2}`, path2, (rawLine) => {
|
|
3343
|
+
const line = rawLine.trimEnd();
|
|
3344
|
+
if (line.length === 0 || line.toLowerCase().startsWith("total ")) return;
|
|
3345
|
+
collectEntry(parseUnixListLine(line, path2, now));
|
|
3346
|
+
});
|
|
3347
|
+
return entries;
|
|
3181
3348
|
}
|
|
3182
3349
|
async function openPassiveDataCommand(control, command, path2, options = {}) {
|
|
3183
3350
|
const offset = normalizeOptionalByteCount(options.offset, "offset", path2);
|
|
@@ -3350,22 +3517,58 @@ function openPassiveDataConnection(endpoint, timeoutMs, path2, control) {
|
|
|
3350
3517
|
}
|
|
3351
3518
|
};
|
|
3352
3519
|
}
|
|
3353
|
-
|
|
3354
|
-
|
|
3520
|
+
var MAX_LIST_LINE_BYTES = 64 * 1024;
|
|
3521
|
+
async function consumePassiveLines(dataConnection, timeoutMs, input) {
|
|
3522
|
+
let carry = import_node_buffer3.Buffer.alloc(0);
|
|
3523
|
+
let failure;
|
|
3355
3524
|
const clearIdleTimeout = setSocketTimeout(dataConnection.socket, timeoutMs, {
|
|
3356
3525
|
host: dataConnection.endpoint.host,
|
|
3357
3526
|
operation: "passive data transfer",
|
|
3358
|
-
path:
|
|
3359
|
-
providerId
|
|
3527
|
+
path: input.path,
|
|
3528
|
+
providerId: input.providerId
|
|
3360
3529
|
});
|
|
3530
|
+
const overlongLineFailure = () => new ParseError({
|
|
3531
|
+
details: { command: input.command, limitBytes: MAX_LIST_LINE_BYTES, path: input.path },
|
|
3532
|
+
message: `FTP listing line exceeded ${String(MAX_LIST_LINE_BYTES)} bytes for ${input.command}`,
|
|
3533
|
+
retryable: false
|
|
3534
|
+
});
|
|
3535
|
+
const emit = (lineBytes) => {
|
|
3536
|
+
if (failure !== void 0) return;
|
|
3537
|
+
let end = lineBytes.length;
|
|
3538
|
+
if (end > 0 && lineBytes[end - 1] === 13) end -= 1;
|
|
3539
|
+
if (end === 0) return;
|
|
3540
|
+
if (end > MAX_LIST_LINE_BYTES) {
|
|
3541
|
+
failure = overlongLineFailure();
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
try {
|
|
3545
|
+
input.onLine(lineBytes.toString("utf8", 0, end));
|
|
3546
|
+
} catch (error) {
|
|
3547
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3361
3550
|
try {
|
|
3362
3551
|
for await (const chunk of dataConnection.socket) {
|
|
3363
|
-
|
|
3552
|
+
if (failure !== void 0) continue;
|
|
3553
|
+
const data = carry.length > 0 ? import_node_buffer3.Buffer.concat([carry, chunk]) : chunk;
|
|
3554
|
+
let start = 0;
|
|
3555
|
+
let newline = data.indexOf(10, start);
|
|
3556
|
+
while (newline !== -1) {
|
|
3557
|
+
emit(data.subarray(start, newline));
|
|
3558
|
+
start = newline + 1;
|
|
3559
|
+
newline = data.indexOf(10, start);
|
|
3560
|
+
}
|
|
3561
|
+
carry = import_node_buffer3.Buffer.from(data.subarray(start));
|
|
3562
|
+
if (carry.length > MAX_LIST_LINE_BYTES && failure === void 0) {
|
|
3563
|
+
failure = overlongLineFailure();
|
|
3564
|
+
}
|
|
3565
|
+
if (failure !== void 0) carry = import_node_buffer3.Buffer.alloc(0);
|
|
3364
3566
|
}
|
|
3567
|
+
if (carry.length > 0) emit(carry);
|
|
3365
3568
|
} finally {
|
|
3366
3569
|
clearIdleTimeout();
|
|
3367
3570
|
}
|
|
3368
|
-
return
|
|
3571
|
+
return failure;
|
|
3369
3572
|
}
|
|
3370
3573
|
async function* createPassiveReadSource(control, dataConnection, command, path2, range, request) {
|
|
3371
3574
|
let bytesEmitted = 0;
|
|
@@ -3614,6 +3817,13 @@ function createTlsPinnedFingerprints(profile) {
|
|
|
3614
3817
|
if (pinnedFingerprint256 === void 0) {
|
|
3615
3818
|
return void 0;
|
|
3616
3819
|
}
|
|
3820
|
+
if (profile.tls?.rejectUnauthorized === false) {
|
|
3821
|
+
throw new ConfigurationError({
|
|
3822
|
+
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.",
|
|
3823
|
+
protocol: FTPS_PROVIDER_ID,
|
|
3824
|
+
retryable: false
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3617
3827
|
const fingerprints = Array.isArray(pinnedFingerprint256) ? pinnedFingerprint256 : [pinnedFingerprint256];
|
|
3618
3828
|
if (fingerprints.length === 0) {
|
|
3619
3829
|
throw new ConfigurationError({
|
|
@@ -5585,6 +5795,7 @@ var import_node_buffer12 = require("buffer");
|
|
|
5585
5795
|
var import_node_crypto6 = require("crypto");
|
|
5586
5796
|
var MIN_PADDING_LENGTH = 4;
|
|
5587
5797
|
var MIN_PACKET_LENGTH = 1 + MIN_PADDING_LENGTH;
|
|
5798
|
+
var MAX_SSH_PACKET_LENGTH = 256 * 1024;
|
|
5588
5799
|
function encodeSshTransportPacket(payload, options = {}) {
|
|
5589
5800
|
const body = import_node_buffer12.Buffer.from(payload);
|
|
5590
5801
|
const blockSize = normalizeBlockSize(options.blockSize ?? 8);
|
|
@@ -5662,6 +5873,14 @@ var SshTransportPacketFramer = class {
|
|
|
5662
5873
|
const packets = [];
|
|
5663
5874
|
while (this.pending.length >= 4) {
|
|
5664
5875
|
const packetLength = this.pending.readUInt32BE(0);
|
|
5876
|
+
if (packetLength > MAX_SSH_PACKET_LENGTH) {
|
|
5877
|
+
throw new ParseError({
|
|
5878
|
+
details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
|
|
5879
|
+
message: "SSH transport packet length exceeds the maximum accepted size",
|
|
5880
|
+
protocol: "sftp",
|
|
5881
|
+
retryable: false
|
|
5882
|
+
});
|
|
5883
|
+
}
|
|
5665
5884
|
const frameLength = 4 + packetLength;
|
|
5666
5885
|
if (this.pending.length < frameLength) {
|
|
5667
5886
|
break;
|
|
@@ -6139,14 +6358,35 @@ var SshTransportHandshake = class {
|
|
|
6139
6358
|
return { outbound };
|
|
6140
6359
|
}
|
|
6141
6360
|
};
|
|
6361
|
+
var MAX_IDENTIFICATION_LINE_BYTES = 8192;
|
|
6362
|
+
var MAX_PRE_IDENTIFICATION_LINES = 1024;
|
|
6142
6363
|
var SshIdentificationAccumulator = class {
|
|
6143
6364
|
pending = import_node_buffer14.Buffer.alloc(0);
|
|
6365
|
+
bannerLineCount = 0;
|
|
6144
6366
|
push(chunk) {
|
|
6145
6367
|
this.pending = import_node_buffer14.Buffer.concat([this.pending, import_node_buffer14.Buffer.from(chunk)]);
|
|
6146
6368
|
const bannerLines = [];
|
|
6147
6369
|
while (true) {
|
|
6148
6370
|
const lfIndex = this.pending.indexOf(10);
|
|
6149
|
-
if (lfIndex < 0)
|
|
6371
|
+
if (lfIndex < 0) {
|
|
6372
|
+
if (this.pending.length > MAX_IDENTIFICATION_LINE_BYTES) {
|
|
6373
|
+
throw new ProtocolError({
|
|
6374
|
+
details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
|
|
6375
|
+
message: "SSH identification line exceeds the maximum accepted length",
|
|
6376
|
+
protocol: "sftp",
|
|
6377
|
+
retryable: false
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
break;
|
|
6381
|
+
}
|
|
6382
|
+
if (lfIndex > MAX_IDENTIFICATION_LINE_BYTES) {
|
|
6383
|
+
throw new ProtocolError({
|
|
6384
|
+
details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
|
|
6385
|
+
message: "SSH identification line exceeds the maximum accepted length",
|
|
6386
|
+
protocol: "sftp",
|
|
6387
|
+
retryable: false
|
|
6388
|
+
});
|
|
6389
|
+
}
|
|
6150
6390
|
const lineText = trimLineEndings(this.pending.subarray(0, lfIndex + 1).toString("ascii"));
|
|
6151
6391
|
const remainder = import_node_buffer14.Buffer.from(this.pending.subarray(lfIndex + 1));
|
|
6152
6392
|
this.pending = remainder;
|
|
@@ -6154,6 +6394,15 @@ var SshIdentificationAccumulator = class {
|
|
|
6154
6394
|
this.pending = import_node_buffer14.Buffer.alloc(0);
|
|
6155
6395
|
return { bannerLines, identLine: lineText, remainder };
|
|
6156
6396
|
}
|
|
6397
|
+
this.bannerLineCount += 1;
|
|
6398
|
+
if (this.bannerLineCount > MAX_PRE_IDENTIFICATION_LINES) {
|
|
6399
|
+
throw new ProtocolError({
|
|
6400
|
+
details: { limitLines: MAX_PRE_IDENTIFICATION_LINES },
|
|
6401
|
+
message: "SSH server sent too many pre-identification banner lines",
|
|
6402
|
+
protocol: "sftp",
|
|
6403
|
+
retryable: false
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6157
6406
|
bannerLines.push(lineText);
|
|
6158
6407
|
}
|
|
6159
6408
|
return { bannerLines, remainder: import_node_buffer14.Buffer.alloc(0) };
|
|
@@ -6286,6 +6535,14 @@ var SshTransportPacketUnprotector = class {
|
|
|
6286
6535
|
this.framePendingRaw = import_node_buffer15.Buffer.from(this.framePendingRaw.subarray(this.blockLength));
|
|
6287
6536
|
this.framePartialDecrypted = this.decipher ? import_node_buffer15.Buffer.from(this.decipher.update(firstBlock)) : import_node_buffer15.Buffer.from(firstBlock);
|
|
6288
6537
|
const packetLength = this.framePartialDecrypted.readUInt32BE(0);
|
|
6538
|
+
if (packetLength > MAX_SSH_PACKET_LENGTH) {
|
|
6539
|
+
throw new ProtocolError({
|
|
6540
|
+
details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
|
|
6541
|
+
message: "SSH encrypted packet length exceeds the maximum accepted size",
|
|
6542
|
+
protocol: "sftp",
|
|
6543
|
+
retryable: false
|
|
6544
|
+
});
|
|
6545
|
+
}
|
|
6289
6546
|
const remaining = 4 + packetLength - this.blockLength + this.macLength;
|
|
6290
6547
|
if (remaining < 0) {
|
|
6291
6548
|
throw new ProtocolError({
|
|
@@ -6909,6 +7166,7 @@ function decodeSftpAttributesFromReader(reader) {
|
|
|
6909
7166
|
|
|
6910
7167
|
// src/protocols/sftp/v3/SftpPacket.ts
|
|
6911
7168
|
var import_node_buffer17 = require("buffer");
|
|
7169
|
+
var MAX_SFTP_PACKET_LENGTH = 256 * 1024;
|
|
6912
7170
|
var SFTP_PACKET_TYPE = {
|
|
6913
7171
|
ATTRS: 105,
|
|
6914
7172
|
CLOSE: 4,
|
|
@@ -6968,6 +7226,13 @@ var SftpPacketFramer = class {
|
|
|
6968
7226
|
const packets = [];
|
|
6969
7227
|
while (this.pending.length >= 4) {
|
|
6970
7228
|
const bodyLength = this.pending.readUInt32BE(0);
|
|
7229
|
+
if (bodyLength > MAX_SFTP_PACKET_LENGTH) {
|
|
7230
|
+
throw new ParseError({
|
|
7231
|
+
details: { bodyLength, maxPacketLength: MAX_SFTP_PACKET_LENGTH },
|
|
7232
|
+
message: "SFTP packet length exceeds the maximum accepted size",
|
|
7233
|
+
retryable: false
|
|
7234
|
+
});
|
|
7235
|
+
}
|
|
6971
7236
|
const frameLength = 4 + bodyLength;
|
|
6972
7237
|
if (this.pending.length < frameLength) {
|
|
6973
7238
|
break;
|
|
@@ -8228,6 +8493,26 @@ function validateNativeSftpOptions(options) {
|
|
|
8228
8493
|
|
|
8229
8494
|
// src/providers/web/httpInternals.ts
|
|
8230
8495
|
var import_node_buffer20 = require("buffer");
|
|
8496
|
+
function assertHttpsEnforced(input) {
|
|
8497
|
+
if (input.enforceHttps && !input.secure) {
|
|
8498
|
+
throw new ConfigurationError({
|
|
8499
|
+
details: { provider: input.providerId },
|
|
8500
|
+
message: `Provider "${input.providerId}" is configured with enforceHttps but its transport is cleartext http; set secure: true (or drop enforceHttps to explicitly accept cleartext)`,
|
|
8501
|
+
retryable: false
|
|
8502
|
+
});
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8505
|
+
var cleartextWarnedKeys = /* @__PURE__ */ new Set();
|
|
8506
|
+
function warnCleartextCredentials(input) {
|
|
8507
|
+
if (!input.hasCredentials) return;
|
|
8508
|
+
const key = `${input.providerId}:${input.host}`;
|
|
8509
|
+
if (cleartextWarnedKeys.has(key)) return;
|
|
8510
|
+
cleartextWarnedKeys.add(key);
|
|
8511
|
+
process.emitWarning(
|
|
8512
|
+
`Provider "${input.providerId}" is sending credentials to ${input.host} over cleartext http; use https or set enforceHttps to block this`,
|
|
8513
|
+
{ code: "ZERO_TRANSFER_CLEARTEXT_CREDENTIALS", type: "SecurityWarning" }
|
|
8514
|
+
);
|
|
8515
|
+
}
|
|
8231
8516
|
function buildBaseUrl(profile, options) {
|
|
8232
8517
|
const protocol = options.secure ? "https:" : "http:";
|
|
8233
8518
|
const portSegment = profile.port !== void 0 ? `:${profile.port}` : "";
|
|
@@ -8275,18 +8560,19 @@ async function dispatchRequest(options, url, init) {
|
|
|
8275
8560
|
signal: controller.signal
|
|
8276
8561
|
});
|
|
8277
8562
|
} catch (error) {
|
|
8563
|
+
const safeUrl = redactUrlForLogging(url);
|
|
8278
8564
|
if (controller.signal.aborted && upstreamSignal?.aborted !== true) {
|
|
8279
8565
|
throw new TimeoutError({
|
|
8280
8566
|
cause: error,
|
|
8281
|
-
details: { timeoutMs: options.timeoutMs, url:
|
|
8282
|
-
message: `HTTP request to ${
|
|
8567
|
+
details: { timeoutMs: options.timeoutMs, url: safeUrl },
|
|
8568
|
+
message: `HTTP request to ${safeUrl} timed out after ${String(options.timeoutMs)}ms`,
|
|
8283
8569
|
retryable: true
|
|
8284
8570
|
});
|
|
8285
8571
|
}
|
|
8286
8572
|
throw new ConnectionError({
|
|
8287
8573
|
cause: error,
|
|
8288
|
-
details: { url:
|
|
8289
|
-
message: `HTTP request to ${
|
|
8574
|
+
details: { url: safeUrl },
|
|
8575
|
+
message: `HTTP request to ${safeUrl} failed`,
|
|
8290
8576
|
retryable: true
|
|
8291
8577
|
});
|
|
8292
8578
|
} finally {
|
|
@@ -8318,8 +8604,48 @@ function formatRangeHeader(offset, length) {
|
|
|
8318
8604
|
const end = offset + length - 1;
|
|
8319
8605
|
return `bytes=${String(offset)}-${String(end)}`;
|
|
8320
8606
|
}
|
|
8321
|
-
|
|
8322
|
-
|
|
8607
|
+
var ERROR_BODY_EXCERPT_LIMIT = 2048;
|
|
8608
|
+
async function readErrorBodyExcerpt(response) {
|
|
8609
|
+
try {
|
|
8610
|
+
const text = await response.text();
|
|
8611
|
+
if (text.length === 0) return void 0;
|
|
8612
|
+
return text.length > ERROR_BODY_EXCERPT_LIMIT ? `${text.slice(0, ERROR_BODY_EXCERPT_LIMIT)}... [truncated]` : text;
|
|
8613
|
+
} catch {
|
|
8614
|
+
return void 0;
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
function parseRetryAfterMs(value, now = Date.now) {
|
|
8618
|
+
if (value === null) return void 0;
|
|
8619
|
+
const trimmed = value.trim();
|
|
8620
|
+
if (trimmed.length === 0) return void 0;
|
|
8621
|
+
if (/^\d+$/.test(trimmed)) {
|
|
8622
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
8623
|
+
return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
|
|
8624
|
+
}
|
|
8625
|
+
if (!/[A-Za-z]/.test(trimmed)) return void 0;
|
|
8626
|
+
const retryAt = Date.parse(trimmed);
|
|
8627
|
+
if (Number.isNaN(retryAt)) return void 0;
|
|
8628
|
+
return Math.max(0, retryAt - now());
|
|
8629
|
+
}
|
|
8630
|
+
async function mapResponseErrorWithBody(response, path2) {
|
|
8631
|
+
return mapResponseError(response, path2, await readErrorBodyExcerpt(response));
|
|
8632
|
+
}
|
|
8633
|
+
function mapResponseError(response, path2, bodyExcerpt) {
|
|
8634
|
+
const details = {
|
|
8635
|
+
path: path2,
|
|
8636
|
+
status: response.status,
|
|
8637
|
+
statusText: response.statusText
|
|
8638
|
+
};
|
|
8639
|
+
if (bodyExcerpt !== void 0) details["body"] = bodyExcerpt;
|
|
8640
|
+
if (response.status === 429 || response.status === 503) {
|
|
8641
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
8642
|
+
if (retryAfterMs !== void 0) details["retryAfterMs"] = retryAfterMs;
|
|
8643
|
+
return new ConnectionError({
|
|
8644
|
+
details,
|
|
8645
|
+
message: response.status === 429 ? `HTTP request for ${path2} was rate limited (429)` : `HTTP service unavailable for ${path2} (503)`,
|
|
8646
|
+
retryable: true
|
|
8647
|
+
});
|
|
8648
|
+
}
|
|
8323
8649
|
if (response.status === 401) {
|
|
8324
8650
|
return new AuthenticationError({
|
|
8325
8651
|
details,
|
|
@@ -8351,14 +8677,54 @@ async function* webStreamToAsyncIterable(body) {
|
|
|
8351
8677
|
const reader = body.getReader();
|
|
8352
8678
|
try {
|
|
8353
8679
|
while (true) {
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8680
|
+
let result;
|
|
8681
|
+
try {
|
|
8682
|
+
result = await reader.read();
|
|
8683
|
+
} catch (error) {
|
|
8684
|
+
if (error instanceof ZeroTransferError) throw error;
|
|
8685
|
+
throw new ConnectionError({
|
|
8686
|
+
cause: error,
|
|
8687
|
+
message: "HTTP response stream was interrupted before completion",
|
|
8688
|
+
retryable: true
|
|
8689
|
+
});
|
|
8690
|
+
}
|
|
8691
|
+
if (result.done) break;
|
|
8692
|
+
if (result.value !== void 0) yield result.value;
|
|
8357
8693
|
}
|
|
8358
8694
|
} finally {
|
|
8359
8695
|
reader.releaseLock();
|
|
8360
8696
|
}
|
|
8361
8697
|
}
|
|
8698
|
+
function asyncIterableToReadableStream(source, onChunk) {
|
|
8699
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
8700
|
+
return new ReadableStream({
|
|
8701
|
+
async pull(controller) {
|
|
8702
|
+
try {
|
|
8703
|
+
const next = await iterator.next();
|
|
8704
|
+
if (next.done === true) {
|
|
8705
|
+
controller.close();
|
|
8706
|
+
return;
|
|
8707
|
+
}
|
|
8708
|
+
const chunk = next.value;
|
|
8709
|
+
if (chunk.byteLength === 0) {
|
|
8710
|
+
return;
|
|
8711
|
+
}
|
|
8712
|
+
controller.enqueue(chunk);
|
|
8713
|
+
onChunk(chunk);
|
|
8714
|
+
} catch (error) {
|
|
8715
|
+
controller.error(error);
|
|
8716
|
+
}
|
|
8717
|
+
},
|
|
8718
|
+
async cancel(reason) {
|
|
8719
|
+
if (typeof iterator.return === "function") {
|
|
8720
|
+
try {
|
|
8721
|
+
await iterator.return(reason);
|
|
8722
|
+
} catch {
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
}
|
|
8726
|
+
});
|
|
8727
|
+
}
|
|
8362
8728
|
function secretToString2(value) {
|
|
8363
8729
|
if (typeof value === "string") return value;
|
|
8364
8730
|
if (value instanceof Uint8Array || import_node_buffer20.Buffer.isBuffer(value)) {
|
|
@@ -11957,6 +12323,7 @@ function createHttpProviderFactory(options = {}) {
|
|
|
11957
12323
|
const secure = options.secure ?? id === "https";
|
|
11958
12324
|
const basePath = options.basePath ?? "";
|
|
11959
12325
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
12326
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
11960
12327
|
if (typeof fetchImpl !== "function") {
|
|
11961
12328
|
throw new ConfigurationError({
|
|
11962
12329
|
message: "Global fetch is unavailable; supply HttpProviderOptions.fetch explicitly",
|
|
@@ -12006,6 +12373,13 @@ var HttpProvider = class {
|
|
|
12006
12373
|
id;
|
|
12007
12374
|
capabilities;
|
|
12008
12375
|
async connect(profile) {
|
|
12376
|
+
if (!this.internals.secure) {
|
|
12377
|
+
warnCleartextCredentials({
|
|
12378
|
+
hasCredentials: profile.username !== void 0 || profile.password !== void 0,
|
|
12379
|
+
host: profile.host,
|
|
12380
|
+
providerId: this.internals.id
|
|
12381
|
+
});
|
|
12382
|
+
}
|
|
12009
12383
|
const headers = { ...this.internals.defaultHeaders };
|
|
12010
12384
|
if (profile.username !== void 0) {
|
|
12011
12385
|
const username = await resolveSecret(profile.username);
|
|
@@ -12064,7 +12438,7 @@ var HttpFileSystem = class {
|
|
|
12064
12438
|
method: "HEAD"
|
|
12065
12439
|
});
|
|
12066
12440
|
if (!response.ok) {
|
|
12067
|
-
throw
|
|
12441
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12068
12442
|
}
|
|
12069
12443
|
return responseToStat(response, normalized);
|
|
12070
12444
|
}
|
|
@@ -12089,12 +12463,12 @@ var HttpTransferOperations = class {
|
|
|
12089
12463
|
if (request.signal !== void 0) requestInit.signal = request.signal;
|
|
12090
12464
|
const response = await dispatchRequest(this.options, url, requestInit);
|
|
12091
12465
|
if (!response.ok && response.status !== 206) {
|
|
12092
|
-
throw
|
|
12466
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12093
12467
|
}
|
|
12094
12468
|
const body = response.body;
|
|
12095
12469
|
if (body === null) {
|
|
12096
12470
|
throw new ConnectionError({
|
|
12097
|
-
message: `HTTP response had no body for ${url
|
|
12471
|
+
message: `HTTP response had no body for ${redactUrlForLogging(url)}`,
|
|
12098
12472
|
retryable: true
|
|
12099
12473
|
});
|
|
12100
12474
|
}
|
|
@@ -12152,7 +12526,8 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
12152
12526
|
const secure = options.secure ?? false;
|
|
12153
12527
|
const basePath = options.basePath ?? "";
|
|
12154
12528
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
12155
|
-
const uploadStreaming = options.uploadStreaming ?? "
|
|
12529
|
+
const uploadStreaming = options.uploadStreaming ?? "always";
|
|
12530
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
12156
12531
|
if (typeof fetchImpl !== "function") {
|
|
12157
12532
|
throw new ConfigurationError({
|
|
12158
12533
|
message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
|
|
@@ -12204,6 +12579,13 @@ var WebDavProvider = class {
|
|
|
12204
12579
|
id;
|
|
12205
12580
|
capabilities;
|
|
12206
12581
|
async connect(profile) {
|
|
12582
|
+
if (!this.internals.secure) {
|
|
12583
|
+
warnCleartextCredentials({
|
|
12584
|
+
hasCredentials: profile.username !== void 0 || profile.password !== void 0,
|
|
12585
|
+
host: profile.host,
|
|
12586
|
+
providerId: this.internals.id
|
|
12587
|
+
});
|
|
12588
|
+
}
|
|
12207
12589
|
const headers = { ...this.internals.defaultHeaders };
|
|
12208
12590
|
if (profile.username !== void 0) {
|
|
12209
12591
|
const username = await resolveSecret(profile.username);
|
|
@@ -12258,7 +12640,7 @@ var WebDavFileSystem = class {
|
|
|
12258
12640
|
method: "PROPFIND"
|
|
12259
12641
|
});
|
|
12260
12642
|
if (!response.ok && response.status !== 207) {
|
|
12261
|
-
throw
|
|
12643
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12262
12644
|
}
|
|
12263
12645
|
const body = await response.text();
|
|
12264
12646
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12272,7 +12654,7 @@ var WebDavFileSystem = class {
|
|
|
12272
12654
|
method: "PROPFIND"
|
|
12273
12655
|
});
|
|
12274
12656
|
if (!response.ok && response.status !== 207) {
|
|
12275
|
-
throw
|
|
12657
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12276
12658
|
}
|
|
12277
12659
|
const body = await response.text();
|
|
12278
12660
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12317,12 +12699,12 @@ var WebDavTransferOperations = class {
|
|
|
12317
12699
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12318
12700
|
const response = await dispatchRequest(this.options, url, init);
|
|
12319
12701
|
if (!response.ok && response.status !== 206) {
|
|
12320
|
-
throw
|
|
12702
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12321
12703
|
}
|
|
12322
12704
|
const body = response.body;
|
|
12323
12705
|
if (body === null) {
|
|
12324
12706
|
throw new ConnectionError({
|
|
12325
|
-
message: `WebDAV response had no body for ${url
|
|
12707
|
+
message: `WebDAV response had no body for ${redactUrlForLogging(url)}`,
|
|
12326
12708
|
retryable: true
|
|
12327
12709
|
});
|
|
12328
12710
|
}
|
|
@@ -12366,7 +12748,7 @@ var WebDavTransferOperations = class {
|
|
|
12366
12748
|
if (request.signal !== void 0) init2.signal = request.signal;
|
|
12367
12749
|
const response2 = await dispatchRequest(this.options, url, init2);
|
|
12368
12750
|
if (!response2.ok) {
|
|
12369
|
-
throw
|
|
12751
|
+
throw await mapResponseErrorWithBody(response2, normalized);
|
|
12370
12752
|
}
|
|
12371
12753
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
12372
12754
|
const result2 = {
|
|
@@ -12402,7 +12784,7 @@ var WebDavTransferOperations = class {
|
|
|
12402
12784
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12403
12785
|
const response = await dispatchRequest(this.options, url, init);
|
|
12404
12786
|
if (!response.ok) {
|
|
12405
|
-
throw
|
|
12787
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12406
12788
|
}
|
|
12407
12789
|
const result = {
|
|
12408
12790
|
bytesTransferred,
|
|
@@ -12428,36 +12810,6 @@ async function collectChunks6(source) {
|
|
|
12428
12810
|
}
|
|
12429
12811
|
return out;
|
|
12430
12812
|
}
|
|
12431
|
-
function asyncIterableToReadableStream(source, onChunk) {
|
|
12432
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
12433
|
-
return new ReadableStream({
|
|
12434
|
-
async pull(controller) {
|
|
12435
|
-
try {
|
|
12436
|
-
const next = await iterator.next();
|
|
12437
|
-
if (next.done === true) {
|
|
12438
|
-
controller.close();
|
|
12439
|
-
return;
|
|
12440
|
-
}
|
|
12441
|
-
const chunk = next.value;
|
|
12442
|
-
if (chunk.byteLength === 0) {
|
|
12443
|
-
return;
|
|
12444
|
-
}
|
|
12445
|
-
controller.enqueue(chunk);
|
|
12446
|
-
onChunk(chunk);
|
|
12447
|
-
} catch (error) {
|
|
12448
|
-
controller.error(error);
|
|
12449
|
-
}
|
|
12450
|
-
},
|
|
12451
|
-
async cancel(reason) {
|
|
12452
|
-
if (typeof iterator.return === "function") {
|
|
12453
|
-
try {
|
|
12454
|
-
await iterator.return(reason);
|
|
12455
|
-
} catch {
|
|
12456
|
-
}
|
|
12457
|
-
}
|
|
12458
|
-
}
|
|
12459
|
-
});
|
|
12460
|
-
}
|
|
12461
12813
|
function parsePropfindResponses(xml, baseUrl) {
|
|
12462
12814
|
const entries = [];
|
|
12463
12815
|
const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
|
|
@@ -12544,11 +12896,12 @@ var import_node_path3 = require("path");
|
|
|
12544
12896
|
|
|
12545
12897
|
// src/providers/web/awsSigv4.ts
|
|
12546
12898
|
var import_node_crypto12 = require("crypto");
|
|
12899
|
+
var UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
|
12547
12900
|
function signSigV4(input) {
|
|
12548
12901
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
12549
12902
|
const amzDate = formatAmzDate(now);
|
|
12550
12903
|
const dateStamp = amzDate.slice(0, 8);
|
|
12551
|
-
const payloadHash = input.body !== void 0 ? sha256Hex(input.body) : sha256Hex(new Uint8Array());
|
|
12904
|
+
const payloadHash = input.unsignedPayload === true ? UNSIGNED_PAYLOAD : input.body !== void 0 ? sha256Hex(input.body) : sha256Hex(new Uint8Array());
|
|
12552
12905
|
input.headers["host"] = input.url.host;
|
|
12553
12906
|
input.headers["x-amz-date"] = amzDate;
|
|
12554
12907
|
input.headers["x-amz-content-sha256"] = payloadHash;
|
|
@@ -12829,7 +13182,7 @@ var S3FileSystem = class {
|
|
|
12829
13182
|
url.searchParams.set("delimiter", "/");
|
|
12830
13183
|
if (prefix.length > 0) url.searchParams.set("prefix", prefix);
|
|
12831
13184
|
const response = await s3Fetch(this.options, "GET", url);
|
|
12832
|
-
if (!response.ok) throw
|
|
13185
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12833
13186
|
const body = await response.text();
|
|
12834
13187
|
return parseListObjectsV2(body, prefix);
|
|
12835
13188
|
}
|
|
@@ -12837,7 +13190,7 @@ var S3FileSystem = class {
|
|
|
12837
13190
|
const normalized = normalizeRemotePath(path2);
|
|
12838
13191
|
const url = buildObjectUrl(this.options, normalized);
|
|
12839
13192
|
const response = await s3Fetch(this.options, "HEAD", url);
|
|
12840
|
-
if (!response.ok) throw
|
|
13193
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12841
13194
|
const stat = {
|
|
12842
13195
|
exists: true,
|
|
12843
13196
|
name: basenameRemotePath(normalized),
|
|
@@ -12877,12 +13230,12 @@ var S3TransferOperations = class {
|
|
|
12877
13230
|
extraHeaders: headers
|
|
12878
13231
|
});
|
|
12879
13232
|
if (!response.ok && response.status !== 206) {
|
|
12880
|
-
throw
|
|
13233
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12881
13234
|
}
|
|
12882
13235
|
const body = response.body;
|
|
12883
13236
|
if (body === null) {
|
|
12884
13237
|
throw new ConnectionError({
|
|
12885
|
-
message: `S3 response had no body for ${url
|
|
13238
|
+
message: `S3 response had no body for ${redactUrlForLogging(url)}`,
|
|
12886
13239
|
retryable: true
|
|
12887
13240
|
});
|
|
12888
13241
|
}
|
|
@@ -12918,19 +13271,33 @@ var S3TransferOperations = class {
|
|
|
12918
13271
|
}
|
|
12919
13272
|
return this.writeSingleShot(request, normalized);
|
|
12920
13273
|
}
|
|
13274
|
+
/**
|
|
13275
|
+
* Single PUT upload used when multipart is disabled. Streams the body with
|
|
13276
|
+
* a declared `Content-Length` (signed as `UNSIGNED-PAYLOAD`) when the
|
|
13277
|
+
* caller knows the total size; S3 requires a length up front, so only
|
|
13278
|
+
* unknown-size payloads fall back to buffering the content in memory.
|
|
13279
|
+
*/
|
|
12921
13280
|
async writeSingleShot(request, normalized) {
|
|
12922
13281
|
const url = buildObjectUrl(this.options, normalized);
|
|
12923
|
-
const
|
|
13282
|
+
const totalBytes = request.totalBytes;
|
|
13283
|
+
if (typeof totalBytes !== "number" || totalBytes < 0) {
|
|
13284
|
+
const buffered = await collectChunks7(request.content);
|
|
13285
|
+
return this.singleShotFromBuffer(request, normalized, buffered);
|
|
13286
|
+
}
|
|
13287
|
+
let bytesTransferred = 0;
|
|
13288
|
+
const stream = asyncIterableToReadableStream(request.content, (chunk) => {
|
|
13289
|
+
bytesTransferred += chunk.byteLength;
|
|
13290
|
+
request.reportProgress(bytesTransferred, totalBytes);
|
|
13291
|
+
});
|
|
12924
13292
|
const response = await s3Fetch(this.options, "PUT", url, {
|
|
12925
13293
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12926
|
-
|
|
12927
|
-
|
|
13294
|
+
extraHeaders: { "content-type": "application/octet-stream" },
|
|
13295
|
+
streamBody: { content: stream, contentLength: totalBytes }
|
|
12928
13296
|
});
|
|
12929
|
-
if (!response.ok) throw
|
|
12930
|
-
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
13297
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12931
13298
|
const result = {
|
|
12932
|
-
bytesTransferred
|
|
12933
|
-
totalBytes
|
|
13299
|
+
bytesTransferred,
|
|
13300
|
+
totalBytes
|
|
12934
13301
|
};
|
|
12935
13302
|
const etag = response.headers.get("etag");
|
|
12936
13303
|
if (etag !== null) result.checksum = etag;
|
|
@@ -12994,7 +13361,7 @@ var S3TransferOperations = class {
|
|
|
12994
13361
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12995
13362
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
12996
13363
|
});
|
|
12997
|
-
if (!initiateResponse.ok) throw
|
|
13364
|
+
if (!initiateResponse.ok) throw await mapResponseErrorWithBody(initiateResponse, normalized);
|
|
12998
13365
|
const initiateBody = await initiateResponse.text();
|
|
12999
13366
|
const initiated = innerText(initiateBody, "UploadId");
|
|
13000
13367
|
if (initiated === void 0 || initiated === "") {
|
|
@@ -13033,7 +13400,7 @@ var S3TransferOperations = class {
|
|
|
13033
13400
|
body: partBytes.bytes
|
|
13034
13401
|
});
|
|
13035
13402
|
if (!partResponse.ok) {
|
|
13036
|
-
throw
|
|
13403
|
+
throw await mapResponseErrorWithBody(partResponse, normalized);
|
|
13037
13404
|
}
|
|
13038
13405
|
const partEtag = partResponse.headers.get("etag");
|
|
13039
13406
|
if (partEtag === null) {
|
|
@@ -13089,7 +13456,7 @@ var S3TransferOperations = class {
|
|
|
13089
13456
|
if (resumeStore === void 0) {
|
|
13090
13457
|
await abortMultipart(this.options, objectUrl, uploadId).catch(() => void 0);
|
|
13091
13458
|
}
|
|
13092
|
-
throw
|
|
13459
|
+
throw await mapResponseErrorWithBody(completeResponse, normalized);
|
|
13093
13460
|
}
|
|
13094
13461
|
if (resumeStore !== void 0) await resumeStore.clear(resumeKey);
|
|
13095
13462
|
const completeBody = await completeResponse.text();
|
|
@@ -13108,7 +13475,7 @@ var S3TransferOperations = class {
|
|
|
13108
13475
|
body: buffered,
|
|
13109
13476
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
13110
13477
|
});
|
|
13111
|
-
if (!response.ok) throw
|
|
13478
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
13112
13479
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
13113
13480
|
const result = {
|
|
13114
13481
|
bytesTransferred: buffered.byteLength,
|
|
@@ -13126,6 +13493,8 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13126
13493
|
};
|
|
13127
13494
|
if (fetchOptions.body !== void 0) {
|
|
13128
13495
|
headers["content-length"] = String(fetchOptions.body.byteLength);
|
|
13496
|
+
} else if (fetchOptions.streamBody !== void 0) {
|
|
13497
|
+
headers["content-length"] = String(fetchOptions.streamBody.contentLength);
|
|
13129
13498
|
}
|
|
13130
13499
|
signSigV4({
|
|
13131
13500
|
accessKeyId: options.accessKeyId,
|
|
@@ -13136,10 +13505,16 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13136
13505
|
service: options.service,
|
|
13137
13506
|
url,
|
|
13138
13507
|
...fetchOptions.body !== void 0 ? { body: fetchOptions.body } : {},
|
|
13508
|
+
...fetchOptions.streamBody !== void 0 ? { unsignedPayload: true } : {},
|
|
13139
13509
|
...options.sessionToken !== void 0 ? { sessionToken: options.sessionToken } : {}
|
|
13140
13510
|
});
|
|
13141
13511
|
const init = { headers, method };
|
|
13142
|
-
if (fetchOptions.body !== void 0)
|
|
13512
|
+
if (fetchOptions.body !== void 0) {
|
|
13513
|
+
init.body = fetchOptions.body;
|
|
13514
|
+
} else if (fetchOptions.streamBody !== void 0) {
|
|
13515
|
+
init.body = fetchOptions.streamBody.content;
|
|
13516
|
+
init.duplex = "half";
|
|
13517
|
+
}
|
|
13143
13518
|
if (fetchOptions.signal !== void 0) init.signal = fetchOptions.signal;
|
|
13144
13519
|
const controller = new AbortController();
|
|
13145
13520
|
const upstreamSignal = init.signal ?? null;
|
|
@@ -13157,10 +13532,11 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13157
13532
|
try {
|
|
13158
13533
|
return await options.fetch(url.toString(), { ...init, signal: controller.signal });
|
|
13159
13534
|
} catch (error) {
|
|
13535
|
+
const safeUrl = redactUrlForLogging(url);
|
|
13160
13536
|
throw new ConnectionError({
|
|
13161
13537
|
cause: error,
|
|
13162
|
-
details: { url:
|
|
13163
|
-
message: `S3 request to ${
|
|
13538
|
+
details: { url: safeUrl },
|
|
13539
|
+
message: `S3 request to ${safeUrl} failed`,
|
|
13164
13540
|
retryable: true
|
|
13165
13541
|
});
|
|
13166
13542
|
} finally {
|
|
@@ -13628,7 +14004,6 @@ function expandAlgorithms(values) {
|
|
|
13628
14004
|
}
|
|
13629
14005
|
|
|
13630
14006
|
// src/profiles/importers/FileZillaImporter.ts
|
|
13631
|
-
var import_node_buffer25 = require("buffer");
|
|
13632
14007
|
function importFileZillaSites(xml) {
|
|
13633
14008
|
const events = tokenizeXml(xml);
|
|
13634
14009
|
if (events.length === 0) {
|
|
@@ -13644,7 +14019,6 @@ function importFileZillaSites(xml) {
|
|
|
13644
14019
|
const folderNamePending = [];
|
|
13645
14020
|
let inServer = false;
|
|
13646
14021
|
let serverFields = {};
|
|
13647
|
-
let serverPasswordEncoding;
|
|
13648
14022
|
let activeTag;
|
|
13649
14023
|
let captureFolderName = false;
|
|
13650
14024
|
for (const event of events) {
|
|
@@ -13657,13 +14031,9 @@ function importFileZillaSites(xml) {
|
|
|
13657
14031
|
if (event.name === "Server") {
|
|
13658
14032
|
inServer = true;
|
|
13659
14033
|
serverFields = {};
|
|
13660
|
-
serverPasswordEncoding = void 0;
|
|
13661
14034
|
continue;
|
|
13662
14035
|
}
|
|
13663
14036
|
activeTag = event.name;
|
|
13664
|
-
if (event.name === "Pass" && inServer) {
|
|
13665
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
13666
|
-
}
|
|
13667
14037
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
13668
14038
|
captureFolderName = true;
|
|
13669
14039
|
}
|
|
@@ -13689,7 +14059,7 @@ function importFileZillaSites(xml) {
|
|
|
13689
14059
|
}
|
|
13690
14060
|
if (event.name === "Server") {
|
|
13691
14061
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
13692
|
-
const result = buildSiteFromFields(serverFields
|
|
14062
|
+
const result = buildSiteFromFields(serverFields);
|
|
13693
14063
|
if (result.kind === "site") {
|
|
13694
14064
|
sites.push({ ...result.site, folder });
|
|
13695
14065
|
} else {
|
|
@@ -13701,7 +14071,6 @@ function importFileZillaSites(xml) {
|
|
|
13701
14071
|
}
|
|
13702
14072
|
inServer = false;
|
|
13703
14073
|
serverFields = {};
|
|
13704
|
-
serverPasswordEncoding = void 0;
|
|
13705
14074
|
activeTag = void 0;
|
|
13706
14075
|
continue;
|
|
13707
14076
|
}
|
|
@@ -13710,7 +14079,7 @@ function importFileZillaSites(xml) {
|
|
|
13710
14079
|
}
|
|
13711
14080
|
return { sites, skipped };
|
|
13712
14081
|
}
|
|
13713
|
-
function buildSiteFromFields(fields
|
|
14082
|
+
function buildSiteFromFields(fields) {
|
|
13714
14083
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
13715
14084
|
const host = (fields["Host"] ?? "").trim();
|
|
13716
14085
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -13729,18 +14098,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
13729
14098
|
}
|
|
13730
14099
|
const user = fields["User"]?.trim();
|
|
13731
14100
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
13732
|
-
let password;
|
|
13733
14101
|
const rawPass = fields["Pass"];
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
password = import_node_buffer25.Buffer.from(rawPass, "base64").toString("utf8");
|
|
13737
|
-
} else {
|
|
13738
|
-
password = rawPass;
|
|
13739
|
-
}
|
|
13740
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
13741
|
-
}
|
|
13742
|
-
const site = { name, profile };
|
|
13743
|
-
if (password !== void 0) site.password = password;
|
|
14102
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
14103
|
+
const site = { hasStoredPassword, name, profile };
|
|
13744
14104
|
const logonText = fields["Logontype"];
|
|
13745
14105
|
if (logonText !== void 0) {
|
|
13746
14106
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -14061,6 +14421,62 @@ function openTcpSocket(host, port, timeoutMs) {
|
|
|
14061
14421
|
});
|
|
14062
14422
|
}
|
|
14063
14423
|
|
|
14424
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
14425
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
14426
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
14427
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
14428
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
14429
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
14430
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
14431
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
14432
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
14433
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
14434
|
+
const random = options.random ?? Math.random;
|
|
14435
|
+
return {
|
|
14436
|
+
getDelayMs(input) {
|
|
14437
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
14438
|
+
if (retryAfterMs !== void 0) {
|
|
14439
|
+
return retryAfterMs;
|
|
14440
|
+
}
|
|
14441
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
14442
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
14443
|
+
return Math.floor(random() * cappedMs);
|
|
14444
|
+
},
|
|
14445
|
+
maxAttempts,
|
|
14446
|
+
shouldRetry(input) {
|
|
14447
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
14448
|
+
return false;
|
|
14449
|
+
}
|
|
14450
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
14451
|
+
return false;
|
|
14452
|
+
}
|
|
14453
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
14454
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
14455
|
+
return false;
|
|
14456
|
+
}
|
|
14457
|
+
return true;
|
|
14458
|
+
}
|
|
14459
|
+
};
|
|
14460
|
+
}
|
|
14461
|
+
function readRetryAfterMs(error) {
|
|
14462
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
14463
|
+
const value = error.details?.["retryAfterMs"];
|
|
14464
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
14465
|
+
return Math.floor(value);
|
|
14466
|
+
}
|
|
14467
|
+
function normalizePositiveInteger(value, fallback) {
|
|
14468
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
14469
|
+
return fallback;
|
|
14470
|
+
}
|
|
14471
|
+
return Math.floor(value);
|
|
14472
|
+
}
|
|
14473
|
+
function normalizeNonNegative(value, fallback) {
|
|
14474
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
14475
|
+
return fallback;
|
|
14476
|
+
}
|
|
14477
|
+
return Math.floor(value);
|
|
14478
|
+
}
|
|
14479
|
+
|
|
14064
14480
|
// src/transfers/TransferPlan.ts
|
|
14065
14481
|
function createTransferPlan(input) {
|
|
14066
14482
|
const plan = {
|
|
@@ -14158,8 +14574,8 @@ var TransferQueue = class {
|
|
|
14158
14574
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
14159
14575
|
this.defaultExecutor = options.executor;
|
|
14160
14576
|
this.resolveExecutor = options.resolveExecutor;
|
|
14161
|
-
this.retry = options.retry;
|
|
14162
|
-
this.timeout = options.timeout;
|
|
14577
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
14578
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
14163
14579
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
14164
14580
|
this.onProgress = options.onProgress;
|
|
14165
14581
|
this.onReceipt = options.onReceipt;
|
|
@@ -15559,6 +15975,7 @@ async function dispatchWebhook(options) {
|
|
|
15559
15975
|
return { attempts: attempt, delivered: false, status: lastStatus };
|
|
15560
15976
|
}
|
|
15561
15977
|
function createWebhookAuditLog(options) {
|
|
15978
|
+
validateTarget(options.target);
|
|
15562
15979
|
return {
|
|
15563
15980
|
list: () => Promise.resolve([]),
|
|
15564
15981
|
record: async (entry) => {
|
|
@@ -15581,6 +15998,24 @@ function validateTarget(target) {
|
|
|
15581
15998
|
retryable: false
|
|
15582
15999
|
});
|
|
15583
16000
|
}
|
|
16001
|
+
let parsed;
|
|
16002
|
+
try {
|
|
16003
|
+
parsed = new URL(target.url);
|
|
16004
|
+
} catch (error) {
|
|
16005
|
+
throw new ConfigurationError({
|
|
16006
|
+
cause: error,
|
|
16007
|
+
details: { url: target.url },
|
|
16008
|
+
message: "Webhook target url must be an absolute URL",
|
|
16009
|
+
retryable: false
|
|
16010
|
+
});
|
|
16011
|
+
}
|
|
16012
|
+
if (parsed.protocol === "https:") return;
|
|
16013
|
+
if (parsed.protocol === "http:" && target.allowInsecureUrl === true) return;
|
|
16014
|
+
throw new ConfigurationError({
|
|
16015
|
+
details: { protocol: parsed.protocol, url: target.url },
|
|
16016
|
+
message: parsed.protocol === "http:" ? "Webhook target url must use https; set allowInsecureUrl: true to permit cleartext http delivery" : `Webhook target url must use https, got "${parsed.protocol}"`,
|
|
16017
|
+
retryable: false
|
|
16018
|
+
});
|
|
15584
16019
|
}
|
|
15585
16020
|
function normalizeRetry(retry) {
|
|
15586
16021
|
return {
|
|
@@ -15617,6 +16052,26 @@ var ApprovalRejectedError = class _ApprovalRejectedError extends ZeroTransferErr
|
|
|
15617
16052
|
}
|
|
15618
16053
|
request;
|
|
15619
16054
|
};
|
|
16055
|
+
var ApprovalTimeoutError = class _ApprovalTimeoutError extends ZeroTransferError {
|
|
16056
|
+
/**
|
|
16057
|
+
* Creates an approval timeout error.
|
|
16058
|
+
*
|
|
16059
|
+
* @param request - The approval request that timed out while pending.
|
|
16060
|
+
* @param timeoutMs - Configured timeout window in milliseconds.
|
|
16061
|
+
*/
|
|
16062
|
+
constructor(request, timeoutMs) {
|
|
16063
|
+
super({
|
|
16064
|
+
code: "approval_timeout",
|
|
16065
|
+
details: { approvalId: request.id, routeId: request.routeId, timeoutMs },
|
|
16066
|
+
message: `Approval "${request.id}" for route "${request.routeId}" timed out after ${String(timeoutMs)}ms`,
|
|
16067
|
+
retryable: false
|
|
16068
|
+
});
|
|
16069
|
+
this.request = request;
|
|
16070
|
+
Object.setPrototypeOf(this, _ApprovalTimeoutError.prototype);
|
|
16071
|
+
this.name = "ApprovalTimeoutError";
|
|
16072
|
+
}
|
|
16073
|
+
request;
|
|
16074
|
+
};
|
|
15620
16075
|
var ApprovalRegistry = class {
|
|
15621
16076
|
requests = /* @__PURE__ */ new Map();
|
|
15622
16077
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -15742,9 +16197,27 @@ function createApprovalGate(options) {
|
|
|
15742
16197
|
};
|
|
15743
16198
|
if (input.signal.aborted) onAbort();
|
|
15744
16199
|
input.signal.addEventListener("abort", onAbort);
|
|
16200
|
+
let timeoutTimer;
|
|
16201
|
+
const timeoutMs = options.timeoutMs;
|
|
16202
|
+
const pendingPromises = [settled];
|
|
16203
|
+
if (timeoutMs !== void 0) {
|
|
16204
|
+
pendingPromises.push(
|
|
16205
|
+
new Promise((_resolve, reject) => {
|
|
16206
|
+
timeoutTimer = setTimeout(() => {
|
|
16207
|
+
const current = options.registry.get(approvalId) ?? request;
|
|
16208
|
+
reject(new ApprovalTimeoutError(current, timeoutMs));
|
|
16209
|
+
if (current.status === "pending") {
|
|
16210
|
+
settled.catch(() => void 0);
|
|
16211
|
+
options.registry.reject(approvalId, { reason: "timeout" }, now());
|
|
16212
|
+
}
|
|
16213
|
+
}, timeoutMs);
|
|
16214
|
+
})
|
|
16215
|
+
);
|
|
16216
|
+
}
|
|
15745
16217
|
try {
|
|
15746
|
-
await
|
|
16218
|
+
await Promise.race(pendingPromises);
|
|
15747
16219
|
} finally {
|
|
16220
|
+
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
15748
16221
|
input.signal.removeEventListener("abort", onAbort);
|
|
15749
16222
|
}
|
|
15750
16223
|
return options.runner(input);
|
|
@@ -16117,6 +16590,7 @@ function isMainModule(importMetaUrl) {
|
|
|
16117
16590
|
AbortError,
|
|
16118
16591
|
ApprovalRegistry,
|
|
16119
16592
|
ApprovalRejectedError,
|
|
16593
|
+
ApprovalTimeoutError,
|
|
16120
16594
|
AuthenticationError,
|
|
16121
16595
|
AuthorizationError,
|
|
16122
16596
|
CLASSIC_PROVIDER_IDS,
|
|
@@ -16166,6 +16640,7 @@ function isMainModule(importMetaUrl) {
|
|
|
16166
16640
|
createAtomicDeployPlan,
|
|
16167
16641
|
createAzureBlobProviderFactory,
|
|
16168
16642
|
createBandwidthThrottle,
|
|
16643
|
+
createDefaultRetryPolicy,
|
|
16169
16644
|
createDropboxProviderFactory,
|
|
16170
16645
|
createFileSystemS3MultipartResumeStore,
|
|
16171
16646
|
createFtpProviderFactory,
|
|
@@ -16235,8 +16710,10 @@ function isMainModule(importMetaUrl) {
|
|
|
16235
16710
|
parseUnixListLine,
|
|
16236
16711
|
redactCommand,
|
|
16237
16712
|
redactConnectionProfile,
|
|
16713
|
+
redactErrorForLogging,
|
|
16238
16714
|
redactObject,
|
|
16239
16715
|
redactSecretSource,
|
|
16716
|
+
redactUrlForLogging,
|
|
16240
16717
|
redactValue,
|
|
16241
16718
|
resolveConnectionProfileSecrets,
|
|
16242
16719
|
resolveOpenSshHost,
|