Files
RSSHub/lib/utils/proxy/unify-proxy.ts
T
pseudoyu e0b3b98401 fix(core): stop SOCKS proxies other than socks5 from crashing startup
Since #22401 every socks* PROXY_URI is handed to undici's ProxyAgent,
which only accepts socks5: and socks:. A socks5h, socks4 or socks4a
URI fell through to its HTTP proxy path and threw "Invalid URL
protocol" while the proxy module loaded, so the server never started,
even though unify-proxy listed all of them as supported.

Pass socks5h to undici as socks5: its SOCKS5 client sends host names
to the proxy, so DNS still resolves there, as playwright.ts already
assumes. undici has no SOCKS4 support, so drop socks4 and socks4a from
the accepted protocols, which logs the supported list and disables the
proxy like any other unsupported protocol.
2026-10-15 08:29:37 +08:00

116 lines
5.1 KiB
TypeScript

import type { Config } from '@/config';
import logger from '@/utils/logger';
const defaultProtocol = 'http';
// SOCKS4 is left out because the undici dispatcher behind fetch only speaks SOCKS5
const possibleProtocol = ['http', 'https', 'socks', 'socks5', 'socks5h'];
const unifyProxy = (proxyUri: Config['proxyUri'] | string, proxyObj: Config['proxy']) => {
const [oriProxyUri, oriProxyObj] = [proxyUri, proxyObj];
proxyObj = { ...proxyObj };
let proxyUrlHandler: URL | null = null;
// PROXY_URI
if (proxyUri) {
if (!proxyUri.includes('://')) {
logger.warn(`PROXY_URI contains no protocol, assuming ${defaultProtocol}`);
proxyUri = `${defaultProtocol}://${proxyUri}`;
}
try {
proxyUrlHandler = new URL(proxyUri);
} catch (error: any) {
logger.error(`Parse PROXY_URI error: ${error.stack}`);
}
}
// PROXY_{PROTOCOL,HOST,PORT}
if (proxyObj.protocol || proxyObj.host || proxyObj.port) {
if (proxyUrlHandler) {
logger.warn('PROXY_URI is set, ignoring PROXY_{PROTOCOL,HOST,PORT}');
} else if (proxyObj.host) {
let tempProxyStr = proxyObj.host;
if (tempProxyStr.includes('://')) {
logger.warn('PROXY_HOST contains protocol, ignoring PROXY_PROTOCOL');
} else if (proxyObj.protocol) {
tempProxyStr = `${proxyObj.protocol}://${tempProxyStr}`;
} else {
logger.warn(`PROXY_PROTOCOL is not set, assuming '${defaultProtocol}'`);
tempProxyStr = `${defaultProtocol}://${tempProxyStr}`;
}
try {
proxyUrlHandler = new URL(tempProxyStr);
if (proxyUrlHandler.port && proxyObj.port) {
logger.warn('PROXY_HOST contains port, ignoring PROXY_PORT');
} else if (proxyObj.port) {
if (Number.parseInt(proxyObj.port)) {
proxyUrlHandler.port = proxyObj.port;
} else {
logger.warn('PROXY_PORT is not a number, ignoring');
}
} else {
logger.warn('PROXY_PORT is not set, leaving proxy agent to determine');
}
} catch (error: any) {
logger.error(`Parse PROXY_HOST error: ${error.stack}`);
}
} else {
logger.warn('Either PROXY_{PROTOCOL,PORT} is set, but PROXY_HOST is missing, ignoring');
}
}
// PROXY_AUTH
if (proxyObj.auth && proxyUrlHandler) {
let promptProxyUri = false;
if (proxyUrlHandler.username || proxyUrlHandler.password) {
logger.warn('PROXY_URI contains username and/or password, ignoring PROXY_AUTH');
proxyObj.auth = undefined;
} else if (['http:', 'https:'].includes(proxyUrlHandler.protocol)) {
logger.info('PROXY_AUTH is set and will be used for requests from Node.js. However, requests from Playwright will not use it');
promptProxyUri = true;
} else {
logger.warn(`PROXY_AUTH is only supported by HTTP(S) proxies, but got ${proxyUrlHandler.protocol}, ignoring`);
proxyObj.auth = undefined;
promptProxyUri = true;
}
if (promptProxyUri) {
logger.info('To get rid of this, set PROXY_URI like protocol://username:password@host:port and clear PROXY_{AUTH,PROTOCOL,HOST,PORT}');
}
}
// is proxy enabled and valid?
let isProxyValid = false;
if (proxyUrlHandler) {
const protocol = proxyUrlHandler.protocol.replace(':', '');
if (possibleProtocol.includes(protocol)) {
if (protocol !== 'http' && (proxyUrlHandler.username || proxyUrlHandler.password)) {
logger.warn("PROXY_URI is an HTTPS/SOCKS proxy with authentication, which is not supported by Playwright (ignore if you don't need it)");
logger.info('To get rid of this, consider using an HTTP proxy instead');
}
proxyObj.protocol = protocol;
proxyObj.host = proxyUrlHandler.hostname;
proxyObj.port = proxyUrlHandler.port || undefined;
// Trailing slash can make Chromium reject the proxy URL, trim it.
proxyUri = proxyUrlHandler.href.endsWith('/') ? proxyUrlHandler.href.slice(0, -1) : proxyUrlHandler.href;
isProxyValid = true;
} else {
logger.error(`Unsupported proxy protocol: ${protocol}, expect one of ${possibleProtocol.join(', ')}`);
}
}
if (!isProxyValid) {
if (oriProxyUri || oriProxyObj.protocol || oriProxyObj.host || oriProxyObj.port || oriProxyObj.auth) {
logger.error('Proxy is disabled due to misconfiguration');
}
proxyObj.protocol = proxyObj.host = proxyObj.port = proxyObj.auth = undefined;
proxyUri = undefined;
proxyUrlHandler = null;
}
return { proxyUri, proxyObj, proxyUrlHandler };
};
export const unifyProxies = (proxyUris: string[], proxyObj: Config['proxy']) => proxyUris.map((uri) => unifyProxy(uri, proxyObj)).filter((result) => result.proxyUri);
export default unifyProxy;