@zero-transfer/sdk 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 +25 -11
- package/assets/zero-transfer-icon.svg +117 -0
- package/dist/index.cjs +641 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +302 -49
- package/dist/index.d.ts +302 -49
- package/dist/index.mjs +637 -165
- 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,
|
|
@@ -94,7 +96,6 @@ __export(index_exports, {
|
|
|
94
96
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
95
97
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
96
98
|
createMemoryS3MultipartResumeStore: () => createMemoryS3MultipartResumeStore,
|
|
97
|
-
createNativeSftpProviderFactory: () => createNativeSftpProviderFactory,
|
|
98
99
|
createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
|
|
99
100
|
createOneDriveProviderFactory: () => createOneDriveProviderFactory,
|
|
100
101
|
createOutboxRoute: () => createOutboxRoute,
|
|
@@ -104,7 +105,7 @@ __export(index_exports, {
|
|
|
104
105
|
createRemoteBrowser: () => createRemoteBrowser,
|
|
105
106
|
createRemoteManifest: () => createRemoteManifest,
|
|
106
107
|
createS3ProviderFactory: () => createS3ProviderFactory,
|
|
107
|
-
createSftpProviderFactory: () =>
|
|
108
|
+
createSftpProviderFactory: () => createSftpProviderFactory,
|
|
108
109
|
createSyncPlan: () => createSyncPlan,
|
|
109
110
|
createTransferClient: () => createTransferClient,
|
|
110
111
|
createTransferJobsFromPlan: () => createTransferJobsFromPlan,
|
|
@@ -152,8 +153,10 @@ __export(index_exports, {
|
|
|
152
153
|
parseUnixListLine: () => parseUnixListLine,
|
|
153
154
|
redactCommand: () => redactCommand,
|
|
154
155
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
156
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
155
157
|
redactObject: () => redactObject,
|
|
156
158
|
redactSecretSource: () => redactSecretSource,
|
|
159
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
157
160
|
redactValue: () => redactValue,
|
|
158
161
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
159
162
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -179,6 +182,68 @@ module.exports = __toCommonJS(index_exports);
|
|
|
179
182
|
// src/client/ZeroTransfer.ts
|
|
180
183
|
var import_node_events = require("events");
|
|
181
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
|
+
|
|
182
247
|
// src/errors/ZeroTransferError.ts
|
|
183
248
|
var ZeroTransferError = class extends Error {
|
|
184
249
|
/** Stable machine-readable error code. */
|
|
@@ -220,6 +285,11 @@ var ZeroTransferError = class extends Error {
|
|
|
220
285
|
/**
|
|
221
286
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
222
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
|
+
*
|
|
223
293
|
* @returns A JSON-safe object containing public structured error fields.
|
|
224
294
|
*/
|
|
225
295
|
toJSON() {
|
|
@@ -229,12 +299,12 @@ var ZeroTransferError = class extends Error {
|
|
|
229
299
|
message: this.message,
|
|
230
300
|
protocol: this.protocol,
|
|
231
301
|
host: this.host,
|
|
232
|
-
command: this.command,
|
|
302
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
233
303
|
ftpCode: this.ftpCode,
|
|
234
304
|
sftpCode: this.sftpCode,
|
|
235
305
|
path: this.path,
|
|
236
306
|
retryable: this.retryable,
|
|
237
|
-
details: this.details
|
|
307
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
238
308
|
};
|
|
239
309
|
}
|
|
240
310
|
};
|
|
@@ -757,15 +827,20 @@ var ProviderRegistry = class {
|
|
|
757
827
|
var TransferClient = class {
|
|
758
828
|
/** Provider registry used by this client. */
|
|
759
829
|
registry;
|
|
830
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
831
|
+
defaults;
|
|
760
832
|
logger;
|
|
761
833
|
/**
|
|
762
834
|
* Creates a transfer client without opening any provider connections.
|
|
763
835
|
*
|
|
764
|
-
* @param options - Optional registry, provider factories, and
|
|
836
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
765
837
|
*/
|
|
766
838
|
constructor(options = {}) {
|
|
767
839
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
768
840
|
this.logger = options.logger ?? noopLogger;
|
|
841
|
+
if (options.defaults !== void 0) {
|
|
842
|
+
this.defaults = { ...options.defaults };
|
|
843
|
+
}
|
|
769
844
|
for (const provider of options.providers ?? []) {
|
|
770
845
|
this.registry.register(provider);
|
|
771
846
|
}
|
|
@@ -1338,18 +1413,25 @@ var TransferEngine = class {
|
|
|
1338
1413
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1339
1414
|
this.throwIfAborted(abortScope.signal, job);
|
|
1340
1415
|
const attemptStartedAt = this.now();
|
|
1416
|
+
const attemptScope = createAttemptScope(
|
|
1417
|
+
abortScope.signal,
|
|
1418
|
+
options.timeout,
|
|
1419
|
+
job,
|
|
1420
|
+
attemptNumber
|
|
1421
|
+
);
|
|
1341
1422
|
const context = this.createExecutionContext(
|
|
1342
1423
|
job,
|
|
1343
1424
|
attemptNumber,
|
|
1344
1425
|
attemptStartedAt,
|
|
1345
1426
|
options,
|
|
1346
|
-
|
|
1427
|
+
attemptScope.signal,
|
|
1347
1428
|
(bytesTransferred) => {
|
|
1348
1429
|
latestBytesTransferred = bytesTransferred;
|
|
1349
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
attemptScope.notifyProgress
|
|
1350
1432
|
);
|
|
1351
1433
|
try {
|
|
1352
|
-
const result = await runExecutor(executor, context,
|
|
1434
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1353
1435
|
context.throwIfAborted();
|
|
1354
1436
|
latestBytesTransferred = result.bytesTransferred;
|
|
1355
1437
|
const completedAt = this.now();
|
|
@@ -1367,16 +1449,27 @@ var TransferEngine = class {
|
|
|
1367
1449
|
summarizeError(error)
|
|
1368
1450
|
);
|
|
1369
1451
|
attempts.push(attempt);
|
|
1370
|
-
if (error instanceof AbortError ||
|
|
1452
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1371
1453
|
throw error;
|
|
1372
1454
|
}
|
|
1373
|
-
const retryInput = {
|
|
1455
|
+
const retryInput = {
|
|
1456
|
+
attempt: attemptNumber,
|
|
1457
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1458
|
+
error,
|
|
1459
|
+
job
|
|
1460
|
+
};
|
|
1374
1461
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1375
1462
|
if (shouldRetry) {
|
|
1376
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
|
+
}
|
|
1377
1468
|
continue;
|
|
1378
1469
|
}
|
|
1379
1470
|
throw createTransferFailure(job, error, attempts);
|
|
1471
|
+
} finally {
|
|
1472
|
+
attemptScope.dispose();
|
|
1380
1473
|
}
|
|
1381
1474
|
}
|
|
1382
1475
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1384,12 +1477,13 @@ var TransferEngine = class {
|
|
|
1384
1477
|
abortScope.dispose();
|
|
1385
1478
|
}
|
|
1386
1479
|
}
|
|
1387
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1480
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1388
1481
|
const context = {
|
|
1389
1482
|
attempt,
|
|
1390
1483
|
job,
|
|
1391
1484
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1392
1485
|
this.throwIfAborted(signal, job);
|
|
1486
|
+
notifyProgress();
|
|
1393
1487
|
updateBytesTransferred(bytesTransferred);
|
|
1394
1488
|
const progressInput = {
|
|
1395
1489
|
bytesTransferred,
|
|
@@ -1458,6 +1552,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1458
1552
|
signal: controller.signal
|
|
1459
1553
|
};
|
|
1460
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
|
+
}
|
|
1461
1645
|
function normalizeTimeoutMs(value) {
|
|
1462
1646
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1463
1647
|
return void 0;
|
|
@@ -1626,7 +1810,7 @@ async function runRoute(options) {
|
|
|
1626
1810
|
const executor = createProviderTransferExecutor({
|
|
1627
1811
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1628
1812
|
});
|
|
1629
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1813
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1630
1814
|
} finally {
|
|
1631
1815
|
if (destinationSession !== void 0) {
|
|
1632
1816
|
await destinationSession.disconnect();
|
|
@@ -1663,12 +1847,14 @@ function defaultJobId(route, now) {
|
|
|
1663
1847
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1664
1848
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1665
1849
|
}
|
|
1666
|
-
function buildExecuteOptions(options) {
|
|
1850
|
+
function buildExecuteOptions(options, client) {
|
|
1667
1851
|
const execute = {};
|
|
1852
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1853
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1668
1854
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1669
|
-
if (
|
|
1855
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1670
1856
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1671
|
-
if (
|
|
1857
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1672
1858
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1673
1859
|
return execute;
|
|
1674
1860
|
}
|
|
@@ -1725,41 +1911,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1725
1911
|
return `${source}->${destination}`;
|
|
1726
1912
|
}
|
|
1727
1913
|
|
|
1728
|
-
// src/logging/redaction.ts
|
|
1729
|
-
var REDACTED = "[REDACTED]";
|
|
1730
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1731
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1732
|
-
function isSensitiveKey(key) {
|
|
1733
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1734
|
-
}
|
|
1735
|
-
function redactCommand(command) {
|
|
1736
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1737
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1738
|
-
});
|
|
1739
|
-
}
|
|
1740
|
-
function redactValue(value) {
|
|
1741
|
-
if (typeof value === "string") {
|
|
1742
|
-
return redactCommand(value);
|
|
1743
|
-
}
|
|
1744
|
-
if (Array.isArray(value)) {
|
|
1745
|
-
return value.map((item) => redactValue(item));
|
|
1746
|
-
}
|
|
1747
|
-
if (value !== null && typeof value === "object") {
|
|
1748
|
-
return redactObject(value);
|
|
1749
|
-
}
|
|
1750
|
-
return value;
|
|
1751
|
-
}
|
|
1752
|
-
function redactObject(input) {
|
|
1753
|
-
return Object.fromEntries(
|
|
1754
|
-
Object.entries(input).map(([key, value]) => {
|
|
1755
|
-
if (isSensitiveKey(key)) {
|
|
1756
|
-
return [key, REDACTED];
|
|
1757
|
-
}
|
|
1758
|
-
return [key, redactValue(value)];
|
|
1759
|
-
})
|
|
1760
|
-
);
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
1914
|
// src/profiles/SecretSource.ts
|
|
1764
1915
|
var import_node_buffer2 = require("buffer");
|
|
1765
1916
|
var import_promises = require("fs/promises");
|
|
@@ -2181,11 +2332,11 @@ async function resolveTlsSecretSource(source, options) {
|
|
|
2181
2332
|
}
|
|
2182
2333
|
|
|
2183
2334
|
// src/utils/path.ts
|
|
2184
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2335
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2185
2336
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2186
2337
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2187
2338
|
throw new ConfigurationError({
|
|
2188
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2339
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2189
2340
|
retryable: false,
|
|
2190
2341
|
details: {
|
|
2191
2342
|
label
|
|
@@ -3147,38 +3298,53 @@ async function expectCompletion(control, command, path2) {
|
|
|
3147
3298
|
const response = await control.sendCommand(command);
|
|
3148
3299
|
assertPathCommandSucceeded(response, command, path2, control.providerId);
|
|
3149
3300
|
}
|
|
3150
|
-
async function
|
|
3151
|
-
const dataConnection = await openPassiveDataCommand(control, command, path2
|
|
3301
|
+
async function readPassiveLinesCommand(control, command, path2, onLine) {
|
|
3302
|
+
const dataConnection = await openPassiveDataCommand(control, command, path2);
|
|
3152
3303
|
try {
|
|
3153
|
-
const
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
path2,
|
|
3157
|
-
control.providerId
|
|
3158
|
-
);
|
|
3304
|
+
const failure = await consumePassiveLines(dataConnection, control.operationTimeoutMs, {
|
|
3305
|
+
command,
|
|
3306
|
+
onLine,
|
|
3307
|
+
path: path2,
|
|
3308
|
+
providerId: control.providerId
|
|
3309
|
+
});
|
|
3159
3310
|
const finalResponse = await control.readFinalResponse({
|
|
3160
3311
|
command,
|
|
3161
3312
|
operation: "data command completion",
|
|
3162
3313
|
path: path2
|
|
3163
3314
|
});
|
|
3164
3315
|
assertPathCommandSucceeded(finalResponse, command, path2, control.providerId);
|
|
3165
|
-
|
|
3316
|
+
if (failure !== void 0) throw failure;
|
|
3166
3317
|
} catch (error) {
|
|
3167
3318
|
dataConnection.close();
|
|
3168
3319
|
throw error;
|
|
3169
3320
|
}
|
|
3170
3321
|
}
|
|
3171
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
|
+
};
|
|
3172
3328
|
try {
|
|
3173
|
-
|
|
3174
|
-
|
|
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;
|
|
3175
3335
|
} catch (error) {
|
|
3176
3336
|
if (!isUnsupportedFtpCommandError(error, "MLSD")) {
|
|
3177
3337
|
throw error;
|
|
3178
3338
|
}
|
|
3179
3339
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
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;
|
|
3182
3348
|
}
|
|
3183
3349
|
async function openPassiveDataCommand(control, command, path2, options = {}) {
|
|
3184
3350
|
const offset = normalizeOptionalByteCount(options.offset, "offset", path2);
|
|
@@ -3351,22 +3517,58 @@ function openPassiveDataConnection(endpoint, timeoutMs, path2, control) {
|
|
|
3351
3517
|
}
|
|
3352
3518
|
};
|
|
3353
3519
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
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;
|
|
3356
3524
|
const clearIdleTimeout = setSocketTimeout(dataConnection.socket, timeoutMs, {
|
|
3357
3525
|
host: dataConnection.endpoint.host,
|
|
3358
3526
|
operation: "passive data transfer",
|
|
3359
|
-
path:
|
|
3360
|
-
providerId
|
|
3527
|
+
path: input.path,
|
|
3528
|
+
providerId: input.providerId
|
|
3361
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
|
+
};
|
|
3362
3550
|
try {
|
|
3363
3551
|
for await (const chunk of dataConnection.socket) {
|
|
3364
|
-
|
|
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);
|
|
3365
3566
|
}
|
|
3567
|
+
if (carry.length > 0) emit(carry);
|
|
3366
3568
|
} finally {
|
|
3367
3569
|
clearIdleTimeout();
|
|
3368
3570
|
}
|
|
3369
|
-
return
|
|
3571
|
+
return failure;
|
|
3370
3572
|
}
|
|
3371
3573
|
async function* createPassiveReadSource(control, dataConnection, command, path2, range, request) {
|
|
3372
3574
|
let bytesEmitted = 0;
|
|
@@ -3615,6 +3817,13 @@ function createTlsPinnedFingerprints(profile) {
|
|
|
3615
3817
|
if (pinnedFingerprint256 === void 0) {
|
|
3616
3818
|
return void 0;
|
|
3617
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
|
+
}
|
|
3618
3827
|
const fingerprints = Array.isArray(pinnedFingerprint256) ? pinnedFingerprint256 : [pinnedFingerprint256];
|
|
3619
3828
|
if (fingerprints.length === 0) {
|
|
3620
3829
|
throw new ConfigurationError({
|
|
@@ -5586,6 +5795,7 @@ var import_node_buffer12 = require("buffer");
|
|
|
5586
5795
|
var import_node_crypto6 = require("crypto");
|
|
5587
5796
|
var MIN_PADDING_LENGTH = 4;
|
|
5588
5797
|
var MIN_PACKET_LENGTH = 1 + MIN_PADDING_LENGTH;
|
|
5798
|
+
var MAX_SSH_PACKET_LENGTH = 256 * 1024;
|
|
5589
5799
|
function encodeSshTransportPacket(payload, options = {}) {
|
|
5590
5800
|
const body = import_node_buffer12.Buffer.from(payload);
|
|
5591
5801
|
const blockSize = normalizeBlockSize(options.blockSize ?? 8);
|
|
@@ -5663,6 +5873,14 @@ var SshTransportPacketFramer = class {
|
|
|
5663
5873
|
const packets = [];
|
|
5664
5874
|
while (this.pending.length >= 4) {
|
|
5665
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
|
+
}
|
|
5666
5884
|
const frameLength = 4 + packetLength;
|
|
5667
5885
|
if (this.pending.length < frameLength) {
|
|
5668
5886
|
break;
|
|
@@ -6140,14 +6358,35 @@ var SshTransportHandshake = class {
|
|
|
6140
6358
|
return { outbound };
|
|
6141
6359
|
}
|
|
6142
6360
|
};
|
|
6361
|
+
var MAX_IDENTIFICATION_LINE_BYTES = 8192;
|
|
6362
|
+
var MAX_PRE_IDENTIFICATION_LINES = 1024;
|
|
6143
6363
|
var SshIdentificationAccumulator = class {
|
|
6144
6364
|
pending = import_node_buffer14.Buffer.alloc(0);
|
|
6365
|
+
bannerLineCount = 0;
|
|
6145
6366
|
push(chunk) {
|
|
6146
6367
|
this.pending = import_node_buffer14.Buffer.concat([this.pending, import_node_buffer14.Buffer.from(chunk)]);
|
|
6147
6368
|
const bannerLines = [];
|
|
6148
6369
|
while (true) {
|
|
6149
6370
|
const lfIndex = this.pending.indexOf(10);
|
|
6150
|
-
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
|
+
}
|
|
6151
6390
|
const lineText = trimLineEndings(this.pending.subarray(0, lfIndex + 1).toString("ascii"));
|
|
6152
6391
|
const remainder = import_node_buffer14.Buffer.from(this.pending.subarray(lfIndex + 1));
|
|
6153
6392
|
this.pending = remainder;
|
|
@@ -6155,6 +6394,15 @@ var SshIdentificationAccumulator = class {
|
|
|
6155
6394
|
this.pending = import_node_buffer14.Buffer.alloc(0);
|
|
6156
6395
|
return { bannerLines, identLine: lineText, remainder };
|
|
6157
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
|
+
}
|
|
6158
6406
|
bannerLines.push(lineText);
|
|
6159
6407
|
}
|
|
6160
6408
|
return { bannerLines, remainder: import_node_buffer14.Buffer.alloc(0) };
|
|
@@ -6287,6 +6535,14 @@ var SshTransportPacketUnprotector = class {
|
|
|
6287
6535
|
this.framePendingRaw = import_node_buffer15.Buffer.from(this.framePendingRaw.subarray(this.blockLength));
|
|
6288
6536
|
this.framePartialDecrypted = this.decipher ? import_node_buffer15.Buffer.from(this.decipher.update(firstBlock)) : import_node_buffer15.Buffer.from(firstBlock);
|
|
6289
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
|
+
}
|
|
6290
6546
|
const remaining = 4 + packetLength - this.blockLength + this.macLength;
|
|
6291
6547
|
if (remaining < 0) {
|
|
6292
6548
|
throw new ProtocolError({
|
|
@@ -6910,6 +7166,7 @@ function decodeSftpAttributesFromReader(reader) {
|
|
|
6910
7166
|
|
|
6911
7167
|
// src/protocols/sftp/v3/SftpPacket.ts
|
|
6912
7168
|
var import_node_buffer17 = require("buffer");
|
|
7169
|
+
var MAX_SFTP_PACKET_LENGTH = 256 * 1024;
|
|
6913
7170
|
var SFTP_PACKET_TYPE = {
|
|
6914
7171
|
ATTRS: 105,
|
|
6915
7172
|
CLOSE: 4,
|
|
@@ -6969,6 +7226,13 @@ var SftpPacketFramer = class {
|
|
|
6969
7226
|
const packets = [];
|
|
6970
7227
|
while (this.pending.length >= 4) {
|
|
6971
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
|
+
}
|
|
6972
7236
|
const frameLength = 4 + bodyLength;
|
|
6973
7237
|
if (this.pending.length < frameLength) {
|
|
6974
7238
|
break;
|
|
@@ -7570,7 +7834,7 @@ function buildNativeSftpCapabilities(maxConcurrency) {
|
|
|
7570
7834
|
var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
|
|
7571
7835
|
NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
7572
7836
|
);
|
|
7573
|
-
function
|
|
7837
|
+
function createSftpProviderFactory(options = {}) {
|
|
7574
7838
|
validateNativeSftpOptions(options);
|
|
7575
7839
|
const capabilities = buildNativeSftpCapabilities(
|
|
7576
7840
|
options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
@@ -8229,6 +8493,26 @@ function validateNativeSftpOptions(options) {
|
|
|
8229
8493
|
|
|
8230
8494
|
// src/providers/web/httpInternals.ts
|
|
8231
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
|
+
}
|
|
8232
8516
|
function buildBaseUrl(profile, options) {
|
|
8233
8517
|
const protocol = options.secure ? "https:" : "http:";
|
|
8234
8518
|
const portSegment = profile.port !== void 0 ? `:${profile.port}` : "";
|
|
@@ -8276,18 +8560,19 @@ async function dispatchRequest(options, url, init) {
|
|
|
8276
8560
|
signal: controller.signal
|
|
8277
8561
|
});
|
|
8278
8562
|
} catch (error) {
|
|
8563
|
+
const safeUrl = redactUrlForLogging(url);
|
|
8279
8564
|
if (controller.signal.aborted && upstreamSignal?.aborted !== true) {
|
|
8280
8565
|
throw new TimeoutError({
|
|
8281
8566
|
cause: error,
|
|
8282
|
-
details: { timeoutMs: options.timeoutMs, url:
|
|
8283
|
-
message: `HTTP request to ${
|
|
8567
|
+
details: { timeoutMs: options.timeoutMs, url: safeUrl },
|
|
8568
|
+
message: `HTTP request to ${safeUrl} timed out after ${String(options.timeoutMs)}ms`,
|
|
8284
8569
|
retryable: true
|
|
8285
8570
|
});
|
|
8286
8571
|
}
|
|
8287
8572
|
throw new ConnectionError({
|
|
8288
8573
|
cause: error,
|
|
8289
|
-
details: { url:
|
|
8290
|
-
message: `HTTP request to ${
|
|
8574
|
+
details: { url: safeUrl },
|
|
8575
|
+
message: `HTTP request to ${safeUrl} failed`,
|
|
8291
8576
|
retryable: true
|
|
8292
8577
|
});
|
|
8293
8578
|
} finally {
|
|
@@ -8319,8 +8604,48 @@ function formatRangeHeader(offset, length) {
|
|
|
8319
8604
|
const end = offset + length - 1;
|
|
8320
8605
|
return `bytes=${String(offset)}-${String(end)}`;
|
|
8321
8606
|
}
|
|
8322
|
-
|
|
8323
|
-
|
|
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
|
+
}
|
|
8324
8649
|
if (response.status === 401) {
|
|
8325
8650
|
return new AuthenticationError({
|
|
8326
8651
|
details,
|
|
@@ -8352,14 +8677,54 @@ async function* webStreamToAsyncIterable(body) {
|
|
|
8352
8677
|
const reader = body.getReader();
|
|
8353
8678
|
try {
|
|
8354
8679
|
while (true) {
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
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;
|
|
8358
8693
|
}
|
|
8359
8694
|
} finally {
|
|
8360
8695
|
reader.releaseLock();
|
|
8361
8696
|
}
|
|
8362
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
|
+
}
|
|
8363
8728
|
function secretToString2(value) {
|
|
8364
8729
|
if (typeof value === "string") return value;
|
|
8365
8730
|
if (value instanceof Uint8Array || import_node_buffer20.Buffer.isBuffer(value)) {
|
|
@@ -11958,6 +12323,7 @@ function createHttpProviderFactory(options = {}) {
|
|
|
11958
12323
|
const secure = options.secure ?? id === "https";
|
|
11959
12324
|
const basePath = options.basePath ?? "";
|
|
11960
12325
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
12326
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
11961
12327
|
if (typeof fetchImpl !== "function") {
|
|
11962
12328
|
throw new ConfigurationError({
|
|
11963
12329
|
message: "Global fetch is unavailable; supply HttpProviderOptions.fetch explicitly",
|
|
@@ -12007,6 +12373,13 @@ var HttpProvider = class {
|
|
|
12007
12373
|
id;
|
|
12008
12374
|
capabilities;
|
|
12009
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
|
+
}
|
|
12010
12383
|
const headers = { ...this.internals.defaultHeaders };
|
|
12011
12384
|
if (profile.username !== void 0) {
|
|
12012
12385
|
const username = await resolveSecret(profile.username);
|
|
@@ -12065,7 +12438,7 @@ var HttpFileSystem = class {
|
|
|
12065
12438
|
method: "HEAD"
|
|
12066
12439
|
});
|
|
12067
12440
|
if (!response.ok) {
|
|
12068
|
-
throw
|
|
12441
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12069
12442
|
}
|
|
12070
12443
|
return responseToStat(response, normalized);
|
|
12071
12444
|
}
|
|
@@ -12090,12 +12463,12 @@ var HttpTransferOperations = class {
|
|
|
12090
12463
|
if (request.signal !== void 0) requestInit.signal = request.signal;
|
|
12091
12464
|
const response = await dispatchRequest(this.options, url, requestInit);
|
|
12092
12465
|
if (!response.ok && response.status !== 206) {
|
|
12093
|
-
throw
|
|
12466
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12094
12467
|
}
|
|
12095
12468
|
const body = response.body;
|
|
12096
12469
|
if (body === null) {
|
|
12097
12470
|
throw new ConnectionError({
|
|
12098
|
-
message: `HTTP response had no body for ${url
|
|
12471
|
+
message: `HTTP response had no body for ${redactUrlForLogging(url)}`,
|
|
12099
12472
|
retryable: true
|
|
12100
12473
|
});
|
|
12101
12474
|
}
|
|
@@ -12153,7 +12526,8 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
12153
12526
|
const secure = options.secure ?? false;
|
|
12154
12527
|
const basePath = options.basePath ?? "";
|
|
12155
12528
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
12156
|
-
const uploadStreaming = options.uploadStreaming ?? "
|
|
12529
|
+
const uploadStreaming = options.uploadStreaming ?? "always";
|
|
12530
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
12157
12531
|
if (typeof fetchImpl !== "function") {
|
|
12158
12532
|
throw new ConfigurationError({
|
|
12159
12533
|
message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
|
|
@@ -12205,6 +12579,13 @@ var WebDavProvider = class {
|
|
|
12205
12579
|
id;
|
|
12206
12580
|
capabilities;
|
|
12207
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
|
+
}
|
|
12208
12589
|
const headers = { ...this.internals.defaultHeaders };
|
|
12209
12590
|
if (profile.username !== void 0) {
|
|
12210
12591
|
const username = await resolveSecret(profile.username);
|
|
@@ -12259,7 +12640,7 @@ var WebDavFileSystem = class {
|
|
|
12259
12640
|
method: "PROPFIND"
|
|
12260
12641
|
});
|
|
12261
12642
|
if (!response.ok && response.status !== 207) {
|
|
12262
|
-
throw
|
|
12643
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12263
12644
|
}
|
|
12264
12645
|
const body = await response.text();
|
|
12265
12646
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12273,7 +12654,7 @@ var WebDavFileSystem = class {
|
|
|
12273
12654
|
method: "PROPFIND"
|
|
12274
12655
|
});
|
|
12275
12656
|
if (!response.ok && response.status !== 207) {
|
|
12276
|
-
throw
|
|
12657
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12277
12658
|
}
|
|
12278
12659
|
const body = await response.text();
|
|
12279
12660
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12318,12 +12699,12 @@ var WebDavTransferOperations = class {
|
|
|
12318
12699
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12319
12700
|
const response = await dispatchRequest(this.options, url, init);
|
|
12320
12701
|
if (!response.ok && response.status !== 206) {
|
|
12321
|
-
throw
|
|
12702
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12322
12703
|
}
|
|
12323
12704
|
const body = response.body;
|
|
12324
12705
|
if (body === null) {
|
|
12325
12706
|
throw new ConnectionError({
|
|
12326
|
-
message: `WebDAV response had no body for ${url
|
|
12707
|
+
message: `WebDAV response had no body for ${redactUrlForLogging(url)}`,
|
|
12327
12708
|
retryable: true
|
|
12328
12709
|
});
|
|
12329
12710
|
}
|
|
@@ -12367,7 +12748,7 @@ var WebDavTransferOperations = class {
|
|
|
12367
12748
|
if (request.signal !== void 0) init2.signal = request.signal;
|
|
12368
12749
|
const response2 = await dispatchRequest(this.options, url, init2);
|
|
12369
12750
|
if (!response2.ok) {
|
|
12370
|
-
throw
|
|
12751
|
+
throw await mapResponseErrorWithBody(response2, normalized);
|
|
12371
12752
|
}
|
|
12372
12753
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
12373
12754
|
const result2 = {
|
|
@@ -12403,7 +12784,7 @@ var WebDavTransferOperations = class {
|
|
|
12403
12784
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12404
12785
|
const response = await dispatchRequest(this.options, url, init);
|
|
12405
12786
|
if (!response.ok) {
|
|
12406
|
-
throw
|
|
12787
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12407
12788
|
}
|
|
12408
12789
|
const result = {
|
|
12409
12790
|
bytesTransferred,
|
|
@@ -12429,36 +12810,6 @@ async function collectChunks6(source) {
|
|
|
12429
12810
|
}
|
|
12430
12811
|
return out;
|
|
12431
12812
|
}
|
|
12432
|
-
function asyncIterableToReadableStream(source, onChunk) {
|
|
12433
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
12434
|
-
return new ReadableStream({
|
|
12435
|
-
async pull(controller) {
|
|
12436
|
-
try {
|
|
12437
|
-
const next = await iterator.next();
|
|
12438
|
-
if (next.done === true) {
|
|
12439
|
-
controller.close();
|
|
12440
|
-
return;
|
|
12441
|
-
}
|
|
12442
|
-
const chunk = next.value;
|
|
12443
|
-
if (chunk.byteLength === 0) {
|
|
12444
|
-
return;
|
|
12445
|
-
}
|
|
12446
|
-
controller.enqueue(chunk);
|
|
12447
|
-
onChunk(chunk);
|
|
12448
|
-
} catch (error) {
|
|
12449
|
-
controller.error(error);
|
|
12450
|
-
}
|
|
12451
|
-
},
|
|
12452
|
-
async cancel(reason) {
|
|
12453
|
-
if (typeof iterator.return === "function") {
|
|
12454
|
-
try {
|
|
12455
|
-
await iterator.return(reason);
|
|
12456
|
-
} catch {
|
|
12457
|
-
}
|
|
12458
|
-
}
|
|
12459
|
-
}
|
|
12460
|
-
});
|
|
12461
|
-
}
|
|
12462
12813
|
function parsePropfindResponses(xml, baseUrl) {
|
|
12463
12814
|
const entries = [];
|
|
12464
12815
|
const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
|
|
@@ -12545,11 +12896,12 @@ var import_node_path3 = require("path");
|
|
|
12545
12896
|
|
|
12546
12897
|
// src/providers/web/awsSigv4.ts
|
|
12547
12898
|
var import_node_crypto12 = require("crypto");
|
|
12899
|
+
var UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
|
12548
12900
|
function signSigV4(input) {
|
|
12549
12901
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
12550
12902
|
const amzDate = formatAmzDate(now);
|
|
12551
12903
|
const dateStamp = amzDate.slice(0, 8);
|
|
12552
|
-
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());
|
|
12553
12905
|
input.headers["host"] = input.url.host;
|
|
12554
12906
|
input.headers["x-amz-date"] = amzDate;
|
|
12555
12907
|
input.headers["x-amz-content-sha256"] = payloadHash;
|
|
@@ -12830,7 +13182,7 @@ var S3FileSystem = class {
|
|
|
12830
13182
|
url.searchParams.set("delimiter", "/");
|
|
12831
13183
|
if (prefix.length > 0) url.searchParams.set("prefix", prefix);
|
|
12832
13184
|
const response = await s3Fetch(this.options, "GET", url);
|
|
12833
|
-
if (!response.ok) throw
|
|
13185
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12834
13186
|
const body = await response.text();
|
|
12835
13187
|
return parseListObjectsV2(body, prefix);
|
|
12836
13188
|
}
|
|
@@ -12838,7 +13190,7 @@ var S3FileSystem = class {
|
|
|
12838
13190
|
const normalized = normalizeRemotePath(path2);
|
|
12839
13191
|
const url = buildObjectUrl(this.options, normalized);
|
|
12840
13192
|
const response = await s3Fetch(this.options, "HEAD", url);
|
|
12841
|
-
if (!response.ok) throw
|
|
13193
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12842
13194
|
const stat = {
|
|
12843
13195
|
exists: true,
|
|
12844
13196
|
name: basenameRemotePath(normalized),
|
|
@@ -12878,12 +13230,12 @@ var S3TransferOperations = class {
|
|
|
12878
13230
|
extraHeaders: headers
|
|
12879
13231
|
});
|
|
12880
13232
|
if (!response.ok && response.status !== 206) {
|
|
12881
|
-
throw
|
|
13233
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12882
13234
|
}
|
|
12883
13235
|
const body = response.body;
|
|
12884
13236
|
if (body === null) {
|
|
12885
13237
|
throw new ConnectionError({
|
|
12886
|
-
message: `S3 response had no body for ${url
|
|
13238
|
+
message: `S3 response had no body for ${redactUrlForLogging(url)}`,
|
|
12887
13239
|
retryable: true
|
|
12888
13240
|
});
|
|
12889
13241
|
}
|
|
@@ -12919,19 +13271,33 @@ var S3TransferOperations = class {
|
|
|
12919
13271
|
}
|
|
12920
13272
|
return this.writeSingleShot(request, normalized);
|
|
12921
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
|
+
*/
|
|
12922
13280
|
async writeSingleShot(request, normalized) {
|
|
12923
13281
|
const url = buildObjectUrl(this.options, normalized);
|
|
12924
|
-
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
|
+
});
|
|
12925
13292
|
const response = await s3Fetch(this.options, "PUT", url, {
|
|
12926
13293
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12927
|
-
|
|
12928
|
-
|
|
13294
|
+
extraHeaders: { "content-type": "application/octet-stream" },
|
|
13295
|
+
streamBody: { content: stream, contentLength: totalBytes }
|
|
12929
13296
|
});
|
|
12930
|
-
if (!response.ok) throw
|
|
12931
|
-
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
13297
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12932
13298
|
const result = {
|
|
12933
|
-
bytesTransferred
|
|
12934
|
-
totalBytes
|
|
13299
|
+
bytesTransferred,
|
|
13300
|
+
totalBytes
|
|
12935
13301
|
};
|
|
12936
13302
|
const etag = response.headers.get("etag");
|
|
12937
13303
|
if (etag !== null) result.checksum = etag;
|
|
@@ -12995,7 +13361,7 @@ var S3TransferOperations = class {
|
|
|
12995
13361
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12996
13362
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
12997
13363
|
});
|
|
12998
|
-
if (!initiateResponse.ok) throw
|
|
13364
|
+
if (!initiateResponse.ok) throw await mapResponseErrorWithBody(initiateResponse, normalized);
|
|
12999
13365
|
const initiateBody = await initiateResponse.text();
|
|
13000
13366
|
const initiated = innerText(initiateBody, "UploadId");
|
|
13001
13367
|
if (initiated === void 0 || initiated === "") {
|
|
@@ -13034,7 +13400,7 @@ var S3TransferOperations = class {
|
|
|
13034
13400
|
body: partBytes.bytes
|
|
13035
13401
|
});
|
|
13036
13402
|
if (!partResponse.ok) {
|
|
13037
|
-
throw
|
|
13403
|
+
throw await mapResponseErrorWithBody(partResponse, normalized);
|
|
13038
13404
|
}
|
|
13039
13405
|
const partEtag = partResponse.headers.get("etag");
|
|
13040
13406
|
if (partEtag === null) {
|
|
@@ -13090,7 +13456,7 @@ var S3TransferOperations = class {
|
|
|
13090
13456
|
if (resumeStore === void 0) {
|
|
13091
13457
|
await abortMultipart(this.options, objectUrl, uploadId).catch(() => void 0);
|
|
13092
13458
|
}
|
|
13093
|
-
throw
|
|
13459
|
+
throw await mapResponseErrorWithBody(completeResponse, normalized);
|
|
13094
13460
|
}
|
|
13095
13461
|
if (resumeStore !== void 0) await resumeStore.clear(resumeKey);
|
|
13096
13462
|
const completeBody = await completeResponse.text();
|
|
@@ -13109,7 +13475,7 @@ var S3TransferOperations = class {
|
|
|
13109
13475
|
body: buffered,
|
|
13110
13476
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
13111
13477
|
});
|
|
13112
|
-
if (!response.ok) throw
|
|
13478
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
13113
13479
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
13114
13480
|
const result = {
|
|
13115
13481
|
bytesTransferred: buffered.byteLength,
|
|
@@ -13127,6 +13493,8 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13127
13493
|
};
|
|
13128
13494
|
if (fetchOptions.body !== void 0) {
|
|
13129
13495
|
headers["content-length"] = String(fetchOptions.body.byteLength);
|
|
13496
|
+
} else if (fetchOptions.streamBody !== void 0) {
|
|
13497
|
+
headers["content-length"] = String(fetchOptions.streamBody.contentLength);
|
|
13130
13498
|
}
|
|
13131
13499
|
signSigV4({
|
|
13132
13500
|
accessKeyId: options.accessKeyId,
|
|
@@ -13137,10 +13505,16 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13137
13505
|
service: options.service,
|
|
13138
13506
|
url,
|
|
13139
13507
|
...fetchOptions.body !== void 0 ? { body: fetchOptions.body } : {},
|
|
13508
|
+
...fetchOptions.streamBody !== void 0 ? { unsignedPayload: true } : {},
|
|
13140
13509
|
...options.sessionToken !== void 0 ? { sessionToken: options.sessionToken } : {}
|
|
13141
13510
|
});
|
|
13142
13511
|
const init = { headers, method };
|
|
13143
|
-
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
|
+
}
|
|
13144
13518
|
if (fetchOptions.signal !== void 0) init.signal = fetchOptions.signal;
|
|
13145
13519
|
const controller = new AbortController();
|
|
13146
13520
|
const upstreamSignal = init.signal ?? null;
|
|
@@ -13158,10 +13532,11 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13158
13532
|
try {
|
|
13159
13533
|
return await options.fetch(url.toString(), { ...init, signal: controller.signal });
|
|
13160
13534
|
} catch (error) {
|
|
13535
|
+
const safeUrl = redactUrlForLogging(url);
|
|
13161
13536
|
throw new ConnectionError({
|
|
13162
13537
|
cause: error,
|
|
13163
|
-
details: { url:
|
|
13164
|
-
message: `S3 request to ${
|
|
13538
|
+
details: { url: safeUrl },
|
|
13539
|
+
message: `S3 request to ${safeUrl} failed`,
|
|
13165
13540
|
retryable: true
|
|
13166
13541
|
});
|
|
13167
13542
|
} finally {
|
|
@@ -13328,7 +13703,7 @@ function getBuiltinCapabilityMatrix() {
|
|
|
13328
13703
|
label: "FTPS"
|
|
13329
13704
|
},
|
|
13330
13705
|
{
|
|
13331
|
-
capabilities:
|
|
13706
|
+
capabilities: createSftpProviderFactory().capabilities,
|
|
13332
13707
|
id: "sftp",
|
|
13333
13708
|
label: "SFTP"
|
|
13334
13709
|
},
|
|
@@ -13629,7 +14004,6 @@ function expandAlgorithms(values) {
|
|
|
13629
14004
|
}
|
|
13630
14005
|
|
|
13631
14006
|
// src/profiles/importers/FileZillaImporter.ts
|
|
13632
|
-
var import_node_buffer25 = require("buffer");
|
|
13633
14007
|
function importFileZillaSites(xml) {
|
|
13634
14008
|
const events = tokenizeXml(xml);
|
|
13635
14009
|
if (events.length === 0) {
|
|
@@ -13645,7 +14019,6 @@ function importFileZillaSites(xml) {
|
|
|
13645
14019
|
const folderNamePending = [];
|
|
13646
14020
|
let inServer = false;
|
|
13647
14021
|
let serverFields = {};
|
|
13648
|
-
let serverPasswordEncoding;
|
|
13649
14022
|
let activeTag;
|
|
13650
14023
|
let captureFolderName = false;
|
|
13651
14024
|
for (const event of events) {
|
|
@@ -13658,13 +14031,9 @@ function importFileZillaSites(xml) {
|
|
|
13658
14031
|
if (event.name === "Server") {
|
|
13659
14032
|
inServer = true;
|
|
13660
14033
|
serverFields = {};
|
|
13661
|
-
serverPasswordEncoding = void 0;
|
|
13662
14034
|
continue;
|
|
13663
14035
|
}
|
|
13664
14036
|
activeTag = event.name;
|
|
13665
|
-
if (event.name === "Pass" && inServer) {
|
|
13666
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
13667
|
-
}
|
|
13668
14037
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
13669
14038
|
captureFolderName = true;
|
|
13670
14039
|
}
|
|
@@ -13690,7 +14059,7 @@ function importFileZillaSites(xml) {
|
|
|
13690
14059
|
}
|
|
13691
14060
|
if (event.name === "Server") {
|
|
13692
14061
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
13693
|
-
const result = buildSiteFromFields(serverFields
|
|
14062
|
+
const result = buildSiteFromFields(serverFields);
|
|
13694
14063
|
if (result.kind === "site") {
|
|
13695
14064
|
sites.push({ ...result.site, folder });
|
|
13696
14065
|
} else {
|
|
@@ -13702,7 +14071,6 @@ function importFileZillaSites(xml) {
|
|
|
13702
14071
|
}
|
|
13703
14072
|
inServer = false;
|
|
13704
14073
|
serverFields = {};
|
|
13705
|
-
serverPasswordEncoding = void 0;
|
|
13706
14074
|
activeTag = void 0;
|
|
13707
14075
|
continue;
|
|
13708
14076
|
}
|
|
@@ -13711,7 +14079,7 @@ function importFileZillaSites(xml) {
|
|
|
13711
14079
|
}
|
|
13712
14080
|
return { sites, skipped };
|
|
13713
14081
|
}
|
|
13714
|
-
function buildSiteFromFields(fields
|
|
14082
|
+
function buildSiteFromFields(fields) {
|
|
13715
14083
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
13716
14084
|
const host = (fields["Host"] ?? "").trim();
|
|
13717
14085
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -13730,18 +14098,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
13730
14098
|
}
|
|
13731
14099
|
const user = fields["User"]?.trim();
|
|
13732
14100
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
13733
|
-
let password;
|
|
13734
14101
|
const rawPass = fields["Pass"];
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
password = import_node_buffer25.Buffer.from(rawPass, "base64").toString("utf8");
|
|
13738
|
-
} else {
|
|
13739
|
-
password = rawPass;
|
|
13740
|
-
}
|
|
13741
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
13742
|
-
}
|
|
13743
|
-
const site = { name, profile };
|
|
13744
|
-
if (password !== void 0) site.password = password;
|
|
14102
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
14103
|
+
const site = { hasStoredPassword, name, profile };
|
|
13745
14104
|
const logonText = fields["Logontype"];
|
|
13746
14105
|
if (logonText !== void 0) {
|
|
13747
14106
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -14062,6 +14421,62 @@ function openTcpSocket(host, port, timeoutMs) {
|
|
|
14062
14421
|
});
|
|
14063
14422
|
}
|
|
14064
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
|
+
|
|
14065
14480
|
// src/transfers/TransferPlan.ts
|
|
14066
14481
|
function createTransferPlan(input) {
|
|
14067
14482
|
const plan = {
|
|
@@ -14159,8 +14574,8 @@ var TransferQueue = class {
|
|
|
14159
14574
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
14160
14575
|
this.defaultExecutor = options.executor;
|
|
14161
14576
|
this.resolveExecutor = options.resolveExecutor;
|
|
14162
|
-
this.retry = options.retry;
|
|
14163
|
-
this.timeout = options.timeout;
|
|
14577
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
14578
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
14164
14579
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
14165
14580
|
this.onProgress = options.onProgress;
|
|
14166
14581
|
this.onReceipt = options.onReceipt;
|
|
@@ -15560,6 +15975,7 @@ async function dispatchWebhook(options) {
|
|
|
15560
15975
|
return { attempts: attempt, delivered: false, status: lastStatus };
|
|
15561
15976
|
}
|
|
15562
15977
|
function createWebhookAuditLog(options) {
|
|
15978
|
+
validateTarget(options.target);
|
|
15563
15979
|
return {
|
|
15564
15980
|
list: () => Promise.resolve([]),
|
|
15565
15981
|
record: async (entry) => {
|
|
@@ -15582,6 +15998,24 @@ function validateTarget(target) {
|
|
|
15582
15998
|
retryable: false
|
|
15583
15999
|
});
|
|
15584
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
|
+
});
|
|
15585
16019
|
}
|
|
15586
16020
|
function normalizeRetry(retry) {
|
|
15587
16021
|
return {
|
|
@@ -15618,6 +16052,26 @@ var ApprovalRejectedError = class _ApprovalRejectedError extends ZeroTransferErr
|
|
|
15618
16052
|
}
|
|
15619
16053
|
request;
|
|
15620
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
|
+
};
|
|
15621
16075
|
var ApprovalRegistry = class {
|
|
15622
16076
|
requests = /* @__PURE__ */ new Map();
|
|
15623
16077
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -15743,9 +16197,27 @@ function createApprovalGate(options) {
|
|
|
15743
16197
|
};
|
|
15744
16198
|
if (input.signal.aborted) onAbort();
|
|
15745
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
|
+
}
|
|
15746
16217
|
try {
|
|
15747
|
-
await
|
|
16218
|
+
await Promise.race(pendingPromises);
|
|
15748
16219
|
} finally {
|
|
16220
|
+
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
15749
16221
|
input.signal.removeEventListener("abort", onAbort);
|
|
15750
16222
|
}
|
|
15751
16223
|
return options.runner(input);
|
|
@@ -16118,6 +16590,7 @@ function isMainModule(importMetaUrl) {
|
|
|
16118
16590
|
AbortError,
|
|
16119
16591
|
ApprovalRegistry,
|
|
16120
16592
|
ApprovalRejectedError,
|
|
16593
|
+
ApprovalTimeoutError,
|
|
16121
16594
|
AuthenticationError,
|
|
16122
16595
|
AuthorizationError,
|
|
16123
16596
|
CLASSIC_PROVIDER_IDS,
|
|
@@ -16167,6 +16640,7 @@ function isMainModule(importMetaUrl) {
|
|
|
16167
16640
|
createAtomicDeployPlan,
|
|
16168
16641
|
createAzureBlobProviderFactory,
|
|
16169
16642
|
createBandwidthThrottle,
|
|
16643
|
+
createDefaultRetryPolicy,
|
|
16170
16644
|
createDropboxProviderFactory,
|
|
16171
16645
|
createFileSystemS3MultipartResumeStore,
|
|
16172
16646
|
createFtpProviderFactory,
|
|
@@ -16179,7 +16653,6 @@ function isMainModule(importMetaUrl) {
|
|
|
16179
16653
|
createLocalProviderFactory,
|
|
16180
16654
|
createMemoryProviderFactory,
|
|
16181
16655
|
createMemoryS3MultipartResumeStore,
|
|
16182
|
-
createNativeSftpProviderFactory,
|
|
16183
16656
|
createOAuthTokenSecretSource,
|
|
16184
16657
|
createOneDriveProviderFactory,
|
|
16185
16658
|
createOutboxRoute,
|
|
@@ -16237,8 +16710,10 @@ function isMainModule(importMetaUrl) {
|
|
|
16237
16710
|
parseUnixListLine,
|
|
16238
16711
|
redactCommand,
|
|
16239
16712
|
redactConnectionProfile,
|
|
16713
|
+
redactErrorForLogging,
|
|
16240
16714
|
redactObject,
|
|
16241
16715
|
redactSecretSource,
|
|
16716
|
+
redactUrlForLogging,
|
|
16242
16717
|
redactValue,
|
|
16243
16718
|
resolveConnectionProfileSecrets,
|
|
16244
16719
|
resolveOpenSshHost,
|