@openclaw/proxyline 0.3.4 → 0.3.5
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/CHANGELOG.md +5 -0
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +10 -5
- package/dist/node-http.d.ts.map +1 -1
- package/dist/node-http.js +15 -18
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +19 -5
- package/dist/shared.d.ts +2 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +9 -0
- package/package.json +6 -6
- package/src/connect.ts +9 -5
- package/src/node-http.ts +15 -18
- package/src/runtime.ts +22 -5
- package/src/shared.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.5 - 2026-08-01
|
|
4
|
+
|
|
5
|
+
- Fixed malformed percent-encoding in proxy credentials to fail through CONNECT promises and Node request errors with `INVALID_PROXY_USERINFO`. Thanks @SebTardif.
|
|
6
|
+
- Updated Undici, WebSocket and TypeScript tooling, pnpm, and pinned GitHub Actions to their latest stable releases.
|
|
7
|
+
|
|
3
8
|
## 0.3.4 - 2026-07-20
|
|
4
9
|
|
|
5
10
|
- Added `AbortSignal` cancellation to explicit CONNECT tunnels, including active socket cleanup.
|
package/dist/connect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAgD,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAExI,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,CAAC;AAMH,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC;AAsB9C,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBrF;AAkDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,WAAW,CAAC,CA4HtB"}
|
package/dist/connect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
import tls from "node:tls";
|
|
3
|
-
import { ProxylineError, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
|
|
3
|
+
import { ProxylineError, decodeProxyUserinfoComponent, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
|
|
4
4
|
const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
|
|
5
5
|
const INVALID_CONNECT_AUTHORITY_PATTERN = /[\u0000-\u0020\u007f]/;
|
|
6
6
|
const INVALID_CONNECT_HOST_DELIMITER_PATTERN = /[/:?#@\\]/;
|
|
@@ -17,8 +17,8 @@ function resolveProxyAuthorization(proxy) {
|
|
|
17
17
|
if (!proxy.username && !proxy.password) {
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
|
-
const username =
|
|
21
|
-
const password =
|
|
20
|
+
const username = decodeProxyUserinfoComponent(proxy.username);
|
|
21
|
+
const password = decodeProxyUserinfoComponent(proxy.password);
|
|
22
22
|
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
23
23
|
}
|
|
24
24
|
export function formatConnectAuthority(targetHost, targetPort) {
|
|
@@ -109,7 +109,7 @@ export async function openProxyConnectTunnel(options) {
|
|
|
109
109
|
settled = true;
|
|
110
110
|
cleanup();
|
|
111
111
|
socket?.destroy();
|
|
112
|
-
reject(failConnect(proxy, error));
|
|
112
|
+
reject(error instanceof ProxylineError ? error : failConnect(proxy, error));
|
|
113
113
|
};
|
|
114
114
|
const succeed = (connectedSocket, tunneledBytes) => {
|
|
115
115
|
if (settled) {
|
|
@@ -128,7 +128,12 @@ export async function openProxyConnectTunnel(options) {
|
|
|
128
128
|
fail(new Error("proxy socket missing after connect"));
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
try {
|
|
132
|
+
writeConnectRequest(socket, proxy, target);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
fail(error);
|
|
136
|
+
}
|
|
132
137
|
};
|
|
133
138
|
const onData = (chunk) => {
|
|
134
139
|
responseBuffer = Buffer.concat([responseBuffer, chunk]);
|
package/dist/node-http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAI/B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAElB,OAAO,
|
|
1
|
+
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
|
package/dist/node-http.js
CHANGED
|
@@ -5,7 +5,7 @@ import tls from "node:tls";
|
|
|
5
5
|
import { domainToASCII } from "node:url";
|
|
6
6
|
import { readProxyEnv, resolveAmbientProxyForUrl, } from "./env.js";
|
|
7
7
|
import { formatConnectAuthority } from "./connect.js";
|
|
8
|
-
import { ProxylineError, resolveProxyTlsCa } from "./shared.js";
|
|
8
|
+
import { ProxylineError, decodeProxyUserinfoComponent, resolveProxyTlsCa } from "./shared.js";
|
|
9
9
|
const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
|
|
10
10
|
const INVALID_PROXY_TARGET_HOST_DELIMITER_PATTERN = /[/:?#@\\]/;
|
|
11
11
|
const INVALID_PROXY_TARGET_HOST_CONTROL_PATTERN = /[\u0000-\u0020\u007f]/;
|
|
@@ -133,8 +133,8 @@ function proxyAuthorization(proxy) {
|
|
|
133
133
|
if (!proxy.username && !proxy.password) {
|
|
134
134
|
return undefined;
|
|
135
135
|
}
|
|
136
|
-
const username =
|
|
137
|
-
const password =
|
|
136
|
+
const username = decodeProxyUserinfoComponent(proxy.username);
|
|
137
|
+
const password = decodeProxyUserinfoComponent(proxy.password);
|
|
138
138
|
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
139
139
|
}
|
|
140
140
|
function proxyConnectOptions(proxy, proxyTls) {
|
|
@@ -445,26 +445,23 @@ class ProxylineConnectAgent extends http.Agent {
|
|
|
445
445
|
finish(error, proxySocket);
|
|
446
446
|
};
|
|
447
447
|
const onConnected = () => {
|
|
448
|
-
let target;
|
|
449
448
|
try {
|
|
450
|
-
|
|
449
|
+
const { host, port } = connectTarget(options);
|
|
450
|
+
const authority = formatConnectAuthority(host, port);
|
|
451
|
+
const headers = [
|
|
452
|
+
`CONNECT ${authority} HTTP/1.1`,
|
|
453
|
+
`Host: ${authority}`,
|
|
454
|
+
`Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
|
|
455
|
+
];
|
|
456
|
+
const authorization = proxyAuthorization(this.#proxy);
|
|
457
|
+
if (authorization !== undefined) {
|
|
458
|
+
headers.push(`Proxy-Authorization: ${authorization}`);
|
|
459
|
+
}
|
|
460
|
+
proxySocket.write([...headers, "", ""].join("\r\n"));
|
|
451
461
|
}
|
|
452
462
|
catch (error) {
|
|
453
463
|
fail(error instanceof Error ? error : new Error(String(error)));
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
const { host, port } = target;
|
|
457
|
-
const authority = formatConnectAuthority(host, port);
|
|
458
|
-
const headers = [
|
|
459
|
-
`CONNECT ${authority} HTTP/1.1`,
|
|
460
|
-
`Host: ${authority}`,
|
|
461
|
-
`Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
|
|
462
|
-
];
|
|
463
|
-
const authorization = proxyAuthorization(this.#proxy);
|
|
464
|
-
if (authorization !== undefined) {
|
|
465
|
-
headers.push(`Proxy-Authorization: ${authorization}`);
|
|
466
464
|
}
|
|
467
|
-
proxySocket.write([...headers, "", ""].join("\r\n"));
|
|
468
465
|
};
|
|
469
466
|
const onData = (chunk) => {
|
|
470
467
|
responseBuffer = Buffer.concat([responseBuffer, chunk]);
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AAs0BpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -334,13 +334,25 @@ function createUndiciProxyAgent(proxyUrl, options) {
|
|
|
334
334
|
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
335
335
|
}
|
|
336
336
|
: undefined;
|
|
337
|
+
const dispatcherFactory = createProxyClientFactory();
|
|
337
338
|
return new UndiciProxyAgent({
|
|
338
339
|
...resolveUndiciBaseOptions(options.undici),
|
|
339
340
|
uri: proxyUrl,
|
|
340
|
-
clientFactory:
|
|
341
|
+
clientFactory: dispatcherFactory,
|
|
342
|
+
factory: dispatcherFactory,
|
|
341
343
|
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
342
344
|
});
|
|
343
345
|
}
|
|
346
|
+
function normalizeUndiciDispatchOptions(options) {
|
|
347
|
+
if (options.origin === undefined || !/^https?:\/\//i.test(options.path)) {
|
|
348
|
+
return options;
|
|
349
|
+
}
|
|
350
|
+
const pathUrl = new URL(options.path);
|
|
351
|
+
return {
|
|
352
|
+
...options,
|
|
353
|
+
path: `${pathUrl.pathname}${pathUrl.search}`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
344
356
|
function createUndiciProxyDispatcher(options, dispatcherOptions) {
|
|
345
357
|
if (options.mode === "ambient") {
|
|
346
358
|
if (!options.active) {
|
|
@@ -379,10 +391,11 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
379
391
|
if (this.#closedError !== undefined) {
|
|
380
392
|
return reportClosedDispatchError(handler, this.#closedError);
|
|
381
393
|
}
|
|
382
|
-
const
|
|
394
|
+
const normalizedOptions = normalizeUndiciDispatchOptions(options);
|
|
395
|
+
const url = resolveUndiciDispatchUrl(normalizedOptions);
|
|
383
396
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
384
397
|
const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
|
|
385
|
-
return dispatcher.dispatch(
|
|
398
|
+
return dispatcher.dispatch(normalizedOptions, handler);
|
|
386
399
|
}
|
|
387
400
|
close(callback) {
|
|
388
401
|
const closing = this.#closeAll();
|
|
@@ -445,10 +458,11 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
445
458
|
if (this.#closedError !== undefined) {
|
|
446
459
|
return reportClosedDispatchError(handler, this.#closedError);
|
|
447
460
|
}
|
|
448
|
-
const
|
|
461
|
+
const normalizedOptions = normalizeUndiciDispatchOptions(options);
|
|
462
|
+
const url = resolveUndiciDispatchUrl(normalizedOptions);
|
|
449
463
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|
|
450
464
|
const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
|
|
451
|
-
return dispatcher.dispatch(
|
|
465
|
+
return dispatcher.dispatch(normalizedOptions, handler);
|
|
452
466
|
}
|
|
453
467
|
close(callback) {
|
|
454
468
|
const closing = this.#closeAll();
|
package/dist/shared.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare class ProxylineError extends Error {
|
|
|
6
6
|
readonly code: string;
|
|
7
7
|
constructor(code: string, message: string);
|
|
8
8
|
}
|
|
9
|
+
/** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
|
|
10
|
+
export declare function decodeProxyUserinfoComponent(value: string): string;
|
|
9
11
|
export declare function resolveProxyTlsCa(options: ProxylineTlsOptions | undefined): string | undefined;
|
|
10
12
|
export declare function formatUrl(value: string | URL): string;
|
|
11
13
|
export declare function redactProxyUrl(value: string | URL): string;
|
package/dist/shared.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,YAAmB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI/C;CACF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,YAAmB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI/C;CACF;AAED,2FAA2F;AAC3F,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
|
package/dist/shared.js
CHANGED
|
@@ -7,6 +7,15 @@ export class ProxylineError extends Error {
|
|
|
7
7
|
this.code = code;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
/** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
|
|
11
|
+
export function decodeProxyUserinfoComponent(value) {
|
|
12
|
+
try {
|
|
13
|
+
return decodeURIComponent(value);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new ProxylineError("INVALID_PROXY_USERINFO", "Proxy username or password contains invalid percent-encoding.");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
export function resolveProxyTlsCa(options) {
|
|
11
20
|
if (!options) {
|
|
12
21
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/proxyline",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Process-global proxy routing for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/openclaw/proxyline/issues"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://proxyline.dev",
|
|
20
|
-
"packageManager": "pnpm@11.
|
|
20
|
+
"packageManager": "pnpm@11.18.0",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"crabbox:warmup": "crabbox warmup"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@types/node": "26.1.
|
|
65
|
+
"@types/node": "26.1.2",
|
|
66
66
|
"@types/ws": "8.18.1",
|
|
67
|
-
"tsx": "4.23.
|
|
67
|
+
"tsx": "4.23.1",
|
|
68
68
|
"typescript": "^7.0.2",
|
|
69
|
-
"undici": "8.
|
|
70
|
-
"ws": "8.21.
|
|
69
|
+
"undici": "8.9.0",
|
|
70
|
+
"ws": "8.21.1"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22.19.0"
|
package/src/connect.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
import tls from "node:tls";
|
|
3
|
-
import { ProxylineError, type ProxylineTlsOptions, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
|
|
3
|
+
import { ProxylineError, decodeProxyUserinfoComponent, type ProxylineTlsOptions, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
|
|
4
4
|
|
|
5
5
|
export type OpenProxyConnectTunnelOptions = Readonly<{
|
|
6
6
|
proxyUrl: string | URL;
|
|
@@ -32,8 +32,8 @@ function resolveProxyAuthorization(proxy: URL): string | undefined {
|
|
|
32
32
|
if (!proxy.username && !proxy.password) {
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
|
-
const username =
|
|
36
|
-
const password =
|
|
35
|
+
const username = decodeProxyUserinfoComponent(proxy.username);
|
|
36
|
+
const password = decodeProxyUserinfoComponent(proxy.password);
|
|
37
37
|
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -142,7 +142,7 @@ export async function openProxyConnectTunnel(
|
|
|
142
142
|
settled = true;
|
|
143
143
|
cleanup();
|
|
144
144
|
socket?.destroy();
|
|
145
|
-
reject(failConnect(proxy, error));
|
|
145
|
+
reject(error instanceof ProxylineError ? error : failConnect(proxy, error));
|
|
146
146
|
};
|
|
147
147
|
|
|
148
148
|
const succeed = (connectedSocket: ProxySocket, tunneledBytes: Buffer | undefined): void => {
|
|
@@ -163,7 +163,11 @@ export async function openProxyConnectTunnel(
|
|
|
163
163
|
fail(new Error("proxy socket missing after connect"));
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
-
|
|
166
|
+
try {
|
|
167
|
+
writeConnectRequest(socket, proxy, target);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
fail(error);
|
|
170
|
+
}
|
|
167
171
|
};
|
|
168
172
|
|
|
169
173
|
const onData = (chunk: Buffer): void => {
|
package/src/node-http.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type ProxyEnvSnapshot,
|
|
10
10
|
} from "./env.js";
|
|
11
11
|
import { formatConnectAuthority } from "./connect.js";
|
|
12
|
-
import { ProxylineError, resolveProxyTlsCa, type ProxylineTlsOptions } from "./shared.js";
|
|
12
|
+
import { ProxylineError, decodeProxyUserinfoComponent, resolveProxyTlsCa, type ProxylineTlsOptions } from "./shared.js";
|
|
13
13
|
import type { ProxylineSurface, ProxyResolver } from "./types.js";
|
|
14
14
|
|
|
15
15
|
export type NodeHttpRequestOptions = http.RequestOptions & https.RequestOptions & {
|
|
@@ -198,8 +198,8 @@ function proxyAuthorization(proxy: URL): string | undefined {
|
|
|
198
198
|
if (!proxy.username && !proxy.password) {
|
|
199
199
|
return undefined;
|
|
200
200
|
}
|
|
201
|
-
const username =
|
|
202
|
-
const password =
|
|
201
|
+
const username = decodeProxyUserinfoComponent(proxy.username);
|
|
202
|
+
const password = decodeProxyUserinfoComponent(proxy.password);
|
|
203
203
|
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -591,25 +591,22 @@ class ProxylineConnectAgent extends http.Agent {
|
|
|
591
591
|
};
|
|
592
592
|
|
|
593
593
|
const onConnected = (): void => {
|
|
594
|
-
let target: { host: string; port: number };
|
|
595
594
|
try {
|
|
596
|
-
|
|
595
|
+
const { host, port } = connectTarget(options);
|
|
596
|
+
const authority = formatConnectAuthority(host, port);
|
|
597
|
+
const headers = [
|
|
598
|
+
`CONNECT ${authority} HTTP/1.1`,
|
|
599
|
+
`Host: ${authority}`,
|
|
600
|
+
`Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
|
|
601
|
+
];
|
|
602
|
+
const authorization = proxyAuthorization(this.#proxy);
|
|
603
|
+
if (authorization !== undefined) {
|
|
604
|
+
headers.push(`Proxy-Authorization: ${authorization}`);
|
|
605
|
+
}
|
|
606
|
+
proxySocket.write([...headers, "", ""].join("\r\n"));
|
|
597
607
|
} catch (error) {
|
|
598
608
|
fail(error instanceof Error ? error : new Error(String(error)));
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
const { host, port } = target;
|
|
602
|
-
const authority = formatConnectAuthority(host, port);
|
|
603
|
-
const headers = [
|
|
604
|
-
`CONNECT ${authority} HTTP/1.1`,
|
|
605
|
-
`Host: ${authority}`,
|
|
606
|
-
`Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
|
|
607
|
-
];
|
|
608
|
-
const authorization = proxyAuthorization(this.#proxy);
|
|
609
|
-
if (authorization !== undefined) {
|
|
610
|
-
headers.push(`Proxy-Authorization: ${authorization}`);
|
|
611
609
|
}
|
|
612
|
-
proxySocket.write([...headers, "", ""].join("\r\n"));
|
|
613
610
|
};
|
|
614
611
|
|
|
615
612
|
const onData = (chunk: Buffer): void => {
|
package/src/runtime.ts
CHANGED
|
@@ -524,14 +524,29 @@ function createUndiciProxyAgent(
|
|
|
524
524
|
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
525
525
|
}
|
|
526
526
|
: undefined;
|
|
527
|
+
const dispatcherFactory = createProxyClientFactory();
|
|
527
528
|
return new UndiciProxyAgent({
|
|
528
529
|
...resolveUndiciBaseOptions(options.undici),
|
|
529
530
|
uri: proxyUrl,
|
|
530
|
-
clientFactory:
|
|
531
|
+
clientFactory: dispatcherFactory,
|
|
532
|
+
factory: dispatcherFactory,
|
|
531
533
|
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
532
534
|
} as ConstructorParameters<typeof UndiciProxyAgent>[0]);
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
function normalizeUndiciDispatchOptions(
|
|
538
|
+
options: Dispatcher.DispatchOptions,
|
|
539
|
+
): Dispatcher.DispatchOptions {
|
|
540
|
+
if (options.origin === undefined || !/^https?:\/\//i.test(options.path)) {
|
|
541
|
+
return options;
|
|
542
|
+
}
|
|
543
|
+
const pathUrl = new URL(options.path);
|
|
544
|
+
return {
|
|
545
|
+
...options,
|
|
546
|
+
path: `${pathUrl.pathname}${pathUrl.search}`,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
535
550
|
function createUndiciProxyDispatcher(
|
|
536
551
|
options:
|
|
537
552
|
| { mode: "managed"; resolver: ProxyResolver }
|
|
@@ -585,10 +600,11 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
585
600
|
if (this.#closedError !== undefined) {
|
|
586
601
|
return reportClosedDispatchError(handler, this.#closedError);
|
|
587
602
|
}
|
|
588
|
-
const
|
|
603
|
+
const normalizedOptions = normalizeUndiciDispatchOptions(options);
|
|
604
|
+
const url = resolveUndiciDispatchUrl(normalizedOptions);
|
|
589
605
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
590
606
|
const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
|
|
591
|
-
return dispatcher.dispatch(
|
|
607
|
+
return dispatcher.dispatch(normalizedOptions, handler);
|
|
592
608
|
}
|
|
593
609
|
|
|
594
610
|
public override close(callback: () => void): void;
|
|
@@ -671,10 +687,11 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
671
687
|
if (this.#closedError !== undefined) {
|
|
672
688
|
return reportClosedDispatchError(handler, this.#closedError);
|
|
673
689
|
}
|
|
674
|
-
const
|
|
690
|
+
const normalizedOptions = normalizeUndiciDispatchOptions(options);
|
|
691
|
+
const url = resolveUndiciDispatchUrl(normalizedOptions);
|
|
675
692
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|
|
676
693
|
const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
|
|
677
|
-
return dispatcher.dispatch(
|
|
694
|
+
return dispatcher.dispatch(normalizedOptions, handler);
|
|
678
695
|
}
|
|
679
696
|
|
|
680
697
|
public override close(callback: () => void): void;
|
package/src/shared.ts
CHANGED
|
@@ -15,6 +15,18 @@ export class ProxylineError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
|
|
19
|
+
export function decodeProxyUserinfoComponent(value: string): string {
|
|
20
|
+
try {
|
|
21
|
+
return decodeURIComponent(value);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new ProxylineError(
|
|
24
|
+
"INVALID_PROXY_USERINFO",
|
|
25
|
+
"Proxy username or password contains invalid percent-encoding.",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
export function resolveProxyTlsCa(options: ProxylineTlsOptions | undefined): string | undefined {
|
|
19
31
|
if (!options) {
|
|
20
32
|
return undefined;
|