mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
154 lines
3.7 KiB
TypeScript
154 lines
3.7 KiB
TypeScript
type ProxyEnv = Record<string, string | undefined>;
|
|
type ProxyResolution = { url?: string; error?: string };
|
|
type ProxyResolutionOptions = { allowedProtocols?: string[] };
|
|
|
|
function getEnvValue(env: ProxyEnv, keys: string[]): string | undefined {
|
|
for (const key of keys) {
|
|
const value = env[key];
|
|
if (typeof value === 'string') {
|
|
const trimmed = value.trim();
|
|
if (trimmed.length > 0) {
|
|
return trimmed;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function normalizeHostname(hostname: string): string {
|
|
const trimmed = hostname.trim().toLowerCase().replace(/\.$/, '');
|
|
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
return trimmed.slice(1, -1);
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
function isIpLikeHost(hostname: string): boolean {
|
|
return /^[\d.:]+$/.test(hostname);
|
|
}
|
|
|
|
function validateProxyUrl(proxyUrl: string, allowedProtocols?: string[]): string {
|
|
let parsedUrl: URL;
|
|
try {
|
|
parsedUrl = new URL(proxyUrl);
|
|
} catch {
|
|
throw new Error('Invalid URL');
|
|
}
|
|
|
|
if (allowedProtocols && !allowedProtocols.includes(parsedUrl.protocol)) {
|
|
throw new Error(`Unsupported proxy protocol: ${parsedUrl.protocol}`);
|
|
}
|
|
|
|
return proxyUrl;
|
|
}
|
|
|
|
function getProxyKeys(isHttps: boolean): string[] {
|
|
if (isHttps) {
|
|
return ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY', 'all_proxy', 'ALL_PROXY'];
|
|
}
|
|
|
|
return ['http_proxy', 'HTTP_PROXY', 'all_proxy', 'ALL_PROXY'];
|
|
}
|
|
|
|
export function getProxyResolution(
|
|
isHttps: boolean,
|
|
env: ProxyEnv = process.env,
|
|
options: ProxyResolutionOptions = {}
|
|
): ProxyResolution {
|
|
let firstError: string | undefined;
|
|
|
|
for (const key of getProxyKeys(isHttps)) {
|
|
const proxyUrl = getEnvValue(env, [key]);
|
|
if (!proxyUrl) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
return { url: validateProxyUrl(proxyUrl, options.allowedProtocols) };
|
|
} catch (error) {
|
|
if (!firstError) {
|
|
firstError = error instanceof Error ? error.message : 'Unknown proxy configuration error';
|
|
}
|
|
}
|
|
}
|
|
|
|
return { error: firstError };
|
|
}
|
|
|
|
export function getProxyUrl(isHttps: boolean, env: ProxyEnv = process.env): string | undefined {
|
|
const { url } = getProxyResolution(isHttps, env);
|
|
return url;
|
|
}
|
|
|
|
export function isLoopbackHost(hostname: string): boolean {
|
|
const normalizedHost = normalizeHostname(hostname);
|
|
if (!normalizedHost) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
normalizedHost === 'localhost' ||
|
|
normalizedHost.endsWith('.localhost') ||
|
|
normalizedHost === '0.0.0.0' ||
|
|
normalizedHost === '::1'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (/^127(?:\.\d{1,3}){3}$/.test(normalizedHost)) {
|
|
return true;
|
|
}
|
|
|
|
return normalizedHost.startsWith('::ffff:127.') || normalizedHost.startsWith('::ffff:7f');
|
|
}
|
|
|
|
export function getNoProxyValue(env: ProxyEnv = process.env): string | undefined {
|
|
return getEnvValue(env, ['no_proxy', 'NO_PROXY']);
|
|
}
|
|
|
|
export function shouldBypassProxy(hostname: string, env: ProxyEnv = process.env): boolean {
|
|
const normalizedHost = normalizeHostname(hostname);
|
|
if (!normalizedHost) {
|
|
return false;
|
|
}
|
|
|
|
if (isLoopbackHost(normalizedHost)) {
|
|
return true;
|
|
}
|
|
|
|
const noProxy = getNoProxyValue(env);
|
|
if (!noProxy) {
|
|
return false;
|
|
}
|
|
|
|
const patterns = noProxy
|
|
.split(/[,\s]+/)
|
|
.map((pattern) => normalizeHostname(pattern))
|
|
.filter(Boolean);
|
|
|
|
for (const pattern of patterns) {
|
|
if (pattern === '*') {
|
|
return true;
|
|
}
|
|
|
|
const canonicalPattern = pattern.startsWith('.')
|
|
? pattern.slice(1)
|
|
: pattern.replace(/^\*\.?/, '');
|
|
|
|
if (!canonicalPattern) {
|
|
continue;
|
|
}
|
|
|
|
if (normalizedHost === canonicalPattern) {
|
|
return true;
|
|
}
|
|
|
|
if (!isIpLikeHost(canonicalPattern) && normalizedHost.endsWith(`.${canonicalPattern}`)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|