@zero-transfer/http 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/index.cjs +362 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +234 -24
- package/dist/index.d.ts +234 -24
- package/dist/index.mjs +359 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(http_exports, {
|
|
|
60
60
|
copyBetween: () => copyBetween,
|
|
61
61
|
createAtomicDeployPlan: () => createAtomicDeployPlan,
|
|
62
62
|
createBandwidthThrottle: () => createBandwidthThrottle,
|
|
63
|
+
createDefaultRetryPolicy: () => createDefaultRetryPolicy,
|
|
63
64
|
createHttpProviderFactory: () => createHttpProviderFactory,
|
|
64
65
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
65
66
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
@@ -96,8 +97,10 @@ __export(http_exports, {
|
|
|
96
97
|
parseRemoteManifest: () => parseRemoteManifest,
|
|
97
98
|
redactCommand: () => redactCommand,
|
|
98
99
|
redactConnectionProfile: () => redactConnectionProfile,
|
|
100
|
+
redactErrorForLogging: () => redactErrorForLogging,
|
|
99
101
|
redactObject: () => redactObject,
|
|
100
102
|
redactSecretSource: () => redactSecretSource,
|
|
103
|
+
redactUrlForLogging: () => redactUrlForLogging,
|
|
101
104
|
redactValue: () => redactValue,
|
|
102
105
|
resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
|
|
103
106
|
resolveOpenSshHost: () => resolveOpenSshHost,
|
|
@@ -118,6 +121,68 @@ module.exports = __toCommonJS(http_exports);
|
|
|
118
121
|
// src/client/ZeroTransfer.ts
|
|
119
122
|
var import_node_events = require("events");
|
|
120
123
|
|
|
124
|
+
// src/logging/redaction.ts
|
|
125
|
+
var REDACTED = "[REDACTED]";
|
|
126
|
+
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
127
|
+
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
128
|
+
var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
|
|
129
|
+
function isSensitiveKey(key) {
|
|
130
|
+
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
131
|
+
}
|
|
132
|
+
function redactCommand(command) {
|
|
133
|
+
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
134
|
+
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function redactValue(value) {
|
|
138
|
+
if (typeof value === "string") {
|
|
139
|
+
return redactCommand(value);
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return value.map((item) => redactValue(item));
|
|
143
|
+
}
|
|
144
|
+
if (value !== null && typeof value === "object") {
|
|
145
|
+
return redactObject(value);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
function redactObject(input) {
|
|
150
|
+
return Object.fromEntries(
|
|
151
|
+
Object.entries(input).map(([key, value]) => {
|
|
152
|
+
if (isSensitiveKey(key)) {
|
|
153
|
+
return [key, REDACTED];
|
|
154
|
+
}
|
|
155
|
+
if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
|
|
156
|
+
return [key, redactUrlForLogging(value)];
|
|
157
|
+
}
|
|
158
|
+
return [key, redactValue(value)];
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function redactUrlForLogging(url) {
|
|
163
|
+
let parsed;
|
|
164
|
+
try {
|
|
165
|
+
parsed = typeof url === "string" ? new URL(url) : url;
|
|
166
|
+
} catch {
|
|
167
|
+
return REDACTED;
|
|
168
|
+
}
|
|
169
|
+
const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
|
|
170
|
+
const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
|
|
171
|
+
return `${origin}${parsed.pathname}${query}`;
|
|
172
|
+
}
|
|
173
|
+
function redactErrorForLogging(error) {
|
|
174
|
+
if (error !== null && typeof error === "object") {
|
|
175
|
+
const candidate = error;
|
|
176
|
+
if (typeof candidate.toJSON === "function") {
|
|
177
|
+
return redactObject(candidate.toJSON());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (error instanceof Error) {
|
|
181
|
+
return redactObject({ message: error.message, name: error.name });
|
|
182
|
+
}
|
|
183
|
+
return { message: redactValue(typeof error === "string" ? error : String(error)) };
|
|
184
|
+
}
|
|
185
|
+
|
|
121
186
|
// src/errors/ZeroTransferError.ts
|
|
122
187
|
var ZeroTransferError = class extends Error {
|
|
123
188
|
/** Stable machine-readable error code. */
|
|
@@ -159,6 +224,11 @@ var ZeroTransferError = class extends Error {
|
|
|
159
224
|
/**
|
|
160
225
|
* Serializes the error into a plain object suitable for logs or API responses.
|
|
161
226
|
*
|
|
227
|
+
* `details` and `command` are passed through secret redaction so serialized
|
|
228
|
+
* errors never leak credentials, signed URLs, or raw protocol commands. The
|
|
229
|
+
* live {@link ZeroTransferError.details | details} property stays unredacted
|
|
230
|
+
* for programmatic consumers.
|
|
231
|
+
*
|
|
162
232
|
* @returns A JSON-safe object containing public structured error fields.
|
|
163
233
|
*/
|
|
164
234
|
toJSON() {
|
|
@@ -168,12 +238,12 @@ var ZeroTransferError = class extends Error {
|
|
|
168
238
|
message: this.message,
|
|
169
239
|
protocol: this.protocol,
|
|
170
240
|
host: this.host,
|
|
171
|
-
command: this.command,
|
|
241
|
+
command: this.command === void 0 ? void 0 : redactCommand(this.command),
|
|
172
242
|
ftpCode: this.ftpCode,
|
|
173
243
|
sftpCode: this.sftpCode,
|
|
174
244
|
path: this.path,
|
|
175
245
|
retryable: this.retryable,
|
|
176
|
-
details: this.details
|
|
246
|
+
details: this.details === void 0 ? void 0 : redactObject(this.details)
|
|
177
247
|
};
|
|
178
248
|
}
|
|
179
249
|
};
|
|
@@ -696,15 +766,20 @@ var ProviderRegistry = class {
|
|
|
696
766
|
var TransferClient = class {
|
|
697
767
|
/** Provider registry used by this client. */
|
|
698
768
|
registry;
|
|
769
|
+
/** Execution defaults applied when call sites omit their own values. */
|
|
770
|
+
defaults;
|
|
699
771
|
logger;
|
|
700
772
|
/**
|
|
701
773
|
* Creates a transfer client without opening any provider connections.
|
|
702
774
|
*
|
|
703
|
-
* @param options - Optional registry, provider factories, and
|
|
775
|
+
* @param options - Optional registry, provider factories, logger, and execution defaults.
|
|
704
776
|
*/
|
|
705
777
|
constructor(options = {}) {
|
|
706
778
|
this.registry = options.registry ?? new ProviderRegistry();
|
|
707
779
|
this.logger = options.logger ?? noopLogger;
|
|
780
|
+
if (options.defaults !== void 0) {
|
|
781
|
+
this.defaults = { ...options.defaults };
|
|
782
|
+
}
|
|
708
783
|
for (const provider of options.providers ?? []) {
|
|
709
784
|
this.registry.register(provider);
|
|
710
785
|
}
|
|
@@ -1277,18 +1352,25 @@ var TransferEngine = class {
|
|
|
1277
1352
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
1278
1353
|
this.throwIfAborted(abortScope.signal, job);
|
|
1279
1354
|
const attemptStartedAt = this.now();
|
|
1355
|
+
const attemptScope = createAttemptScope(
|
|
1356
|
+
abortScope.signal,
|
|
1357
|
+
options.timeout,
|
|
1358
|
+
job,
|
|
1359
|
+
attemptNumber
|
|
1360
|
+
);
|
|
1280
1361
|
const context = this.createExecutionContext(
|
|
1281
1362
|
job,
|
|
1282
1363
|
attemptNumber,
|
|
1283
1364
|
attemptStartedAt,
|
|
1284
1365
|
options,
|
|
1285
|
-
|
|
1366
|
+
attemptScope.signal,
|
|
1286
1367
|
(bytesTransferred) => {
|
|
1287
1368
|
latestBytesTransferred = bytesTransferred;
|
|
1288
|
-
}
|
|
1369
|
+
},
|
|
1370
|
+
attemptScope.notifyProgress
|
|
1289
1371
|
);
|
|
1290
1372
|
try {
|
|
1291
|
-
const result = await runExecutor(executor, context,
|
|
1373
|
+
const result = await runExecutor(executor, context, attemptScope.signal, job);
|
|
1292
1374
|
context.throwIfAborted();
|
|
1293
1375
|
latestBytesTransferred = result.bytesTransferred;
|
|
1294
1376
|
const completedAt = this.now();
|
|
@@ -1306,16 +1388,27 @@ var TransferEngine = class {
|
|
|
1306
1388
|
summarizeError(error)
|
|
1307
1389
|
);
|
|
1308
1390
|
attempts.push(attempt);
|
|
1309
|
-
if (error instanceof AbortError ||
|
|
1391
|
+
if (error instanceof AbortError || abortScope.signal?.aborted === true) {
|
|
1310
1392
|
throw error;
|
|
1311
1393
|
}
|
|
1312
|
-
const retryInput = {
|
|
1394
|
+
const retryInput = {
|
|
1395
|
+
attempt: attemptNumber,
|
|
1396
|
+
elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
|
|
1397
|
+
error,
|
|
1398
|
+
job
|
|
1399
|
+
};
|
|
1313
1400
|
const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
|
|
1314
1401
|
if (shouldRetry) {
|
|
1315
1402
|
options.retry?.onRetry?.(retryInput);
|
|
1403
|
+
const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
|
|
1404
|
+
if (delayMs > 0) {
|
|
1405
|
+
await sleepWithAbort(delayMs, abortScope.signal, job);
|
|
1406
|
+
}
|
|
1316
1407
|
continue;
|
|
1317
1408
|
}
|
|
1318
1409
|
throw createTransferFailure(job, error, attempts);
|
|
1410
|
+
} finally {
|
|
1411
|
+
attemptScope.dispose();
|
|
1319
1412
|
}
|
|
1320
1413
|
}
|
|
1321
1414
|
throw createTransferFailure(job, void 0, attempts);
|
|
@@ -1323,12 +1416,13 @@ var TransferEngine = class {
|
|
|
1323
1416
|
abortScope.dispose();
|
|
1324
1417
|
}
|
|
1325
1418
|
}
|
|
1326
|
-
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
|
|
1419
|
+
createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
|
|
1327
1420
|
const context = {
|
|
1328
1421
|
attempt,
|
|
1329
1422
|
job,
|
|
1330
1423
|
reportProgress: (bytesTransferred, totalBytes) => {
|
|
1331
1424
|
this.throwIfAborted(signal, job);
|
|
1425
|
+
notifyProgress();
|
|
1332
1426
|
updateBytesTransferred(bytesTransferred);
|
|
1333
1427
|
const progressInput = {
|
|
1334
1428
|
bytesTransferred,
|
|
@@ -1397,6 +1491,96 @@ function createAbortScope(parentSignal, timeout, job) {
|
|
|
1397
1491
|
signal: controller.signal
|
|
1398
1492
|
};
|
|
1399
1493
|
}
|
|
1494
|
+
function createAttemptScope(parentSignal, timeout, job, attempt) {
|
|
1495
|
+
const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
|
|
1496
|
+
const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
|
|
1497
|
+
if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
|
|
1498
|
+
const scope = {
|
|
1499
|
+
dispose: () => void 0,
|
|
1500
|
+
notifyProgress: () => void 0
|
|
1501
|
+
};
|
|
1502
|
+
if (parentSignal !== void 0) scope.signal = parentSignal;
|
|
1503
|
+
return scope;
|
|
1504
|
+
}
|
|
1505
|
+
const controller = new AbortController();
|
|
1506
|
+
const retryable = timeout?.retryable ?? true;
|
|
1507
|
+
const abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
1508
|
+
if (parentSignal?.aborted === true) {
|
|
1509
|
+
abortFromParent();
|
|
1510
|
+
} else {
|
|
1511
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1512
|
+
}
|
|
1513
|
+
const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
1514
|
+
controller.abort(
|
|
1515
|
+
new TimeoutError({
|
|
1516
|
+
details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
|
|
1517
|
+
message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
|
|
1518
|
+
retryable
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
}, attemptTimeoutMs);
|
|
1522
|
+
let stallTimer;
|
|
1523
|
+
const armStallWatchdog = () => {
|
|
1524
|
+
if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
|
|
1525
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1526
|
+
stallTimer = setTimeout(() => {
|
|
1527
|
+
controller.abort(
|
|
1528
|
+
new TimeoutError({
|
|
1529
|
+
details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
|
|
1530
|
+
message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
|
|
1531
|
+
retryable
|
|
1532
|
+
})
|
|
1533
|
+
);
|
|
1534
|
+
}, stallTimeoutMs);
|
|
1535
|
+
};
|
|
1536
|
+
armStallWatchdog();
|
|
1537
|
+
return {
|
|
1538
|
+
dispose: () => {
|
|
1539
|
+
if (attemptTimer !== void 0) clearTimeout(attemptTimer);
|
|
1540
|
+
if (stallTimer !== void 0) clearTimeout(stallTimer);
|
|
1541
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1542
|
+
},
|
|
1543
|
+
notifyProgress: armStallWatchdog,
|
|
1544
|
+
signal: controller.signal
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function sleepWithAbort(delayMs, signal, job) {
|
|
1548
|
+
return new Promise((resolve, reject) => {
|
|
1549
|
+
if (signal === void 0) {
|
|
1550
|
+
setTimeout(resolve, delayMs);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (signal.aborted) {
|
|
1554
|
+
reject(toAbortFailure(signal, job));
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const rejectAbort = () => {
|
|
1558
|
+
clearTimeout(timer);
|
|
1559
|
+
reject(toAbortFailure(signal, job));
|
|
1560
|
+
};
|
|
1561
|
+
const timer = setTimeout(() => {
|
|
1562
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
1563
|
+
resolve();
|
|
1564
|
+
}, delayMs);
|
|
1565
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
function toAbortFailure(signal, job) {
|
|
1569
|
+
if (signal.reason instanceof ZeroTransferError) {
|
|
1570
|
+
return signal.reason;
|
|
1571
|
+
}
|
|
1572
|
+
return new AbortError({
|
|
1573
|
+
details: { jobId: job.id, operation: job.operation },
|
|
1574
|
+
message: `Transfer job aborted: ${job.id}`,
|
|
1575
|
+
retryable: false
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
function normalizeDelayMs(value) {
|
|
1579
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1580
|
+
return 0;
|
|
1581
|
+
}
|
|
1582
|
+
return Math.floor(value);
|
|
1583
|
+
}
|
|
1400
1584
|
function normalizeTimeoutMs(value) {
|
|
1401
1585
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1402
1586
|
return void 0;
|
|
@@ -1565,7 +1749,7 @@ async function runRoute(options) {
|
|
|
1565
1749
|
const executor = createProviderTransferExecutor({
|
|
1566
1750
|
resolveSession: ({ role }) => sessions.get(role)
|
|
1567
1751
|
});
|
|
1568
|
-
return await engine.execute(job, executor, buildExecuteOptions(options));
|
|
1752
|
+
return await engine.execute(job, executor, buildExecuteOptions(options, client));
|
|
1569
1753
|
} finally {
|
|
1570
1754
|
if (destinationSession !== void 0) {
|
|
1571
1755
|
await destinationSession.disconnect();
|
|
@@ -1602,12 +1786,14 @@ function defaultJobId(route, now) {
|
|
|
1602
1786
|
const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
1603
1787
|
return `route:${route.id}:${timestamp.toString(36)}`;
|
|
1604
1788
|
}
|
|
1605
|
-
function buildExecuteOptions(options) {
|
|
1789
|
+
function buildExecuteOptions(options, client) {
|
|
1606
1790
|
const execute = {};
|
|
1791
|
+
const retry = options.retry ?? client.defaults?.retry;
|
|
1792
|
+
const timeout = options.timeout ?? client.defaults?.timeout;
|
|
1607
1793
|
if (options.signal !== void 0) execute.signal = options.signal;
|
|
1608
|
-
if (
|
|
1794
|
+
if (retry !== void 0) execute.retry = retry;
|
|
1609
1795
|
if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
|
|
1610
|
-
if (
|
|
1796
|
+
if (timeout !== void 0) execute.timeout = timeout;
|
|
1611
1797
|
if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
|
|
1612
1798
|
return execute;
|
|
1613
1799
|
}
|
|
@@ -1664,41 +1850,6 @@ function defaultRouteSuffix(source, destination) {
|
|
|
1664
1850
|
return `${source}->${destination}`;
|
|
1665
1851
|
}
|
|
1666
1852
|
|
|
1667
|
-
// src/logging/redaction.ts
|
|
1668
|
-
var REDACTED = "[REDACTED]";
|
|
1669
|
-
var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
|
|
1670
|
-
var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
|
|
1671
|
-
function isSensitiveKey(key) {
|
|
1672
|
-
return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
|
|
1673
|
-
}
|
|
1674
|
-
function redactCommand(command) {
|
|
1675
|
-
return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
|
|
1676
|
-
return `${commandName.toUpperCase()} ${REDACTED}`;
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
function redactValue(value) {
|
|
1680
|
-
if (typeof value === "string") {
|
|
1681
|
-
return redactCommand(value);
|
|
1682
|
-
}
|
|
1683
|
-
if (Array.isArray(value)) {
|
|
1684
|
-
return value.map((item) => redactValue(item));
|
|
1685
|
-
}
|
|
1686
|
-
if (value !== null && typeof value === "object") {
|
|
1687
|
-
return redactObject(value);
|
|
1688
|
-
}
|
|
1689
|
-
return value;
|
|
1690
|
-
}
|
|
1691
|
-
function redactObject(input) {
|
|
1692
|
-
return Object.fromEntries(
|
|
1693
|
-
Object.entries(input).map(([key, value]) => {
|
|
1694
|
-
if (isSensitiveKey(key)) {
|
|
1695
|
-
return [key, REDACTED];
|
|
1696
|
-
}
|
|
1697
|
-
return [key, redactValue(value)];
|
|
1698
|
-
})
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
1853
|
// src/profiles/SecretSource.ts
|
|
1703
1854
|
var import_node_buffer2 = require("buffer");
|
|
1704
1855
|
var import_promises = require("fs/promises");
|
|
@@ -2070,11 +2221,11 @@ var import_promises2 = require("fs/promises");
|
|
|
2070
2221
|
var import_node_path2 = __toESM(require("path"));
|
|
2071
2222
|
|
|
2072
2223
|
// src/utils/path.ts
|
|
2073
|
-
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
|
|
2224
|
+
var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
|
|
2074
2225
|
function assertSafeFtpArgument(value, label = "path") {
|
|
2075
2226
|
if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
|
|
2076
2227
|
throw new ConfigurationError({
|
|
2077
|
-
message: `Unsafe FTP ${label}: CR and
|
|
2228
|
+
message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
|
|
2078
2229
|
retryable: false,
|
|
2079
2230
|
details: {
|
|
2080
2231
|
label
|
|
@@ -3376,7 +3527,6 @@ function expandAlgorithms(values) {
|
|
|
3376
3527
|
}
|
|
3377
3528
|
|
|
3378
3529
|
// src/profiles/importers/FileZillaImporter.ts
|
|
3379
|
-
var import_node_buffer5 = require("buffer");
|
|
3380
3530
|
function importFileZillaSites(xml) {
|
|
3381
3531
|
const events = tokenizeXml(xml);
|
|
3382
3532
|
if (events.length === 0) {
|
|
@@ -3392,7 +3542,6 @@ function importFileZillaSites(xml) {
|
|
|
3392
3542
|
const folderNamePending = [];
|
|
3393
3543
|
let inServer = false;
|
|
3394
3544
|
let serverFields = {};
|
|
3395
|
-
let serverPasswordEncoding;
|
|
3396
3545
|
let activeTag;
|
|
3397
3546
|
let captureFolderName = false;
|
|
3398
3547
|
for (const event of events) {
|
|
@@ -3405,13 +3554,9 @@ function importFileZillaSites(xml) {
|
|
|
3405
3554
|
if (event.name === "Server") {
|
|
3406
3555
|
inServer = true;
|
|
3407
3556
|
serverFields = {};
|
|
3408
|
-
serverPasswordEncoding = void 0;
|
|
3409
3557
|
continue;
|
|
3410
3558
|
}
|
|
3411
3559
|
activeTag = event.name;
|
|
3412
|
-
if (event.name === "Pass" && inServer) {
|
|
3413
|
-
serverPasswordEncoding = event.attributes["encoding"];
|
|
3414
|
-
}
|
|
3415
3560
|
if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
|
|
3416
3561
|
captureFolderName = true;
|
|
3417
3562
|
}
|
|
@@ -3437,7 +3582,7 @@ function importFileZillaSites(xml) {
|
|
|
3437
3582
|
}
|
|
3438
3583
|
if (event.name === "Server") {
|
|
3439
3584
|
const folder = folderStack.filter((segment) => segment !== "");
|
|
3440
|
-
const result = buildSiteFromFields(serverFields
|
|
3585
|
+
const result = buildSiteFromFields(serverFields);
|
|
3441
3586
|
if (result.kind === "site") {
|
|
3442
3587
|
sites.push({ ...result.site, folder });
|
|
3443
3588
|
} else {
|
|
@@ -3449,7 +3594,6 @@ function importFileZillaSites(xml) {
|
|
|
3449
3594
|
}
|
|
3450
3595
|
inServer = false;
|
|
3451
3596
|
serverFields = {};
|
|
3452
|
-
serverPasswordEncoding = void 0;
|
|
3453
3597
|
activeTag = void 0;
|
|
3454
3598
|
continue;
|
|
3455
3599
|
}
|
|
@@ -3458,7 +3602,7 @@ function importFileZillaSites(xml) {
|
|
|
3458
3602
|
}
|
|
3459
3603
|
return { sites, skipped };
|
|
3460
3604
|
}
|
|
3461
|
-
function buildSiteFromFields(fields
|
|
3605
|
+
function buildSiteFromFields(fields) {
|
|
3462
3606
|
const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
|
|
3463
3607
|
const host = (fields["Host"] ?? "").trim();
|
|
3464
3608
|
if (host === "") return { kind: "skipped", name };
|
|
@@ -3477,18 +3621,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
|
|
|
3477
3621
|
}
|
|
3478
3622
|
const user = fields["User"]?.trim();
|
|
3479
3623
|
if (user !== void 0 && user !== "") profile.username = { value: user };
|
|
3480
|
-
let password;
|
|
3481
3624
|
const rawPass = fields["Pass"];
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
|
|
3485
|
-
} else {
|
|
3486
|
-
password = rawPass;
|
|
3487
|
-
}
|
|
3488
|
-
if (password !== void 0 && password !== "") profile.password = { value: password };
|
|
3489
|
-
}
|
|
3490
|
-
const site = { name, profile };
|
|
3491
|
-
if (password !== void 0) site.password = password;
|
|
3625
|
+
const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
|
|
3626
|
+
const site = { hasStoredPassword, name, profile };
|
|
3492
3627
|
const logonText = fields["Logontype"];
|
|
3493
3628
|
if (logonText !== void 0) {
|
|
3494
3629
|
const logonType = Number.parseInt(logonText.trim(), 10);
|
|
@@ -3731,6 +3866,62 @@ function mapFtp550(details) {
|
|
|
3731
3866
|
return new PermissionDeniedError(details);
|
|
3732
3867
|
}
|
|
3733
3868
|
|
|
3869
|
+
// src/transfers/createDefaultRetryPolicy.ts
|
|
3870
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
3871
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
3872
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3873
|
+
var DEFAULT_MAX_ELAPSED_MS = 3e5;
|
|
3874
|
+
function createDefaultRetryPolicy(options = {}) {
|
|
3875
|
+
const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
3876
|
+
const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
|
|
3877
|
+
const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
|
|
3878
|
+
const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
|
|
3879
|
+
const random = options.random ?? Math.random;
|
|
3880
|
+
return {
|
|
3881
|
+
getDelayMs(input) {
|
|
3882
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3883
|
+
if (retryAfterMs !== void 0) {
|
|
3884
|
+
return retryAfterMs;
|
|
3885
|
+
}
|
|
3886
|
+
const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
|
|
3887
|
+
const cappedMs = Math.min(maxDelayMs, exponentialMs);
|
|
3888
|
+
return Math.floor(random() * cappedMs);
|
|
3889
|
+
},
|
|
3890
|
+
maxAttempts,
|
|
3891
|
+
shouldRetry(input) {
|
|
3892
|
+
if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
|
|
3893
|
+
return false;
|
|
3894
|
+
}
|
|
3895
|
+
if (input.elapsedMs >= maxElapsedMs) {
|
|
3896
|
+
return false;
|
|
3897
|
+
}
|
|
3898
|
+
const retryAfterMs = readRetryAfterMs(input.error);
|
|
3899
|
+
if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
|
|
3900
|
+
return false;
|
|
3901
|
+
}
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
function readRetryAfterMs(error) {
|
|
3907
|
+
if (!(error instanceof ZeroTransferError)) return void 0;
|
|
3908
|
+
const value = error.details?.["retryAfterMs"];
|
|
3909
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
3910
|
+
return Math.floor(value);
|
|
3911
|
+
}
|
|
3912
|
+
function normalizePositiveInteger(value, fallback) {
|
|
3913
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
3914
|
+
return fallback;
|
|
3915
|
+
}
|
|
3916
|
+
return Math.floor(value);
|
|
3917
|
+
}
|
|
3918
|
+
function normalizeNonNegative(value, fallback) {
|
|
3919
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) {
|
|
3920
|
+
return fallback;
|
|
3921
|
+
}
|
|
3922
|
+
return Math.floor(value);
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3734
3925
|
// src/transfers/TransferPlan.ts
|
|
3735
3926
|
function createTransferPlan(input) {
|
|
3736
3927
|
const plan = {
|
|
@@ -3828,8 +4019,8 @@ var TransferQueue = class {
|
|
|
3828
4019
|
this.concurrency = normalizeConcurrency(options.concurrency);
|
|
3829
4020
|
this.defaultExecutor = options.executor;
|
|
3830
4021
|
this.resolveExecutor = options.resolveExecutor;
|
|
3831
|
-
this.retry = options.retry;
|
|
3832
|
-
this.timeout = options.timeout;
|
|
4022
|
+
this.retry = options.retry ?? options.client?.defaults?.retry;
|
|
4023
|
+
this.timeout = options.timeout ?? options.client?.defaults?.timeout;
|
|
3833
4024
|
this.bandwidthLimit = options.bandwidthLimit;
|
|
3834
4025
|
this.onProgress = options.onProgress;
|
|
3835
4026
|
this.onReceipt = options.onReceipt;
|
|
@@ -4906,10 +5097,30 @@ function isMainModule(importMetaUrl) {
|
|
|
4906
5097
|
}
|
|
4907
5098
|
|
|
4908
5099
|
// src/providers/web/HttpProvider.ts
|
|
4909
|
-
var
|
|
5100
|
+
var import_node_buffer6 = require("buffer");
|
|
4910
5101
|
|
|
4911
5102
|
// src/providers/web/httpInternals.ts
|
|
4912
|
-
var
|
|
5103
|
+
var import_node_buffer5 = require("buffer");
|
|
5104
|
+
function assertHttpsEnforced(input) {
|
|
5105
|
+
if (input.enforceHttps && !input.secure) {
|
|
5106
|
+
throw new ConfigurationError({
|
|
5107
|
+
details: { provider: input.providerId },
|
|
5108
|
+
message: `Provider "${input.providerId}" is configured with enforceHttps but its transport is cleartext http; set secure: true (or drop enforceHttps to explicitly accept cleartext)`,
|
|
5109
|
+
retryable: false
|
|
5110
|
+
});
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
var cleartextWarnedKeys = /* @__PURE__ */ new Set();
|
|
5114
|
+
function warnCleartextCredentials(input) {
|
|
5115
|
+
if (!input.hasCredentials) return;
|
|
5116
|
+
const key = `${input.providerId}:${input.host}`;
|
|
5117
|
+
if (cleartextWarnedKeys.has(key)) return;
|
|
5118
|
+
cleartextWarnedKeys.add(key);
|
|
5119
|
+
process.emitWarning(
|
|
5120
|
+
`Provider "${input.providerId}" is sending credentials to ${input.host} over cleartext http; use https or set enforceHttps to block this`,
|
|
5121
|
+
{ code: "ZERO_TRANSFER_CLEARTEXT_CREDENTIALS", type: "SecurityWarning" }
|
|
5122
|
+
);
|
|
5123
|
+
}
|
|
4913
5124
|
function buildBaseUrl(profile, options) {
|
|
4914
5125
|
const protocol = options.secure ? "https:" : "http:";
|
|
4915
5126
|
const portSegment = profile.port !== void 0 ? `:${profile.port}` : "";
|
|
@@ -4957,18 +5168,19 @@ async function dispatchRequest(options, url, init) {
|
|
|
4957
5168
|
signal: controller.signal
|
|
4958
5169
|
});
|
|
4959
5170
|
} catch (error) {
|
|
5171
|
+
const safeUrl = redactUrlForLogging(url);
|
|
4960
5172
|
if (controller.signal.aborted && upstreamSignal?.aborted !== true) {
|
|
4961
5173
|
throw new TimeoutError({
|
|
4962
5174
|
cause: error,
|
|
4963
|
-
details: { timeoutMs: options.timeoutMs, url:
|
|
4964
|
-
message: `HTTP request to ${
|
|
5175
|
+
details: { timeoutMs: options.timeoutMs, url: safeUrl },
|
|
5176
|
+
message: `HTTP request to ${safeUrl} timed out after ${String(options.timeoutMs)}ms`,
|
|
4965
5177
|
retryable: true
|
|
4966
5178
|
});
|
|
4967
5179
|
}
|
|
4968
5180
|
throw new ConnectionError({
|
|
4969
5181
|
cause: error,
|
|
4970
|
-
details: { url:
|
|
4971
|
-
message: `HTTP request to ${
|
|
5182
|
+
details: { url: safeUrl },
|
|
5183
|
+
message: `HTTP request to ${safeUrl} failed`,
|
|
4972
5184
|
retryable: true
|
|
4973
5185
|
});
|
|
4974
5186
|
} finally {
|
|
@@ -5000,8 +5212,48 @@ function formatRangeHeader(offset, length) {
|
|
|
5000
5212
|
const end = offset + length - 1;
|
|
5001
5213
|
return `bytes=${String(offset)}-${String(end)}`;
|
|
5002
5214
|
}
|
|
5003
|
-
|
|
5004
|
-
|
|
5215
|
+
var ERROR_BODY_EXCERPT_LIMIT = 2048;
|
|
5216
|
+
async function readErrorBodyExcerpt(response) {
|
|
5217
|
+
try {
|
|
5218
|
+
const text = await response.text();
|
|
5219
|
+
if (text.length === 0) return void 0;
|
|
5220
|
+
return text.length > ERROR_BODY_EXCERPT_LIMIT ? `${text.slice(0, ERROR_BODY_EXCERPT_LIMIT)}... [truncated]` : text;
|
|
5221
|
+
} catch {
|
|
5222
|
+
return void 0;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
function parseRetryAfterMs(value, now = Date.now) {
|
|
5226
|
+
if (value === null) return void 0;
|
|
5227
|
+
const trimmed = value.trim();
|
|
5228
|
+
if (trimmed.length === 0) return void 0;
|
|
5229
|
+
if (/^\d+$/.test(trimmed)) {
|
|
5230
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
5231
|
+
return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
|
|
5232
|
+
}
|
|
5233
|
+
if (!/[A-Za-z]/.test(trimmed)) return void 0;
|
|
5234
|
+
const retryAt = Date.parse(trimmed);
|
|
5235
|
+
if (Number.isNaN(retryAt)) return void 0;
|
|
5236
|
+
return Math.max(0, retryAt - now());
|
|
5237
|
+
}
|
|
5238
|
+
async function mapResponseErrorWithBody(response, path2) {
|
|
5239
|
+
return mapResponseError(response, path2, await readErrorBodyExcerpt(response));
|
|
5240
|
+
}
|
|
5241
|
+
function mapResponseError(response, path2, bodyExcerpt) {
|
|
5242
|
+
const details = {
|
|
5243
|
+
path: path2,
|
|
5244
|
+
status: response.status,
|
|
5245
|
+
statusText: response.statusText
|
|
5246
|
+
};
|
|
5247
|
+
if (bodyExcerpt !== void 0) details["body"] = bodyExcerpt;
|
|
5248
|
+
if (response.status === 429 || response.status === 503) {
|
|
5249
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
5250
|
+
if (retryAfterMs !== void 0) details["retryAfterMs"] = retryAfterMs;
|
|
5251
|
+
return new ConnectionError({
|
|
5252
|
+
details,
|
|
5253
|
+
message: response.status === 429 ? `HTTP request for ${path2} was rate limited (429)` : `HTTP service unavailable for ${path2} (503)`,
|
|
5254
|
+
retryable: true
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5005
5257
|
if (response.status === 401) {
|
|
5006
5258
|
return new AuthenticationError({
|
|
5007
5259
|
details,
|
|
@@ -5033,9 +5285,19 @@ async function* webStreamToAsyncIterable(body) {
|
|
|
5033
5285
|
const reader = body.getReader();
|
|
5034
5286
|
try {
|
|
5035
5287
|
while (true) {
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5288
|
+
let result;
|
|
5289
|
+
try {
|
|
5290
|
+
result = await reader.read();
|
|
5291
|
+
} catch (error) {
|
|
5292
|
+
if (error instanceof ZeroTransferError) throw error;
|
|
5293
|
+
throw new ConnectionError({
|
|
5294
|
+
cause: error,
|
|
5295
|
+
message: "HTTP response stream was interrupted before completion",
|
|
5296
|
+
retryable: true
|
|
5297
|
+
});
|
|
5298
|
+
}
|
|
5299
|
+
if (result.done) break;
|
|
5300
|
+
if (result.value !== void 0) yield result.value;
|
|
5039
5301
|
}
|
|
5040
5302
|
} finally {
|
|
5041
5303
|
reader.releaseLock();
|
|
@@ -5043,8 +5305,8 @@ async function* webStreamToAsyncIterable(body) {
|
|
|
5043
5305
|
}
|
|
5044
5306
|
function secretToString(value) {
|
|
5045
5307
|
if (typeof value === "string") return value;
|
|
5046
|
-
if (value instanceof Uint8Array ||
|
|
5047
|
-
return
|
|
5308
|
+
if (value instanceof Uint8Array || import_node_buffer5.Buffer.isBuffer(value)) {
|
|
5309
|
+
return import_node_buffer5.Buffer.from(value).toString("utf8");
|
|
5048
5310
|
}
|
|
5049
5311
|
return String(value);
|
|
5050
5312
|
}
|
|
@@ -5056,6 +5318,7 @@ function createHttpProviderFactory(options = {}) {
|
|
|
5056
5318
|
const secure = options.secure ?? id === "https";
|
|
5057
5319
|
const basePath = options.basePath ?? "";
|
|
5058
5320
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
5321
|
+
assertHttpsEnforced({ enforceHttps: options.enforceHttps ?? false, providerId: id, secure });
|
|
5059
5322
|
if (typeof fetchImpl !== "function") {
|
|
5060
5323
|
throw new ConfigurationError({
|
|
5061
5324
|
message: "Global fetch is unavailable; supply HttpProviderOptions.fetch explicitly",
|
|
@@ -5105,13 +5368,20 @@ var HttpProvider = class {
|
|
|
5105
5368
|
id;
|
|
5106
5369
|
capabilities;
|
|
5107
5370
|
async connect(profile) {
|
|
5371
|
+
if (!this.internals.secure) {
|
|
5372
|
+
warnCleartextCredentials({
|
|
5373
|
+
hasCredentials: profile.username !== void 0 || profile.password !== void 0,
|
|
5374
|
+
host: profile.host,
|
|
5375
|
+
providerId: this.internals.id
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5108
5378
|
const headers = { ...this.internals.defaultHeaders };
|
|
5109
5379
|
if (profile.username !== void 0) {
|
|
5110
5380
|
const username = await resolveSecret(profile.username);
|
|
5111
5381
|
const password = profile.password !== void 0 ? await resolveSecret(profile.password) : "";
|
|
5112
5382
|
const usernameText = secretToString(username);
|
|
5113
5383
|
const passwordText = secretToString(password);
|
|
5114
|
-
headers["Authorization"] = `Basic ${
|
|
5384
|
+
headers["Authorization"] = `Basic ${import_node_buffer6.Buffer.from(`${usernameText}:${passwordText}`).toString("base64")}`;
|
|
5115
5385
|
}
|
|
5116
5386
|
const baseUrl = buildSessionBaseUrl(profile, this.internals);
|
|
5117
5387
|
const sessionOptions = {
|
|
@@ -5163,7 +5433,7 @@ var HttpFileSystem = class {
|
|
|
5163
5433
|
method: "HEAD"
|
|
5164
5434
|
});
|
|
5165
5435
|
if (!response.ok) {
|
|
5166
|
-
throw
|
|
5436
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
5167
5437
|
}
|
|
5168
5438
|
return responseToStat(response, normalized);
|
|
5169
5439
|
}
|
|
@@ -5188,12 +5458,12 @@ var HttpTransferOperations = class {
|
|
|
5188
5458
|
if (request.signal !== void 0) requestInit.signal = request.signal;
|
|
5189
5459
|
const response = await dispatchRequest(this.options, url, requestInit);
|
|
5190
5460
|
if (!response.ok && response.status !== 206) {
|
|
5191
|
-
throw
|
|
5461
|
+
throw await mapResponseErrorWithBody(response, normalized);
|
|
5192
5462
|
}
|
|
5193
5463
|
const body = response.body;
|
|
5194
5464
|
if (body === null) {
|
|
5195
5465
|
throw new ConnectionError({
|
|
5196
|
-
message: `HTTP response had no body for ${url
|
|
5466
|
+
message: `HTTP response had no body for ${redactUrlForLogging(url)}`,
|
|
5197
5467
|
retryable: true
|
|
5198
5468
|
});
|
|
5199
5469
|
}
|
|
@@ -5274,6 +5544,7 @@ function responseToStat(response, normalizedPath) {
|
|
|
5274
5544
|
copyBetween,
|
|
5275
5545
|
createAtomicDeployPlan,
|
|
5276
5546
|
createBandwidthThrottle,
|
|
5547
|
+
createDefaultRetryPolicy,
|
|
5277
5548
|
createHttpProviderFactory,
|
|
5278
5549
|
createLocalProviderFactory,
|
|
5279
5550
|
createMemoryProviderFactory,
|
|
@@ -5310,8 +5581,10 @@ function responseToStat(response, normalizedPath) {
|
|
|
5310
5581
|
parseRemoteManifest,
|
|
5311
5582
|
redactCommand,
|
|
5312
5583
|
redactConnectionProfile,
|
|
5584
|
+
redactErrorForLogging,
|
|
5313
5585
|
redactObject,
|
|
5314
5586
|
redactSecretSource,
|
|
5587
|
+
redactUrlForLogging,
|
|
5315
5588
|
redactValue,
|
|
5316
5589
|
resolveConnectionProfileSecrets,
|
|
5317
5590
|
resolveOpenSshHost,
|