@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.mjs
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
// src/client/ZeroTransfer.ts
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
|
|
4
|
+
// src/logging/redaction.ts
|
|
5
|
+
var REDACTED = "[REDACTED]";
|
|
6
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
7
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
8
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
9
|
+
function isSensitiveKey(key) {
|
|
10
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
11
|
+
}
|
|
12
|
+
function redactCommand(command) {
|
|
13
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
14
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function redactValue(value) {
|
|
18
|
+
if (typeof value === "string") {
|
|
19
|
+
return redactCommand(value);
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.map((item) => redactValue(item));
|
|
23
|
+
}
|
|
24
|
+
if (value !== null && typeof value === "object") {
|
|
25
|
+
return redactObject(value);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function redactObject(input) {
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.entries(input).map(([key, value]) => {
|
|
32
|
+
if (isSensitiveKey(key)) {
|
|
33
|
+
return [key, REDACTED];
|
|
34
|
+
}
|
|
35
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
36
|
+
return [key, redactUrlForLogging(value)];
|
|
37
|
+
}
|
|
38
|
+
return [key, redactValue(value)];
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function redactUrlForLogging(url) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
46
|
+
} catch {
|
|
47
|
+
return REDACTED;
|
|
48
|
+
}
|
|
49
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
50
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
51
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
52
|
+
}
|
|
53
|
+
function redactErrorForLogging(error) {
|
|
54
|
+
if (error !== null && typeof error === "object") {
|
|
55
|
+
const candidate = error;
|
|
56
|
+
if (typeof candidate.toJSON === "function") {
|
|
57
|
+
return redactObject(candidate.toJSON());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
return redactObject({ message: error.message, name: error.name });
|
|
62
|
+
}
|
|
63
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
64
|
+
}
|
|
65
|
+
|
|
4
66
|
// src/errors/ZeroTransferError.ts
|
|
5
67
|
var ZeroTransferError = class extends Error {
|
|
6
68
|
/** Stable machine-readable error code. */
|
|
@@ -42,6 +104,11 @@ var ZeroTransferError = class extends Error {
|
|
|
42
104
|
/**
|
|
43
105
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
44
106
|
*
|
|
107
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
108
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
109
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
110
|
+
* for programmatic consumers.
|
|
111
|
+
*
|
|
45
112
|
* @returns A JSON-safe object containing public structured error fields.
|
|
46
113
|
*/
|
|
47
114
|
toJSON() {
|
|
@@ -51,12 +118,12 @@ var ZeroTransferError = class extends Error {
|
|
|
51
118
|
message: this.message,
|
|
52
119
|
protocol: this.protocol,
|
|
53
120
|
host: this.host,
|
|
54
|
-
command: this.command,
|
|
121
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
55
122
|
ftpCode: this.ftpCode,
|
|
56
123
|
sftpCode: this.sftpCode,
|
|
57
124
|
path: this.path,
|
|
58
125
|
retryable: this.retryable,
|
|
59
|
-
details: this.details
|
|
126
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
60
127
|
};
|
|
61
128
|
}
|
|
62
129
|
};
|
|
@@ -579,15 +646,20 @@ var ProviderRegistry = class {
|
|
|
579
646
|
var TransferClient = class {
|
|
580
647
|
/** Provider registry used by this client. */
|
|
581
648
|
registry;
|
|
649
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
650
|
+
defaults;
|
|
582
651
|
logger;
|
|
583
652
|
/**
|
|
584
653
|
* Creates a transfer client without opening any provider connections.
|
|
585
654
|
*
|
|
586
|
-
* @param options - Optional registry, provider factories, and
|
|
655
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
587
656
|
*/
|
|
588
657
|
constructor(options = {}) {
|
|
589
658
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
590
659
|
this.logger = options.logger ?? noopLogger;
|
|
660
|
+
if (options.defaults !== void 0) {
|
|
661
|
+
this.defaults = { ...options.defaults };
|
|
662
|
+
}
|
|
591
663
|
for (const provider of options.providers ?? []) {
|
|
592
664
|
this.registry.register(provider);
|
|
593
665
|
}
|
|
@@ -1160,18 +1232,25 @@ var TransferEngine = class {
|
|
|
1160
1232
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1161
1233
|
this.throwIfAborted(abortScope.signal, job);
|
|
1162
1234
|
const attemptStartedAt = this.now();
|
|
1235
|
+
const attemptScope = createAttemptScope(
|
|
1236
|
+
abortScope.signal,
|
|
1237
|
+
options.timeout,
|
|
1238
|
+
job,
|
|
1239
|
+
attemptNumber
|
|
1240
|
+
);
|
|
1163
1241
|
const context = this.createExecutionContext(
|
|
1164
1242
|
job,
|
|
1165
1243
|
attemptNumber,
|
|
1166
1244
|
attemptStartedAt,
|
|
1167
1245
|
options,
|
|
1168
|
-
|
|
1246
|
+
attemptScope.signal,
|
|
1169
1247
|
(bytesTransferred) => {
|
|
1170
1248
|
latestBytesTransferred = bytesTransferred;
|
|
1171
|
-
}
|
|
1249
|
+
},
|
|
1250
|
+
attemptScope.notifyProgress
|
|
1172
1251
|
);
|
|
1173
1252
|
try {
|
|
1174
|
-
const result = await runExecutor(executor, context,
|
|
1253
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1175
1254
|
context.throwIfAborted();
|
|
1176
1255
|
latestBytesTransferred = result.bytesTransferred;
|
|
1177
1256
|
const completedAt = this.now();
|
|
@@ -1189,16 +1268,27 @@ var TransferEngine = class {
|
|
|
1189
1268
|
summarizeError(error)
|
|
1190
1269
|
);
|
|
1191
1270
|
attempts.push(attempt);
|
|
1192
|
-
if (error instanceof AbortError ||
|
|
1271
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1193
1272
|
throw error;
|
|
1194
1273
|
}
|
|
1195
|
-
const retryInput = {
|
|
1274
|
+
const retryInput = {
|
|
1275
|
+
attempt: attemptNumber,
|
|
1276
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1277
|
+
error,
|
|
1278
|
+
job
|
|
1279
|
+
};
|
|
1196
1280
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1197
1281
|
if (shouldRetry) {
|
|
1198
1282
|
options.retry?.onRetry?.(retryInput);
|
|
1283
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1284
|
+
if (delayMs > 0) {
|
|
1285
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1286
|
+
}
|
|
1199
1287
|
continue;
|
|
1200
1288
|
}
|
|
1201
1289
|
throw createTransferFailure(job, error, attempts);
|
|
1290
|
+
} finally {
|
|
1291
|
+
attemptScope.dispose();
|
|
1202
1292
|
}
|
|
1203
1293
|
}
|
|
1204
1294
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1206,12 +1296,13 @@ var TransferEngine = class {
|
|
|
1206
1296
|
abortScope.dispose();
|
|
1207
1297
|
}
|
|
1208
1298
|
}
|
|
1209
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1299
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1210
1300
|
const context = {
|
|
1211
1301
|
attempt,
|
|
1212
1302
|
job,
|
|
1213
1303
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1214
1304
|
this.throwIfAborted(signal, job);
|
|
1305
|
+
notifyProgress();
|
|
1215
1306
|
updateBytesTransferred(bytesTransferred);
|
|
1216
1307
|
const progressInput = {
|
|
1217
1308
|
bytesTransferred,
|
|
@@ -1280,6 +1371,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1280
1371
|
signal: controller.signal
|
|
1281
1372
|
};
|
|
1282
1373
|
}
|
|
1374
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1375
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1376
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1377
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1378
|
+
const scope = {
|
|
1379
|
+
dispose: () => void 0,
|
|
1380
|
+
notifyProgress: () => void 0
|
|
1381
|
+
};
|
|
1382
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1383
|
+
return scope;
|
|
1384
|
+
}
|
|
1385
|
+
const controller = new AbortController();
|
|
1386
|
+
const retryable = timeout?.retryable ?? true;
|
|
1387
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1388
|
+
if (parentSignal?.aborted === true) {
|
|
1389
|
+
abortFromParent();
|
|
1390
|
+
} else {
|
|
1391
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1392
|
+
}
|
|
1393
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1394
|
+
controller.abort(
|
|
1395
|
+
new TimeoutError({
|
|
1396
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1397
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1398
|
+
retryable
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
}, attemptTimeoutMs);
|
|
1402
|
+
let stallTimer;
|
|
1403
|
+
const armStallWatchdog = () => {
|
|
1404
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1405
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1406
|
+
stallTimer = setTimeout(() => {
|
|
1407
|
+
controller.abort(
|
|
1408
|
+
new TimeoutError({
|
|
1409
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1410
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1411
|
+
retryable
|
|
1412
|
+
})
|
|
1413
|
+
);
|
|
1414
|
+
}, stallTimeoutMs);
|
|
1415
|
+
};
|
|
1416
|
+
armStallWatchdog();
|
|
1417
|
+
return {
|
|
1418
|
+
dispose: () => {
|
|
1419
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1420
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1421
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1422
|
+
},
|
|
1423
|
+
notifyProgress: armStallWatchdog,
|
|
1424
|
+
signal: controller.signal
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1428
|
+
return new Promise((resolve, reject) => {
|
|
1429
|
+
if (signal === void 0) {
|
|
1430
|
+
setTimeout(resolve, delayMs);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (signal.aborted) {
|
|
1434
|
+
reject(toAbortFailure(signal, job));
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const rejectAbort = () => {
|
|
1438
|
+
clearTimeout(timer);
|
|
1439
|
+
reject(toAbortFailure(signal, job));
|
|
1440
|
+
};
|
|
1441
|
+
const timer = setTimeout(() => {
|
|
1442
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1443
|
+
resolve();
|
|
1444
|
+
}, delayMs);
|
|
1445
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function toAbortFailure(signal, job) {
|
|
1449
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1450
|
+
return signal.reason;
|
|
1451
|
+
}
|
|
1452
|
+
return new AbortError({
|
|
1453
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1454
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1455
|
+
retryable: false
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
function normalizeDelayMs(value) {
|
|
1459
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1460
|
+
return 0;
|
|
1461
|
+
}
|
|
1462
|
+
return Math.floor(value);
|
|
1463
|
+
}
|
|
1283
1464
|
function normalizeTimeoutMs(value) {
|
|
1284
1465
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1285
1466
|
return void 0;
|
|
@@ -1448,7 +1629,7 @@ async function runRoute(options) {
|
|
|
1448
1629
|
const executor = createProviderTransferExecutor({
|
|
1449
1630
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1450
1631
|
});
|
|
1451
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1632
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1452
1633
|
} finally {
|
|
1453
1634
|
if (destinationSession !== void 0) {
|
|
1454
1635
|
await destinationSession.disconnect();
|
|
@@ -1485,12 +1666,14 @@ function defaultJobId(route, now) {
|
|
|
1485
1666
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1486
1667
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1487
1668
|
}
|
|
1488
|
-
function buildExecuteOptions(options) {
|
|
1669
|
+
function buildExecuteOptions(options, client) {
|
|
1489
1670
|
const execute = {};
|
|
1671
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1672
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1490
1673
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1491
|
-
if (
|
|
1674
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1492
1675
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1493
|
-
if (
|
|
1676
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1494
1677
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1495
1678
|
return execute;
|
|
1496
1679
|
}
|
|
@@ -1547,41 +1730,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1547
1730
|
return `${source}->${destination}`;
|
|
1548
1731
|
}
|
|
1549
1732
|
|
|
1550
|
-
// src/logging/redaction.ts
|
|
1551
|
-
var REDACTED = "[REDACTED]";
|
|
1552
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1553
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1554
|
-
function isSensitiveKey(key) {
|
|
1555
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1556
|
-
}
|
|
1557
|
-
function redactCommand(command) {
|
|
1558
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1559
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
function redactValue(value) {
|
|
1563
|
-
if (typeof value === "string") {
|
|
1564
|
-
return redactCommand(value);
|
|
1565
|
-
}
|
|
1566
|
-
if (Array.isArray(value)) {
|
|
1567
|
-
return value.map((item) => redactValue(item));
|
|
1568
|
-
}
|
|
1569
|
-
if (value !== null && typeof value === "object") {
|
|
1570
|
-
return redactObject(value);
|
|
1571
|
-
}
|
|
1572
|
-
return value;
|
|
1573
|
-
}
|
|
1574
|
-
function redactObject(input) {
|
|
1575
|
-
return Object.fromEntries(
|
|
1576
|
-
Object.entries(input).map(([key, value]) => {
|
|
1577
|
-
if (isSensitiveKey(key)) {
|
|
1578
|
-
return [key, REDACTED];
|
|
1579
|
-
}
|
|
1580
|
-
return [key, redactValue(value)];
|
|
1581
|
-
})
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
1733
|
// src/profiles/SecretSource.ts
|
|
1586
1734
|
import { Buffer as Buffer3 } from "buffer";
|
|
1587
1735
|
import { readFile } from "fs/promises";
|
|
@@ -2005,11 +2153,11 @@ async function resolveTlsSecretSource(source, options) {
|
|
|
2005
2153
|
}
|
|
2006
2154
|
|
|
2007
2155
|
// src/utils/path.ts
|
|
2008
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2156
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2009
2157
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2010
2158
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2011
2159
|
throw new ConfigurationError({
|
|
2012
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2160
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2013
2161
|
retryable: false,
|
|
2014
2162
|
details: {
|
|
2015
2163
|
label
|
|
@@ -2971,38 +3119,53 @@ async function expectCompletion(control, command, path2) {
|
|
|
2971
3119
|
const response = await control.sendCommand(command);
|
|
2972
3120
|
assertPathCommandSucceeded(response, command, path2, control.providerId);
|
|
2973
3121
|
}
|
|
2974
|
-
async function
|
|
2975
|
-
const dataConnection = await openPassiveDataCommand(control, command, path2
|
|
3122
|
+
async function readPassiveLinesCommand(control, command, path2, onLine) {
|
|
3123
|
+
const dataConnection = await openPassiveDataCommand(control, command, path2);
|
|
2976
3124
|
try {
|
|
2977
|
-
const
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
path2,
|
|
2981
|
-
control.providerId
|
|
2982
|
-
);
|
|
3125
|
+
const failure = await consumePassiveLines(dataConnection, control.operationTimeoutMs, {
|
|
3126
|
+
command,
|
|
3127
|
+
onLine,
|
|
3128
|
+
path: path2,
|
|
3129
|
+
providerId: control.providerId
|
|
3130
|
+
});
|
|
2983
3131
|
const finalResponse = await control.readFinalResponse({
|
|
2984
3132
|
command,
|
|
2985
3133
|
operation: "data command completion",
|
|
2986
3134
|
path: path2
|
|
2987
3135
|
});
|
|
2988
3136
|
assertPathCommandSucceeded(finalResponse, command, path2, control.providerId);
|
|
2989
|
-
|
|
3137
|
+
if (failure !== void 0) throw failure;
|
|
2990
3138
|
} catch (error) {
|
|
2991
3139
|
dataConnection.close();
|
|
2992
3140
|
throw error;
|
|
2993
3141
|
}
|
|
2994
3142
|
}
|
|
2995
3143
|
async function readDirectoryEntries(control, path2) {
|
|
3144
|
+
const entries = [];
|
|
3145
|
+
const collectEntry = (entry) => {
|
|
3146
|
+
if (entry.name === "." || entry.name === "..") return;
|
|
3147
|
+
entries.push(entry);
|
|
3148
|
+
};
|
|
2996
3149
|
try {
|
|
2997
|
-
|
|
2998
|
-
|
|
3150
|
+
await readPassiveLinesCommand(control, `MLSD ${path2}`, path2, (rawLine) => {
|
|
3151
|
+
const line = rawLine.trimEnd();
|
|
3152
|
+
if (line.length === 0) return;
|
|
3153
|
+
collectEntry(parseMlsdLine(line, path2));
|
|
3154
|
+
});
|
|
3155
|
+
return entries;
|
|
2999
3156
|
} catch (error) {
|
|
3000
3157
|
if (!isUnsupportedFtpCommandError(error, "MLSD")) {
|
|
3001
3158
|
throw error;
|
|
3002
3159
|
}
|
|
3003
3160
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3161
|
+
entries.length = 0;
|
|
3162
|
+
const now = /* @__PURE__ */ new Date();
|
|
3163
|
+
await readPassiveLinesCommand(control, `LIST ${path2}`, path2, (rawLine) => {
|
|
3164
|
+
const line = rawLine.trimEnd();
|
|
3165
|
+
if (line.length === 0 || line.toLowerCase().startsWith("total ")) return;
|
|
3166
|
+
collectEntry(parseUnixListLine(line, path2, now));
|
|
3167
|
+
});
|
|
3168
|
+
return entries;
|
|
3006
3169
|
}
|
|
3007
3170
|
async function openPassiveDataCommand(control, command, path2, options = {}) {
|
|
3008
3171
|
const offset = normalizeOptionalByteCount(options.offset, "offset", path2);
|
|
@@ -3175,22 +3338,58 @@ function openPassiveDataConnection(endpoint, timeoutMs, path2, control) {
|
|
|
3175
3338
|
}
|
|
3176
3339
|
};
|
|
3177
3340
|
}
|
|
3178
|
-
|
|
3179
|
-
|
|
3341
|
+
var MAX_LIST_LINE_BYTES = 64 * 1024;
|
|
3342
|
+
async function consumePassiveLines(dataConnection, timeoutMs, input) {
|
|
3343
|
+
let carry = Buffer4.alloc(0);
|
|
3344
|
+
let failure;
|
|
3180
3345
|
const clearIdleTimeout = setSocketTimeout(dataConnection.socket, timeoutMs, {
|
|
3181
3346
|
host: dataConnection.endpoint.host,
|
|
3182
3347
|
operation: "passive data transfer",
|
|
3183
|
-
path:
|
|
3184
|
-
providerId
|
|
3348
|
+
path: input.path,
|
|
3349
|
+
providerId: input.providerId
|
|
3185
3350
|
});
|
|
3351
|
+
const overlongLineFailure = () => new ParseError({
|
|
3352
|
+
details: { command: input.command, limitBytes: MAX_LIST_LINE_BYTES, path: input.path },
|
|
3353
|
+
message: `FTP listing line exceeded ${String(MAX_LIST_LINE_BYTES)} bytes for ${input.command}`,
|
|
3354
|
+
retryable: false
|
|
3355
|
+
});
|
|
3356
|
+
const emit = (lineBytes) => {
|
|
3357
|
+
if (failure !== void 0) return;
|
|
3358
|
+
let end = lineBytes.length;
|
|
3359
|
+
if (end > 0 && lineBytes[end - 1] === 13) end -= 1;
|
|
3360
|
+
if (end === 0) return;
|
|
3361
|
+
if (end > MAX_LIST_LINE_BYTES) {
|
|
3362
|
+
failure = overlongLineFailure();
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
try {
|
|
3366
|
+
input.onLine(lineBytes.toString("utf8", 0, end));
|
|
3367
|
+
} catch (error) {
|
|
3368
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3186
3371
|
try {
|
|
3187
3372
|
for await (const chunk of dataConnection.socket) {
|
|
3188
|
-
|
|
3373
|
+
if (failure !== void 0) continue;
|
|
3374
|
+
const data = carry.length > 0 ? Buffer4.concat([carry, chunk]) : chunk;
|
|
3375
|
+
let start = 0;
|
|
3376
|
+
let newline = data.indexOf(10, start);
|
|
3377
|
+
while (newline !== -1) {
|
|
3378
|
+
emit(data.subarray(start, newline));
|
|
3379
|
+
start = newline + 1;
|
|
3380
|
+
newline = data.indexOf(10, start);
|
|
3381
|
+
}
|
|
3382
|
+
carry = Buffer4.from(data.subarray(start));
|
|
3383
|
+
if (carry.length > MAX_LIST_LINE_BYTES && failure === void 0) {
|
|
3384
|
+
failure = overlongLineFailure();
|
|
3385
|
+
}
|
|
3386
|
+
if (failure !== void 0) carry = Buffer4.alloc(0);
|
|
3189
3387
|
}
|
|
3388
|
+
if (carry.length > 0) emit(carry);
|
|
3190
3389
|
} finally {
|
|
3191
3390
|
clearIdleTimeout();
|
|
3192
3391
|
}
|
|
3193
|
-
return
|
|
3392
|
+
return failure;
|
|
3194
3393
|
}
|
|
3195
3394
|
async function* createPassiveReadSource(control, dataConnection, command, path2, range, request) {
|
|
3196
3395
|
let bytesEmitted = 0;
|
|
@@ -3439,6 +3638,13 @@ function createTlsPinnedFingerprints(profile) {
|
|
|
3439
3638
|
if (pinnedFingerprint256 === void 0) {
|
|
3440
3639
|
return void 0;
|
|
3441
3640
|
}
|
|
3641
|
+
if (profile.tls?.rejectUnauthorized === false) {
|
|
3642
|
+
throw new ConfigurationError({
|
|
3643
|
+
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.",
|
|
3644
|
+
protocol: FTPS_PROVIDER_ID,
|
|
3645
|
+
retryable: false
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3442
3648
|
const fingerprints = Array.isArray(pinnedFingerprint256) ? pinnedFingerprint256 : [pinnedFingerprint256];
|
|
3443
3649
|
if (fingerprints.length === 0) {
|
|
3444
3650
|
throw new ConfigurationError({
|
|
@@ -5410,6 +5616,7 @@ import { Buffer as Buffer13 } from "buffer";
|
|
|
5410
5616
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5411
5617
|
var MIN_PADDING_LENGTH = 4;
|
|
5412
5618
|
var MIN_PACKET_LENGTH = 1 + MIN_PADDING_LENGTH;
|
|
5619
|
+
var MAX_SSH_PACKET_LENGTH = 256 * 1024;
|
|
5413
5620
|
function encodeSshTransportPacket(payload, options = {}) {
|
|
5414
5621
|
const body = Buffer13.from(payload);
|
|
5415
5622
|
const blockSize = normalizeBlockSize(options.blockSize ?? 8);
|
|
@@ -5487,6 +5694,14 @@ var SshTransportPacketFramer = class {
|
|
|
5487
5694
|
const packets = [];
|
|
5488
5695
|
while (this.pending.length >= 4) {
|
|
5489
5696
|
const packetLength = this.pending.readUInt32BE(0);
|
|
5697
|
+
if (packetLength > MAX_SSH_PACKET_LENGTH) {
|
|
5698
|
+
throw new ParseError({
|
|
5699
|
+
details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
|
|
5700
|
+
message: "SSH transport packet length exceeds the maximum accepted size",
|
|
5701
|
+
protocol: "sftp",
|
|
5702
|
+
retryable: false
|
|
5703
|
+
});
|
|
5704
|
+
}
|
|
5490
5705
|
const frameLength = 4 + packetLength;
|
|
5491
5706
|
if (this.pending.length < frameLength) {
|
|
5492
5707
|
break;
|
|
@@ -5964,14 +6179,35 @@ var SshTransportHandshake = class {
|
|
|
5964
6179
|
return { outbound };
|
|
5965
6180
|
}
|
|
5966
6181
|
};
|
|
6182
|
+
var MAX_IDENTIFICATION_LINE_BYTES = 8192;
|
|
6183
|
+
var MAX_PRE_IDENTIFICATION_LINES = 1024;
|
|
5967
6184
|
var SshIdentificationAccumulator = class {
|
|
5968
6185
|
pending = Buffer15.alloc(0);
|
|
6186
|
+
bannerLineCount = 0;
|
|
5969
6187
|
push(chunk) {
|
|
5970
6188
|
this.pending = Buffer15.concat([this.pending, Buffer15.from(chunk)]);
|
|
5971
6189
|
const bannerLines = [];
|
|
5972
6190
|
while (true) {
|
|
5973
6191
|
const lfIndex = this.pending.indexOf(10);
|
|
5974
|
-
if (lfIndex < 0)
|
|
6192
|
+
if (lfIndex < 0) {
|
|
6193
|
+
if (this.pending.length > MAX_IDENTIFICATION_LINE_BYTES) {
|
|
6194
|
+
throw new ProtocolError({
|
|
6195
|
+
details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
|
|
6196
|
+
message: "SSH identification line exceeds the maximum accepted length",
|
|
6197
|
+
protocol: "sftp",
|
|
6198
|
+
retryable: false
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
break;
|
|
6202
|
+
}
|
|
6203
|
+
if (lfIndex > MAX_IDENTIFICATION_LINE_BYTES) {
|
|
6204
|
+
throw new ProtocolError({
|
|
6205
|
+
details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
|
|
6206
|
+
message: "SSH identification line exceeds the maximum accepted length",
|
|
6207
|
+
protocol: "sftp",
|
|
6208
|
+
retryable: false
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
5975
6211
|
const lineText = trimLineEndings(this.pending.subarray(0, lfIndex + 1).toString("ascii"));
|
|
5976
6212
|
const remainder = Buffer15.from(this.pending.subarray(lfIndex + 1));
|
|
5977
6213
|
this.pending = remainder;
|
|
@@ -5979,6 +6215,15 @@ var SshIdentificationAccumulator = class {
|
|
|
5979
6215
|
this.pending = Buffer15.alloc(0);
|
|
5980
6216
|
return { bannerLines, identLine: lineText, remainder };
|
|
5981
6217
|
}
|
|
6218
|
+
this.bannerLineCount += 1;
|
|
6219
|
+
if (this.bannerLineCount > MAX_PRE_IDENTIFICATION_LINES) {
|
|
6220
|
+
throw new ProtocolError({
|
|
6221
|
+
details: { limitLines: MAX_PRE_IDENTIFICATION_LINES },
|
|
6222
|
+
message: "SSH server sent too many pre-identification banner lines",
|
|
6223
|
+
protocol: "sftp",
|
|
6224
|
+
retryable: false
|
|
6225
|
+
});
|
|
6226
|
+
}
|
|
5982
6227
|
bannerLines.push(lineText);
|
|
5983
6228
|
}
|
|
5984
6229
|
return { bannerLines, remainder: Buffer15.alloc(0) };
|
|
@@ -6116,6 +6361,14 @@ var SshTransportPacketUnprotector = class {
|
|
|
6116
6361
|
this.framePendingRaw = Buffer16.from(this.framePendingRaw.subarray(this.blockLength));
|
|
6117
6362
|
this.framePartialDecrypted = this.decipher ? Buffer16.from(this.decipher.update(firstBlock)) : Buffer16.from(firstBlock);
|
|
6118
6363
|
const packetLength = this.framePartialDecrypted.readUInt32BE(0);
|
|
6364
|
+
if (packetLength > MAX_SSH_PACKET_LENGTH) {
|
|
6365
|
+
throw new ProtocolError({
|
|
6366
|
+
details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
|
|
6367
|
+
message: "SSH encrypted packet length exceeds the maximum accepted size",
|
|
6368
|
+
protocol: "sftp",
|
|
6369
|
+
retryable: false
|
|
6370
|
+
});
|
|
6371
|
+
}
|
|
6119
6372
|
const remaining = 4 + packetLength - this.blockLength + this.macLength;
|
|
6120
6373
|
if (remaining < 0) {
|
|
6121
6374
|
throw new ProtocolError({
|
|
@@ -6739,6 +6992,7 @@ function decodeSftpAttributesFromReader(reader) {
|
|
|
6739
6992
|
|
|
6740
6993
|
// src/protocols/sftp/v3/SftpPacket.ts
|
|
6741
6994
|
import { Buffer as Buffer18 } from "buffer";
|
|
6995
|
+
var MAX_SFTP_PACKET_LENGTH = 256 * 1024;
|
|
6742
6996
|
var SFTP_PACKET_TYPE = {
|
|
6743
6997
|
ATTRS: 105,
|
|
6744
6998
|
CLOSE: 4,
|
|
@@ -6798,6 +7052,13 @@ var SftpPacketFramer = class {
|
|
|
6798
7052
|
const packets = [];
|
|
6799
7053
|
while (this.pending.length >= 4) {
|
|
6800
7054
|
const bodyLength = this.pending.readUInt32BE(0);
|
|
7055
|
+
if (bodyLength > MAX_SFTP_PACKET_LENGTH) {
|
|
7056
|
+
throw new ParseError({
|
|
7057
|
+
details: { bodyLength, maxPacketLength: MAX_SFTP_PACKET_LENGTH },
|
|
7058
|
+
message: "SFTP packet length exceeds the maximum accepted size",
|
|
7059
|
+
retryable: false
|
|
7060
|
+
});
|
|
7061
|
+
}
|
|
6801
7062
|
const frameLength = 4 + bodyLength;
|
|
6802
7063
|
if (this.pending.length < frameLength) {
|
|
6803
7064
|
break;
|
|
@@ -7399,7 +7660,7 @@ function buildNativeSftpCapabilities(maxConcurrency) {
|
|
|
7399
7660
|
var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
|
|
7400
7661
|
NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
7401
7662
|
);
|
|
7402
|
-
function
|
|
7663
|
+
function createSftpProviderFactory(options = {}) {
|
|
7403
7664
|
validateNativeSftpOptions(options);
|
|
7404
7665
|
const capabilities = buildNativeSftpCapabilities(
|
|
7405
7666
|
options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
|
|
@@ -8058,6 +8319,26 @@ function validateNativeSftpOptions(options) {
|
|
|
8058
8319
|
|
|
8059
8320
|
// src/providers/web/httpInternals.ts
|
|
8060
8321
|
import { Buffer as Buffer21 } from "buffer";
|
|
8322
|
+
function assertHttpsEnforced(input) {
|
|
8323
|
+
if (input.enforceHttps && !input.secure) {
|
|
8324
|
+
throw new ConfigurationError({
|
|
8325
|
+
details: { provider: input.providerId },
|
|
8326
|
+
message: `Provider "${input.providerId}" is configured with enforceHttps but its transport is cleartext http; set secure: true (or drop enforceHttps to explicitly accept cleartext)`,
|
|
8327
|
+
retryable: false
|
|
8328
|
+
});
|
|
8329
|
+
}
|
|
8330
|
+
}
|
|
8331
|
+
var cleartextWarnedKeys = /* @__PURE__ */ new Set();
|
|
8332
|
+
function warnCleartextCredentials(input) {
|
|
8333
|
+
if (!input.hasCredentials) return;
|
|
8334
|
+
const key = `${input.providerId}:${input.host}`;
|
|
8335
|
+
if (cleartextWarnedKeys.has(key)) return;
|
|
8336
|
+
cleartextWarnedKeys.add(key);
|
|
8337
|
+
process.emitWarning(
|
|
8338
|
+
`Provider "${input.providerId}" is sending credentials to ${input.host} over cleartext http; use https or set enforceHttps to block this`,
|
|
8339
|
+
{ code: "ZERO_TRANSFER_CLEARTEXT_CREDENTIALS", type: "SecurityWarning" }
|
|
8340
|
+
);
|
|
8341
|
+
}
|
|
8061
8342
|
function buildBaseUrl(profile, options) {
|
|
8062
8343
|
const protocol = options.secure ? "https:" : "http:";
|
|
8063
8344
|
const portSegment = profile.port !== void 0 ? `:${profile.port}` : "";
|
|
@@ -8105,18 +8386,19 @@ async function dispatchRequest(options, url, init) {
|
|
|
8105
8386
|
signal: controller.signal
|
|
8106
8387
|
});
|
|
8107
8388
|
} catch (error) {
|
|
8389
|
+
const safeUrl = redactUrlForLogging(url);
|
|
8108
8390
|
if (controller.signal.aborted && upstreamSignal?.aborted !== true) {
|
|
8109
8391
|
throw new TimeoutError({
|
|
8110
8392
|
cause: error,
|
|
8111
|
-
details: { timeoutMs: options.timeoutMs, url:
|
|
8112
|
-
message: `HTTP request to ${
|
|
8393
|
+
details: { timeoutMs: options.timeoutMs, url: safeUrl },
|
|
8394
|
+
message: `HTTP request to ${safeUrl} timed out after ${String(options.timeoutMs)}ms`,
|
|
8113
8395
|
retryable: true
|
|
8114
8396
|
});
|
|
8115
8397
|
}
|
|
8116
8398
|
throw new ConnectionError({
|
|
8117
8399
|
cause: error,
|
|
8118
|
-
details: { url:
|
|
8119
|
-
message: `HTTP request to ${
|
|
8400
|
+
details: { url: safeUrl },
|
|
8401
|
+
message: `HTTP request to ${safeUrl} failed`,
|
|
8120
8402
|
retryable: true
|
|
8121
8403
|
});
|
|
8122
8404
|
} finally {
|
|
@@ -8148,8 +8430,48 @@ function formatRangeHeader(offset, length) {
|
|
|
8148
8430
|
const end = offset + length - 1;
|
|
8149
8431
|
return `bytes=${String(offset)}-${String(end)}`;
|
|
8150
8432
|
}
|
|
8151
|
-
|
|
8152
|
-
|
|
8433
|
+
var ERROR_BODY_EXCERPT_LIMIT = 2048;
|
|
8434
|
+
async function readErrorBodyExcerpt(response) {
|
|
8435
|
+
try {
|
|
8436
|
+
const text = await response.text();
|
|
8437
|
+
if (text.length === 0) return void 0;
|
|
8438
|
+
return text.length > ERROR_BODY_EXCERPT_LIMIT ? `${text.slice(0, ERROR_BODY_EXCERPT_LIMIT)}... [truncated]` : text;
|
|
8439
|
+
} catch {
|
|
8440
|
+
return void 0;
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
function parseRetryAfterMs(value, now = Date.now) {
|
|
8444
|
+
if (value === null) return void 0;
|
|
8445
|
+
const trimmed = value.trim();
|
|
8446
|
+
if (trimmed.length === 0) return void 0;
|
|
8447
|
+
if (/^\d+$/.test(trimmed)) {
|
|
8448
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
8449
|
+
return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
|
|
8450
|
+
}
|
|
8451
|
+
if (!/[A-Za-z]/.test(trimmed)) return void 0;
|
|
8452
|
+
const retryAt = Date.parse(trimmed);
|
|
8453
|
+
if (Number.isNaN(retryAt)) return void 0;
|
|
8454
|
+
return Math.max(0, retryAt - now());
|
|
8455
|
+
}
|
|
8456
|
+
async function mapResponseErrorWithBody(response, path2) {
|
|
8457
|
+
return mapResponseError(response, path2, await readErrorBodyExcerpt(response));
|
|
8458
|
+
}
|
|
8459
|
+
function mapResponseError(response, path2, bodyExcerpt) {
|
|
8460
|
+
const details = {
|
|
8461
|
+
path: path2,
|
|
8462
|
+
status: response.status,
|
|
8463
|
+
statusText: response.statusText
|
|
8464
|
+
};
|
|
8465
|
+
if (bodyExcerpt !== void 0) details["body"] = bodyExcerpt;
|
|
8466
|
+
if (response.status === 429 || response.status === 503) {
|
|
8467
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
8468
|
+
if (retryAfterMs !== void 0) details["retryAfterMs"] = retryAfterMs;
|
|
8469
|
+
return new ConnectionError({
|
|
8470
|
+
details,
|
|
8471
|
+
message: response.status === 429 ? `HTTP request for ${path2} was rate limited (429)` : `HTTP service unavailable for ${path2} (503)`,
|
|
8472
|
+
retryable: true
|
|
8473
|
+
});
|
|
8474
|
+
}
|
|
8153
8475
|
if (response.status === 401) {
|
|
8154
8476
|
return new AuthenticationError({
|
|
8155
8477
|
details,
|
|
@@ -8181,14 +8503,54 @@ async function* webStreamToAsyncIterable(body) {
|
|
|
8181
8503
|
const reader = body.getReader();
|
|
8182
8504
|
try {
|
|
8183
8505
|
while (true) {
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8506
|
+
let result;
|
|
8507
|
+
try {
|
|
8508
|
+
result = await reader.read();
|
|
8509
|
+
} catch (error) {
|
|
8510
|
+
if (error instanceof ZeroTransferError) throw error;
|
|
8511
|
+
throw new ConnectionError({
|
|
8512
|
+
cause: error,
|
|
8513
|
+
message: "HTTP response stream was interrupted before completion",
|
|
8514
|
+
retryable: true
|
|
8515
|
+
});
|
|
8516
|
+
}
|
|
8517
|
+
if (result.done) break;
|
|
8518
|
+
if (result.value !== void 0) yield result.value;
|
|
8187
8519
|
}
|
|
8188
8520
|
} finally {
|
|
8189
8521
|
reader.releaseLock();
|
|
8190
8522
|
}
|
|
8191
8523
|
}
|
|
8524
|
+
function asyncIterableToReadableStream(source, onChunk) {
|
|
8525
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
8526
|
+
return new ReadableStream({
|
|
8527
|
+
async pull(controller) {
|
|
8528
|
+
try {
|
|
8529
|
+
const next = await iterator.next();
|
|
8530
|
+
if (next.done === true) {
|
|
8531
|
+
controller.close();
|
|
8532
|
+
return;
|
|
8533
|
+
}
|
|
8534
|
+
const chunk = next.value;
|
|
8535
|
+
if (chunk.byteLength === 0) {
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8538
|
+
controller.enqueue(chunk);
|
|
8539
|
+
onChunk(chunk);
|
|
8540
|
+
} catch (error) {
|
|
8541
|
+
controller.error(error);
|
|
8542
|
+
}
|
|
8543
|
+
},
|
|
8544
|
+
async cancel(reason) {
|
|
8545
|
+
if (typeof iterator.return === "function") {
|
|
8546
|
+
try {
|
|
8547
|
+
await iterator.return(reason);
|
|
8548
|
+
} catch {
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
});
|
|
8553
|
+
}
|
|
8192
8554
|
function secretToString2(value) {
|
|
8193
8555
|
if (typeof value === "string") return value;
|
|
8194
8556
|
if (value instanceof Uint8Array || Buffer21.isBuffer(value)) {
|
|
@@ -11797,6 +12159,7 @@ function createHttpProviderFactory(options = {}) {
|
|
|
11797
12159
|
const secure = options.secure ?? id === "https";
|
|
11798
12160
|
const basePath = options.basePath ?? "";
|
|
11799
12161
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
12162
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
11800
12163
|
if (typeof fetchImpl !== "function") {
|
|
11801
12164
|
throw new ConfigurationError({
|
|
11802
12165
|
message: "Global fetch is unavailable; supply HttpProviderOptions.fetch explicitly",
|
|
@@ -11846,6 +12209,13 @@ var HttpProvider = class {
|
|
|
11846
12209
|
id;
|
|
11847
12210
|
capabilities;
|
|
11848
12211
|
async connect(profile) {
|
|
12212
|
+
if (!this.internals.secure) {
|
|
12213
|
+
warnCleartextCredentials({
|
|
12214
|
+
hasCredentials: profile.username !== void 0 || profile.password !== void 0,
|
|
12215
|
+
host: profile.host,
|
|
12216
|
+
providerId: this.internals.id
|
|
12217
|
+
});
|
|
12218
|
+
}
|
|
11849
12219
|
const headers = { ...this.internals.defaultHeaders };
|
|
11850
12220
|
if (profile.username !== void 0) {
|
|
11851
12221
|
const username = await resolveSecret(profile.username);
|
|
@@ -11904,7 +12274,7 @@ var HttpFileSystem = class {
|
|
|
11904
12274
|
method: "HEAD"
|
|
11905
12275
|
});
|
|
11906
12276
|
if (!response.ok) {
|
|
11907
|
-
throw
|
|
12277
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
11908
12278
|
}
|
|
11909
12279
|
return responseToStat(response, normalized);
|
|
11910
12280
|
}
|
|
@@ -11929,12 +12299,12 @@ var HttpTransferOperations = class {
|
|
|
11929
12299
|
if (request.signal !== void 0) requestInit.signal = request.signal;
|
|
11930
12300
|
const response = await dispatchRequest(this.options, url, requestInit);
|
|
11931
12301
|
if (!response.ok && response.status !== 206) {
|
|
11932
|
-
throw
|
|
12302
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
11933
12303
|
}
|
|
11934
12304
|
const body = response.body;
|
|
11935
12305
|
if (body === null) {
|
|
11936
12306
|
throw new ConnectionError({
|
|
11937
|
-
message: `HTTP response had no body for ${url
|
|
12307
|
+
message: `HTTP response had no body for ${redactUrlForLogging(url)}`,
|
|
11938
12308
|
retryable: true
|
|
11939
12309
|
});
|
|
11940
12310
|
}
|
|
@@ -11992,7 +12362,8 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
11992
12362
|
const secure = options.secure ?? false;
|
|
11993
12363
|
const basePath = options.basePath ?? "";
|
|
11994
12364
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
11995
|
-
const uploadStreaming = options.uploadStreaming ?? "
|
|
12365
|
+
const uploadStreaming = options.uploadStreaming ?? "always";
|
|
12366
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
11996
12367
|
if (typeof fetchImpl !== "function") {
|
|
11997
12368
|
throw new ConfigurationError({
|
|
11998
12369
|
message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
|
|
@@ -12044,6 +12415,13 @@ var WebDavProvider = class {
|
|
|
12044
12415
|
id;
|
|
12045
12416
|
capabilities;
|
|
12046
12417
|
async connect(profile) {
|
|
12418
|
+
if (!this.internals.secure) {
|
|
12419
|
+
warnCleartextCredentials({
|
|
12420
|
+
hasCredentials: profile.username !== void 0 || profile.password !== void 0,
|
|
12421
|
+
host: profile.host,
|
|
12422
|
+
providerId: this.internals.id
|
|
12423
|
+
});
|
|
12424
|
+
}
|
|
12047
12425
|
const headers = { ...this.internals.defaultHeaders };
|
|
12048
12426
|
if (profile.username !== void 0) {
|
|
12049
12427
|
const username = await resolveSecret(profile.username);
|
|
@@ -12098,7 +12476,7 @@ var WebDavFileSystem = class {
|
|
|
12098
12476
|
method: "PROPFIND"
|
|
12099
12477
|
});
|
|
12100
12478
|
if (!response.ok && response.status !== 207) {
|
|
12101
|
-
throw
|
|
12479
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12102
12480
|
}
|
|
12103
12481
|
const body = await response.text();
|
|
12104
12482
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12112,7 +12490,7 @@ var WebDavFileSystem = class {
|
|
|
12112
12490
|
method: "PROPFIND"
|
|
12113
12491
|
});
|
|
12114
12492
|
if (!response.ok && response.status !== 207) {
|
|
12115
|
-
throw
|
|
12493
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12116
12494
|
}
|
|
12117
12495
|
const body = await response.text();
|
|
12118
12496
|
const entries = parsePropfindResponses(body, this.options.baseUrl);
|
|
@@ -12157,12 +12535,12 @@ var WebDavTransferOperations = class {
|
|
|
12157
12535
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12158
12536
|
const response = await dispatchRequest(this.options, url, init);
|
|
12159
12537
|
if (!response.ok && response.status !== 206) {
|
|
12160
|
-
throw
|
|
12538
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12161
12539
|
}
|
|
12162
12540
|
const body = response.body;
|
|
12163
12541
|
if (body === null) {
|
|
12164
12542
|
throw new ConnectionError({
|
|
12165
|
-
message: `WebDAV response had no body for ${url
|
|
12543
|
+
message: `WebDAV response had no body for ${redactUrlForLogging(url)}`,
|
|
12166
12544
|
retryable: true
|
|
12167
12545
|
});
|
|
12168
12546
|
}
|
|
@@ -12206,7 +12584,7 @@ var WebDavTransferOperations = class {
|
|
|
12206
12584
|
if (request.signal !== void 0) init2.signal = request.signal;
|
|
12207
12585
|
const response2 = await dispatchRequest(this.options, url, init2);
|
|
12208
12586
|
if (!response2.ok) {
|
|
12209
|
-
throw
|
|
12587
|
+
throw await mapResponseErrorWithBody(response2, normalized);
|
|
12210
12588
|
}
|
|
12211
12589
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
12212
12590
|
const result2 = {
|
|
@@ -12242,7 +12620,7 @@ var WebDavTransferOperations = class {
|
|
|
12242
12620
|
if (request.signal !== void 0) init.signal = request.signal;
|
|
12243
12621
|
const response = await dispatchRequest(this.options, url, init);
|
|
12244
12622
|
if (!response.ok) {
|
|
12245
|
-
throw
|
|
12623
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12246
12624
|
}
|
|
12247
12625
|
const result = {
|
|
12248
12626
|
bytesTransferred,
|
|
@@ -12268,36 +12646,6 @@ async function collectChunks6(source) {
|
|
|
12268
12646
|
}
|
|
12269
12647
|
return out;
|
|
12270
12648
|
}
|
|
12271
|
-
function asyncIterableToReadableStream(source, onChunk) {
|
|
12272
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
12273
|
-
return new ReadableStream({
|
|
12274
|
-
async pull(controller) {
|
|
12275
|
-
try {
|
|
12276
|
-
const next = await iterator.next();
|
|
12277
|
-
if (next.done === true) {
|
|
12278
|
-
controller.close();
|
|
12279
|
-
return;
|
|
12280
|
-
}
|
|
12281
|
-
const chunk = next.value;
|
|
12282
|
-
if (chunk.byteLength === 0) {
|
|
12283
|
-
return;
|
|
12284
|
-
}
|
|
12285
|
-
controller.enqueue(chunk);
|
|
12286
|
-
onChunk(chunk);
|
|
12287
|
-
} catch (error) {
|
|
12288
|
-
controller.error(error);
|
|
12289
|
-
}
|
|
12290
|
-
},
|
|
12291
|
-
async cancel(reason) {
|
|
12292
|
-
if (typeof iterator.return === "function") {
|
|
12293
|
-
try {
|
|
12294
|
-
await iterator.return(reason);
|
|
12295
|
-
} catch {
|
|
12296
|
-
}
|
|
12297
|
-
}
|
|
12298
|
-
}
|
|
12299
|
-
});
|
|
12300
|
-
}
|
|
12301
12649
|
function parsePropfindResponses(xml, baseUrl) {
|
|
12302
12650
|
const entries = [];
|
|
12303
12651
|
const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
|
|
@@ -12390,11 +12738,12 @@ import { join as joinPath4 } from "path";
|
|
|
12390
12738
|
|
|
12391
12739
|
// src/providers/web/awsSigv4.ts
|
|
12392
12740
|
import { createHash as createHash4, createHmac as createHmac3 } from "crypto";
|
|
12741
|
+
var UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
|
12393
12742
|
function signSigV4(input) {
|
|
12394
12743
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
12395
12744
|
const amzDate = formatAmzDate(now);
|
|
12396
12745
|
const dateStamp = amzDate.slice(0, 8);
|
|
12397
|
-
const payloadHash = input.body !== void 0 ? sha256Hex(input.body) : sha256Hex(new Uint8Array());
|
|
12746
|
+
const payloadHash = input.unsignedPayload === true ? UNSIGNED_PAYLOAD : input.body !== void 0 ? sha256Hex(input.body) : sha256Hex(new Uint8Array());
|
|
12398
12747
|
input.headers["host"] = input.url.host;
|
|
12399
12748
|
input.headers["x-amz-date"] = amzDate;
|
|
12400
12749
|
input.headers["x-amz-content-sha256"] = payloadHash;
|
|
@@ -12675,7 +13024,7 @@ var S3FileSystem = class {
|
|
|
12675
13024
|
url.searchParams.set("delimiter", "/");
|
|
12676
13025
|
if (prefix.length > 0) url.searchParams.set("prefix", prefix);
|
|
12677
13026
|
const response = await s3Fetch(this.options, "GET", url);
|
|
12678
|
-
if (!response.ok) throw
|
|
13027
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12679
13028
|
const body = await response.text();
|
|
12680
13029
|
return parseListObjectsV2(body, prefix);
|
|
12681
13030
|
}
|
|
@@ -12683,7 +13032,7 @@ var S3FileSystem = class {
|
|
|
12683
13032
|
const normalized = normalizeRemotePath(path2);
|
|
12684
13033
|
const url = buildObjectUrl(this.options, normalized);
|
|
12685
13034
|
const response = await s3Fetch(this.options, "HEAD", url);
|
|
12686
|
-
if (!response.ok) throw
|
|
13035
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12687
13036
|
const stat = {
|
|
12688
13037
|
exists: true,
|
|
12689
13038
|
name: basenameRemotePath(normalized),
|
|
@@ -12723,12 +13072,12 @@ var S3TransferOperations = class {
|
|
|
12723
13072
|
extraHeaders: headers
|
|
12724
13073
|
});
|
|
12725
13074
|
if (!response.ok && response.status !== 206) {
|
|
12726
|
-
throw
|
|
13075
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
12727
13076
|
}
|
|
12728
13077
|
const body = response.body;
|
|
12729
13078
|
if (body === null) {
|
|
12730
13079
|
throw new ConnectionError({
|
|
12731
|
-
message: `S3 response had no body for ${url
|
|
13080
|
+
message: `S3 response had no body for ${redactUrlForLogging(url)}`,
|
|
12732
13081
|
retryable: true
|
|
12733
13082
|
});
|
|
12734
13083
|
}
|
|
@@ -12764,19 +13113,33 @@ var S3TransferOperations = class {
|
|
|
12764
13113
|
}
|
|
12765
13114
|
return this.writeSingleShot(request, normalized);
|
|
12766
13115
|
}
|
|
13116
|
+
/**
|
|
13117
|
+
* Single PUT upload used when multipart is disabled. Streams the body with
|
|
13118
|
+
* a declared `Content-Length` (signed as `UNSIGNED-PAYLOAD`) when the
|
|
13119
|
+
* caller knows the total size; S3 requires a length up front, so only
|
|
13120
|
+
* unknown-size payloads fall back to buffering the content in memory.
|
|
13121
|
+
*/
|
|
12767
13122
|
async writeSingleShot(request, normalized) {
|
|
12768
13123
|
const url = buildObjectUrl(this.options, normalized);
|
|
12769
|
-
const
|
|
13124
|
+
const totalBytes = request.totalBytes;
|
|
13125
|
+
if (typeof totalBytes !== "number" || totalBytes < 0) {
|
|
13126
|
+
const buffered = await collectChunks7(request.content);
|
|
13127
|
+
return this.singleShotFromBuffer(request, normalized, buffered);
|
|
13128
|
+
}
|
|
13129
|
+
let bytesTransferred = 0;
|
|
13130
|
+
const stream = asyncIterableToReadableStream(request.content, (chunk) => {
|
|
13131
|
+
bytesTransferred += chunk.byteLength;
|
|
13132
|
+
request.reportProgress(bytesTransferred, totalBytes);
|
|
13133
|
+
});
|
|
12770
13134
|
const response = await s3Fetch(this.options, "PUT", url, {
|
|
12771
13135
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12772
|
-
|
|
12773
|
-
|
|
13136
|
+
extraHeaders: { "content-type": "application/octet-stream" },
|
|
13137
|
+
streamBody: { content: stream, contentLength: totalBytes }
|
|
12774
13138
|
});
|
|
12775
|
-
if (!response.ok) throw
|
|
12776
|
-
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
13139
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12777
13140
|
const result = {
|
|
12778
|
-
bytesTransferred
|
|
12779
|
-
totalBytes
|
|
13141
|
+
bytesTransferred,
|
|
13142
|
+
totalBytes
|
|
12780
13143
|
};
|
|
12781
13144
|
const etag = response.headers.get("etag");
|
|
12782
13145
|
if (etag !== null) result.checksum = etag;
|
|
@@ -12840,7 +13203,7 @@ var S3TransferOperations = class {
|
|
|
12840
13203
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
12841
13204
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
12842
13205
|
});
|
|
12843
|
-
if (!initiateResponse.ok) throw
|
|
13206
|
+
if (!initiateResponse.ok) throw await mapResponseErrorWithBody(initiateResponse, normalized);
|
|
12844
13207
|
const initiateBody = await initiateResponse.text();
|
|
12845
13208
|
const initiated = innerText(initiateBody, "UploadId");
|
|
12846
13209
|
if (initiated === void 0 || initiated === "") {
|
|
@@ -12879,7 +13242,7 @@ var S3TransferOperations = class {
|
|
|
12879
13242
|
body: partBytes.bytes
|
|
12880
13243
|
});
|
|
12881
13244
|
if (!partResponse.ok) {
|
|
12882
|
-
throw
|
|
13245
|
+
throw await mapResponseErrorWithBody(partResponse, normalized);
|
|
12883
13246
|
}
|
|
12884
13247
|
const partEtag = partResponse.headers.get("etag");
|
|
12885
13248
|
if (partEtag === null) {
|
|
@@ -12935,7 +13298,7 @@ var S3TransferOperations = class {
|
|
|
12935
13298
|
if (resumeStore === void 0) {
|
|
12936
13299
|
await abortMultipart(this.options, objectUrl, uploadId).catch(() => void 0);
|
|
12937
13300
|
}
|
|
12938
|
-
throw
|
|
13301
|
+
throw await mapResponseErrorWithBody(completeResponse, normalized);
|
|
12939
13302
|
}
|
|
12940
13303
|
if (resumeStore !== void 0) await resumeStore.clear(resumeKey);
|
|
12941
13304
|
const completeBody = await completeResponse.text();
|
|
@@ -12954,7 +13317,7 @@ var S3TransferOperations = class {
|
|
|
12954
13317
|
body: buffered,
|
|
12955
13318
|
extraHeaders: { "content-type": "application/octet-stream" }
|
|
12956
13319
|
});
|
|
12957
|
-
if (!response.ok) throw
|
|
13320
|
+
if (!response.ok) throw await mapResponseErrorWithBody(response, normalized);
|
|
12958
13321
|
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
12959
13322
|
const result = {
|
|
12960
13323
|
bytesTransferred: buffered.byteLength,
|
|
@@ -12972,6 +13335,8 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
12972
13335
|
};
|
|
12973
13336
|
if (fetchOptions.body !== void 0) {
|
|
12974
13337
|
headers["content-length"] = String(fetchOptions.body.byteLength);
|
|
13338
|
+
} else if (fetchOptions.streamBody !== void 0) {
|
|
13339
|
+
headers["content-length"] = String(fetchOptions.streamBody.contentLength);
|
|
12975
13340
|
}
|
|
12976
13341
|
signSigV4({
|
|
12977
13342
|
accessKeyId: options.accessKeyId,
|
|
@@ -12982,10 +13347,16 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
12982
13347
|
service: options.service,
|
|
12983
13348
|
url,
|
|
12984
13349
|
...fetchOptions.body !== void 0 ? { body: fetchOptions.body } : {},
|
|
13350
|
+
...fetchOptions.streamBody !== void 0 ? { unsignedPayload: true } : {},
|
|
12985
13351
|
...options.sessionToken !== void 0 ? { sessionToken: options.sessionToken } : {}
|
|
12986
13352
|
});
|
|
12987
13353
|
const init = { headers, method };
|
|
12988
|
-
if (fetchOptions.body !== void 0)
|
|
13354
|
+
if (fetchOptions.body !== void 0) {
|
|
13355
|
+
init.body = fetchOptions.body;
|
|
13356
|
+
} else if (fetchOptions.streamBody !== void 0) {
|
|
13357
|
+
init.body = fetchOptions.streamBody.content;
|
|
13358
|
+
init.duplex = "half";
|
|
13359
|
+
}
|
|
12989
13360
|
if (fetchOptions.signal !== void 0) init.signal = fetchOptions.signal;
|
|
12990
13361
|
const controller = new AbortController();
|
|
12991
13362
|
const upstreamSignal = init.signal ?? null;
|
|
@@ -13003,10 +13374,11 @@ async function s3Fetch(options, method, url, fetchOptions = {}) {
|
|
|
13003
13374
|
try {
|
|
13004
13375
|
return await options.fetch(url.toString(), { ...init, signal: controller.signal });
|
|
13005
13376
|
} catch (error) {
|
|
13377
|
+
const safeUrl = redactUrlForLogging(url);
|
|
13006
13378
|
throw new ConnectionError({
|
|
13007
13379
|
cause: error,
|
|
13008
|
-
details: { url:
|
|
13009
|
-
message: `S3 request to ${
|
|
13380
|
+
details: { url: safeUrl },
|
|
13381
|
+
message: `S3 request to ${safeUrl} failed`,
|
|
13010
13382
|
retryable: true
|
|
13011
13383
|
});
|
|
13012
13384
|
} finally {
|
|
@@ -13173,7 +13545,7 @@ function getBuiltinCapabilityMatrix() {
|
|
|
13173
13545
|
label: "FTPS"
|
|
13174
13546
|
},
|
|
13175
13547
|
{
|
|
13176
|
-
capabilities:
|
|
13548
|
+
capabilities: createSftpProviderFactory().capabilities,
|
|
13177
13549
|
id: "sftp",
|
|
13178
13550
|
label: "SFTP"
|
|
13179
13551
|
},
|
|
@@ -13474,7 +13846,6 @@ function expandAlgorithms(values) {
|
|
|
13474
13846
|
}
|
|
13475
13847
|
|
|
13476
13848
|
// src/profiles/importers/FileZillaImporter.ts
|
|
13477
|
-
import { Buffer as Buffer26 } from "buffer";
|
|
13478
13849
|
function importFileZillaSites(xml) {
|
|
13479
13850
|
const events = tokenizeXml(xml);
|
|
13480
13851
|
if (events.length === 0) {
|
|
@@ -13490,7 +13861,6 @@ function importFileZillaSites(xml) {
|
|
|
13490
13861
|
const folderNamePending = [];
|
|
13491
13862
|
let inServer = false;
|
|
13492
13863
|
let serverFields = {};
|
|
13493
|
-
let serverPasswordEncoding;
|
|
13494
13864
|
let activeTag;
|
|
13495
13865
|
let captureFolderName = false;
|
|
13496
13866
|
for (const event of events) {
|
|
@@ -13503,13 +13873,9 @@ function importFileZillaSites(xml) {
|
|
|
13503
13873
|
if (event.name === "Server") {
|
|
13504
13874
|
inServer = true;
|
|
13505
13875
|
serverFields = {};
|
|
13506
|
-
serverPasswordEncoding = void 0;
|
|
13507
13876
|
continue;
|
|
13508
13877
|
}
|
|
13509
13878
|
activeTag = event.name;
|
|
13510
|
-
if (event.name === "Pass" && inServer) {
|
|
13511
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
13512
|
-
}
|
|
13513
13879
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
13514
13880
|
captureFolderName = true;
|
|
13515
13881
|
}
|
|
@@ -13535,7 +13901,7 @@ function importFileZillaSites(xml) {
|
|
|
13535
13901
|
}
|
|
13536
13902
|
if (event.name === "Server") {
|
|
13537
13903
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
13538
|
-
const result = buildSiteFromFields(serverFields
|
|
13904
|
+
const result = buildSiteFromFields(serverFields);
|
|
13539
13905
|
if (result.kind === "site") {
|
|
13540
13906
|
sites.push({ ...result.site, folder });
|
|
13541
13907
|
} else {
|
|
@@ -13547,7 +13913,6 @@ function importFileZillaSites(xml) {
|
|
|
13547
13913
|
}
|
|
13548
13914
|
inServer = false;
|
|
13549
13915
|
serverFields = {};
|
|
13550
|
-
serverPasswordEncoding = void 0;
|
|
13551
13916
|
activeTag = void 0;
|
|
13552
13917
|
continue;
|
|
13553
13918
|
}
|
|
@@ -13556,7 +13921,7 @@ function importFileZillaSites(xml) {
|
|
|
13556
13921
|
}
|
|
13557
13922
|
return { sites, skipped };
|
|
13558
13923
|
}
|
|
13559
|
-
function buildSiteFromFields(fields
|
|
13924
|
+
function buildSiteFromFields(fields) {
|
|
13560
13925
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
13561
13926
|
const host = (fields["Host"] ?? "").trim();
|
|
13562
13927
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -13575,18 +13940,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
13575
13940
|
}
|
|
13576
13941
|
const user = fields["User"]?.trim();
|
|
13577
13942
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
13578
|
-
let password;
|
|
13579
13943
|
const rawPass = fields["Pass"];
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
password = Buffer26.from(rawPass, "base64").toString("utf8");
|
|
13583
|
-
} else {
|
|
13584
|
-
password = rawPass;
|
|
13585
|
-
}
|
|
13586
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
13587
|
-
}
|
|
13588
|
-
const site = { name, profile };
|
|
13589
|
-
if (password !== void 0) site.password = password;
|
|
13944
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
13945
|
+
const site = { hasStoredPassword, name, profile };
|
|
13590
13946
|
const logonText = fields["Logontype"];
|
|
13591
13947
|
if (logonText !== void 0) {
|
|
13592
13948
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -13907,6 +14263,62 @@ function openTcpSocket(host, port, timeoutMs) {
|
|
|
13907
14263
|
});
|
|
13908
14264
|
}
|
|
13909
14265
|
|
|
14266
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
14267
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
14268
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
14269
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
14270
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
14271
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
14272
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
14273
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
14274
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
14275
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
14276
|
+
const random = options.random ?? Math.random;
|
|
14277
|
+
return {
|
|
14278
|
+
getDelayMs(input) {
|
|
14279
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
14280
|
+
if (retryAfterMs !== void 0) {
|
|
14281
|
+
return retryAfterMs;
|
|
14282
|
+
}
|
|
14283
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
14284
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
14285
|
+
return Math.floor(random() * cappedMs);
|
|
14286
|
+
},
|
|
14287
|
+
maxAttempts,
|
|
14288
|
+
shouldRetry(input) {
|
|
14289
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
14290
|
+
return false;
|
|
14291
|
+
}
|
|
14292
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
14293
|
+
return false;
|
|
14294
|
+
}
|
|
14295
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
14296
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
14297
|
+
return false;
|
|
14298
|
+
}
|
|
14299
|
+
return true;
|
|
14300
|
+
}
|
|
14301
|
+
};
|
|
14302
|
+
}
|
|
14303
|
+
function readRetryAfterMs(error) {
|
|
14304
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
14305
|
+
const value = error.details?.["retryAfterMs"];
|
|
14306
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
14307
|
+
return Math.floor(value);
|
|
14308
|
+
}
|
|
14309
|
+
function normalizePositiveInteger(value, fallback) {
|
|
14310
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
14311
|
+
return fallback;
|
|
14312
|
+
}
|
|
14313
|
+
return Math.floor(value);
|
|
14314
|
+
}
|
|
14315
|
+
function normalizeNonNegative(value, fallback) {
|
|
14316
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
14317
|
+
return fallback;
|
|
14318
|
+
}
|
|
14319
|
+
return Math.floor(value);
|
|
14320
|
+
}
|
|
14321
|
+
|
|
13910
14322
|
// src/transfers/TransferPlan.ts
|
|
13911
14323
|
function createTransferPlan(input) {
|
|
13912
14324
|
const plan = {
|
|
@@ -14004,8 +14416,8 @@ var TransferQueue = class {
|
|
|
14004
14416
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
14005
14417
|
this.defaultExecutor = options.executor;
|
|
14006
14418
|
this.resolveExecutor = options.resolveExecutor;
|
|
14007
|
-
this.retry = options.retry;
|
|
14008
|
-
this.timeout = options.timeout;
|
|
14419
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
14420
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
14009
14421
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
14010
14422
|
this.onProgress = options.onProgress;
|
|
14011
14423
|
this.onReceipt = options.onReceipt;
|
|
@@ -15405,6 +15817,7 @@ async function dispatchWebhook(options) {
|
|
|
15405
15817
|
return { attempts: attempt, delivered: false, status: lastStatus };
|
|
15406
15818
|
}
|
|
15407
15819
|
function createWebhookAuditLog(options) {
|
|
15820
|
+
validateTarget(options.target);
|
|
15408
15821
|
return {
|
|
15409
15822
|
list: () => Promise.resolve([]),
|
|
15410
15823
|
record: async (entry) => {
|
|
@@ -15427,6 +15840,24 @@ function validateTarget(target) {
|
|
|
15427
15840
|
retryable: false
|
|
15428
15841
|
});
|
|
15429
15842
|
}
|
|
15843
|
+
let parsed;
|
|
15844
|
+
try {
|
|
15845
|
+
parsed = new URL(target.url);
|
|
15846
|
+
} catch (error) {
|
|
15847
|
+
throw new ConfigurationError({
|
|
15848
|
+
cause: error,
|
|
15849
|
+
details: { url: target.url },
|
|
15850
|
+
message: "Webhook target url must be an absolute URL",
|
|
15851
|
+
retryable: false
|
|
15852
|
+
});
|
|
15853
|
+
}
|
|
15854
|
+
if (parsed.protocol === "https:") return;
|
|
15855
|
+
if (parsed.protocol === "http:" && target.allowInsecureUrl === true) return;
|
|
15856
|
+
throw new ConfigurationError({
|
|
15857
|
+
details: { protocol: parsed.protocol, url: target.url },
|
|
15858
|
+
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}"`,
|
|
15859
|
+
retryable: false
|
|
15860
|
+
});
|
|
15430
15861
|
}
|
|
15431
15862
|
function normalizeRetry(retry) {
|
|
15432
15863
|
return {
|
|
@@ -15463,6 +15894,26 @@ var ApprovalRejectedError = class _ApprovalRejectedError extends ZeroTransferErr
|
|
|
15463
15894
|
}
|
|
15464
15895
|
request;
|
|
15465
15896
|
};
|
|
15897
|
+
var ApprovalTimeoutError = class _ApprovalTimeoutError extends ZeroTransferError {
|
|
15898
|
+
/**
|
|
15899
|
+
* Creates an approval timeout error.
|
|
15900
|
+
*
|
|
15901
|
+
* @param request - The approval request that timed out while pending.
|
|
15902
|
+
* @param timeoutMs - Configured timeout window in milliseconds.
|
|
15903
|
+
*/
|
|
15904
|
+
constructor(request, timeoutMs) {
|
|
15905
|
+
super({
|
|
15906
|
+
code: "approval_timeout",
|
|
15907
|
+
details: { approvalId: request.id, routeId: request.routeId, timeoutMs },
|
|
15908
|
+
message: `Approval "${request.id}" for route "${request.routeId}" timed out after ${String(timeoutMs)}ms`,
|
|
15909
|
+
retryable: false
|
|
15910
|
+
});
|
|
15911
|
+
this.request = request;
|
|
15912
|
+
Object.setPrototypeOf(this, _ApprovalTimeoutError.prototype);
|
|
15913
|
+
this.name = "ApprovalTimeoutError";
|
|
15914
|
+
}
|
|
15915
|
+
request;
|
|
15916
|
+
};
|
|
15466
15917
|
var ApprovalRegistry = class {
|
|
15467
15918
|
requests = /* @__PURE__ */ new Map();
|
|
15468
15919
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -15588,9 +16039,27 @@ function createApprovalGate(options) {
|
|
|
15588
16039
|
};
|
|
15589
16040
|
if (input.signal.aborted) onAbort();
|
|
15590
16041
|
input.signal.addEventListener("abort", onAbort);
|
|
16042
|
+
let timeoutTimer;
|
|
16043
|
+
const timeoutMs = options.timeoutMs;
|
|
16044
|
+
const pendingPromises = [settled];
|
|
16045
|
+
if (timeoutMs !== void 0) {
|
|
16046
|
+
pendingPromises.push(
|
|
16047
|
+
new Promise((_resolve, reject) => {
|
|
16048
|
+
timeoutTimer = setTimeout(() => {
|
|
16049
|
+
const current = options.registry.get(approvalId) ?? request;
|
|
16050
|
+
reject(new ApprovalTimeoutError(current, timeoutMs));
|
|
16051
|
+
if (current.status === "pending") {
|
|
16052
|
+
settled.catch(() => void 0);
|
|
16053
|
+
options.registry.reject(approvalId, { reason: "timeout" }, now());
|
|
16054
|
+
}
|
|
16055
|
+
}, timeoutMs);
|
|
16056
|
+
})
|
|
16057
|
+
);
|
|
16058
|
+
}
|
|
15591
16059
|
try {
|
|
15592
|
-
await
|
|
16060
|
+
await Promise.race(pendingPromises);
|
|
15593
16061
|
} finally {
|
|
16062
|
+
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
15594
16063
|
input.signal.removeEventListener("abort", onAbort);
|
|
15595
16064
|
}
|
|
15596
16065
|
return options.runner(input);
|
|
@@ -15962,6 +16431,7 @@ export {
|
|
|
15962
16431
|
AbortError,
|
|
15963
16432
|
ApprovalRegistry,
|
|
15964
16433
|
ApprovalRejectedError,
|
|
16434
|
+
ApprovalTimeoutError,
|
|
15965
16435
|
AuthenticationError,
|
|
15966
16436
|
AuthorizationError,
|
|
15967
16437
|
CLASSIC_PROVIDER_IDS,
|
|
@@ -16011,6 +16481,7 @@ export {
|
|
|
16011
16481
|
createAtomicDeployPlan,
|
|
16012
16482
|
createAzureBlobProviderFactory,
|
|
16013
16483
|
createBandwidthThrottle,
|
|
16484
|
+
createDefaultRetryPolicy,
|
|
16014
16485
|
createDropboxProviderFactory,
|
|
16015
16486
|
createFileSystemS3MultipartResumeStore,
|
|
16016
16487
|
createFtpProviderFactory,
|
|
@@ -16023,7 +16494,6 @@ export {
|
|
|
16023
16494
|
createLocalProviderFactory,
|
|
16024
16495
|
createMemoryProviderFactory,
|
|
16025
16496
|
createMemoryS3MultipartResumeStore,
|
|
16026
|
-
createNativeSftpProviderFactory,
|
|
16027
16497
|
createOAuthTokenSecretSource,
|
|
16028
16498
|
createOneDriveProviderFactory,
|
|
16029
16499
|
createOutboxRoute,
|
|
@@ -16033,7 +16503,7 @@ export {
|
|
|
16033
16503
|
createRemoteBrowser,
|
|
16034
16504
|
createRemoteManifest,
|
|
16035
16505
|
createS3ProviderFactory,
|
|
16036
|
-
|
|
16506
|
+
createSftpProviderFactory,
|
|
16037
16507
|
createSyncPlan,
|
|
16038
16508
|
createTransferClient,
|
|
16039
16509
|
createTransferJobsFromPlan,
|
|
@@ -16081,8 +16551,10 @@ export {
|
|
|
16081
16551
|
parseUnixListLine,
|
|
16082
16552
|
redactCommand,
|
|
16083
16553
|
redactConnectionProfile,
|
|
16554
|
+
redactErrorForLogging,
|
|
16084
16555
|
redactObject,
|
|
16085
16556
|
redactSecretSource,
|
|
16557
|
+
redactUrlForLogging,
|
|
16086
16558
|
redactValue,
|
|
16087
16559
|
resolveConnectionProfileSecrets,
|
|
16088
16560
|
resolveOpenSshHost,
|