mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix: preserve explicit proxy port (80/443) instead of defaulting to 8080
The URL parser silently strips default ports (80 for HTTP, 443 for HTTPS) when constructing URL objects. This caused proxy connections to use port 8080 instead of the user-specified port 80, resulting in connection timeouts. Fix by extracting the port from the raw URL string before parsing and building the normalized URL manually to avoid the serializer stripping it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,23 @@ export function clearDispatcherCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the port from a proxy URL string before URL parsing.
|
||||
* `new URL("http://host:80")` strips port 80 since it's the HTTP default,
|
||||
* but proxy servers commonly listen on port 80/443, so we need to preserve it.
|
||||
*/
|
||||
function extractExplicitPort(urlStr) {
|
||||
try {
|
||||
// Match port in the host portion: "scheme://[user:pass@]host:PORT[/...]"
|
||||
const match = urlStr.match(/:\/\/(?:[^@]*@)?[^:/\s]+:(\d+)/);
|
||||
if (match) {
|
||||
const port = Number(match[1]);
|
||||
if (Number.isInteger(port) && port >= 1 && port <= 65535) return String(port);
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function defaultPortForProtocol(protocol) {
|
||||
if (protocol === "https:" || protocol === "wss:") return "443";
|
||||
if (protocol === "socks5:") return "1080";
|
||||
@@ -35,13 +52,28 @@ function normalizePort(port, protocol) {
|
||||
return String(parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a proxy URL string manually from parsed URL components.
|
||||
* We cannot use URL.toString() because the URL serializer silently strips
|
||||
* default ports (80 for http, 443 for https). Proxy servers commonly
|
||||
* listen on these ports, so we must always include the port explicitly.
|
||||
*/
|
||||
function buildProxyUrlString(parsed, port) {
|
||||
const auth =
|
||||
parsed.username
|
||||
? `${parsed.username}${parsed.password ? `:${parsed.password}` : ""}@`
|
||||
: "";
|
||||
return `${parsed.protocol}//${auth}${parsed.hostname}:${port}`;
|
||||
}
|
||||
|
||||
export function isSocks5ProxyEnabled() {
|
||||
return process.env.ENABLE_SOCKS5_PROXY === "true";
|
||||
}
|
||||
|
||||
export function proxyUrlForLogs(proxyUrl) {
|
||||
const explicitPort = extractExplicitPort(proxyUrl);
|
||||
const parsed = new URL(proxyUrl);
|
||||
const port = parsed.port || defaultPortForProtocol(parsed.protocol);
|
||||
const port = explicitPort || parsed.port || defaultPortForProtocol(parsed.protocol);
|
||||
return `${parsed.protocol}//${parsed.hostname}:${port}`;
|
||||
}
|
||||
|
||||
@@ -50,6 +82,11 @@ export function normalizeProxyUrl(
|
||||
source = "proxy",
|
||||
{ allowSocks5 = isSocks5ProxyEnabled() } = {}
|
||||
) {
|
||||
// Extract the explicit port from the raw URL string BEFORE parsing,
|
||||
// because `new URL()` silently strips default ports (80 for http,
|
||||
// 443 for https), which are valid and common for proxy servers.
|
||||
const explicitPort = extractExplicitPort(proxyUrl);
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(proxyUrl);
|
||||
@@ -71,8 +108,12 @@ export function normalizeProxyUrl(
|
||||
throw new Error(`[ProxyDispatcher] Invalid ${source} host`);
|
||||
}
|
||||
|
||||
parsed.port = normalizePort(parsed.port, parsed.protocol);
|
||||
return parsed.toString();
|
||||
// Use the explicit port from the raw string if present, otherwise apply default.
|
||||
const port = explicitPort || normalizePort(parsed.port, parsed.protocol);
|
||||
|
||||
// Build the URL string manually instead of using parsed.toString(),
|
||||
// which would strip default ports (80/443) and break the proxy connection.
|
||||
return buildProxyUrlString(parsed, port);
|
||||
}
|
||||
|
||||
export function proxyConfigToUrl(proxyConfig, { allowSocks5 = isSocks5ProxyEnabled() } = {}) {
|
||||
@@ -102,14 +143,15 @@ export function proxyConfigToUrl(proxyConfig, { allowSocks5 = isSocks5ProxyEnabl
|
||||
}
|
||||
|
||||
const port = normalizePort(proxyConfig.port, protocol);
|
||||
const proxyUrl = new URL(`${type}://${proxyConfig.host}:${port}`);
|
||||
|
||||
if (proxyConfig.username) {
|
||||
proxyUrl.username = encodeURIComponent(proxyConfig.username);
|
||||
proxyUrl.password = proxyConfig.password ? encodeURIComponent(proxyConfig.password) : "";
|
||||
}
|
||||
// Build the URL string manually to preserve the port through normalization.
|
||||
const auth = proxyConfig.username
|
||||
? `${encodeURIComponent(proxyConfig.username)}:${proxyConfig.password ? encodeURIComponent(proxyConfig.password) : ""}@`
|
||||
: "";
|
||||
|
||||
return normalizeProxyUrl(proxyUrl.toString(), "context proxy", { allowSocks5 });
|
||||
const proxyUrlStr = `${type}://${auth}${proxyConfig.host}:${port}`;
|
||||
|
||||
return normalizeProxyUrl(proxyUrlStr, "context proxy", { allowSocks5 });
|
||||
}
|
||||
|
||||
export function createProxyDispatcher(proxyUrl) {
|
||||
@@ -120,11 +162,14 @@ export function createProxyDispatcher(proxyUrl) {
|
||||
if (dispatcher) return dispatcher;
|
||||
|
||||
const parsed = new URL(normalizedUrl);
|
||||
const explicitPort = extractExplicitPort(normalizedUrl);
|
||||
const port = explicitPort || normalizePort(parsed.port, parsed.protocol);
|
||||
|
||||
if (parsed.protocol === "socks5:") {
|
||||
const socksOptions: Record<string, any> = {
|
||||
type: 5,
|
||||
host: parsed.hostname,
|
||||
port: Number(normalizePort(parsed.port, parsed.protocol)),
|
||||
port: Number(port),
|
||||
};
|
||||
if (parsed.username) socksOptions.userId = decodeURIComponent(parsed.username);
|
||||
if (parsed.password) socksOptions.password = decodeURIComponent(parsed.password);
|
||||
@@ -135,4 +180,4 @@ export function createProxyDispatcher(proxyUrl) {
|
||||
|
||||
dispatcherCache.set(normalizedUrl, dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user