UNPKG

pac-proxy-agent

Version:

A PAC file proxy `http.Agent` implementation for HTTP

238 lines 9.93 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PacProxyAgent = void 0; const net = __importStar(require("net")); const tls = __importStar(require("tls")); const crypto = __importStar(require("crypto")); const events_1 = require("events"); const debug_1 = __importDefault(require("debug")); const url_1 = require("url"); const agent_base_1 = require("agent-base"); const get_uri_1 = require("get-uri"); const pac_resolver_1 = require("pac-resolver"); const quickjs_emscripten_1 = require("@tootallnate/quickjs-emscripten"); const debug = (0, debug_1.default)('pac-proxy-agent'); const setServernameFromNonIpHost = (options) => { if (options.servername === undefined && options.host && !net.isIP(options.host)) { return { ...options, servername: options.host, }; } return options; }; /** * The `PacProxyAgent` class. * * A few different "protocol" modes are supported (supported protocols are * backed by the `get-uri` module): * * - "pac+data", "data" - refers to an embedded "data:" URI * - "pac+file", "file" - refers to a local file * - "pac+ftp", "ftp" - refers to a file located on an FTP server * - "pac+http", "http" - refers to an HTTP endpoint * - "pac+https", "https" - refers to an HTTPS endpoint */ class PacProxyAgent extends agent_base_1.Agent { constructor(uri, opts) { super(opts); this.clearResolverPromise = () => { this.resolverPromise = undefined; }; // Strip the "pac+" prefix const uriStr = typeof uri === 'string' ? uri : uri.href; this.uri = new url_1.URL(uriStr.replace(/^pac\+/i, '')); debug('Creating PacProxyAgent with URI %o', this.uri.href); // @ts-expect-error Not sure why TS is complaining here… this.opts = { ...opts }; this.cache = undefined; this.resolver = undefined; this.resolverHash = ''; this.resolverPromise = undefined; // For `PacResolver` if (!this.opts.filename) { this.opts.filename = this.uri.href; } } /** * Loads the PAC proxy file from the source if necessary, and returns * a generated `FindProxyForURL()` resolver function to use. */ getResolver() { if (!this.resolverPromise) { this.resolverPromise = this.loadResolver(); this.resolverPromise.then(this.clearResolverPromise, this.clearResolverPromise); } return this.resolverPromise; } async loadResolver() { try { // (Re)load the contents of the PAC file URI const [qjs, code] = await Promise.all([ (0, quickjs_emscripten_1.getQuickJS)(), this.loadPacFile(), ]); // Create a sha1 hash of the JS code const hash = crypto.createHash('sha1').update(code).digest('hex'); if (this.resolver && this.resolverHash === hash) { debug('Same sha1 hash for code - contents have not changed, reusing previous proxy resolver'); return this.resolver; } // Cache the resolver debug('Creating new proxy resolver instance'); this.resolver = (0, pac_resolver_1.createPacResolver)(qjs, code, this.opts); // Store that sha1 hash for future comparison purposes this.resolverHash = hash; return this.resolver; } catch (err) { if (this.resolver && err.code === 'ENOTMODIFIED') { debug('Got ENOTMODIFIED response, reusing previous proxy resolver'); return this.resolver; } throw err; } } /** * Loads the contents of the PAC proxy file. * * @api private */ async loadPacFile() { debug('Loading PAC file: %o', this.uri); const rs = await (0, get_uri_1.getUri)(this.uri, { ...this.opts, cache: this.cache }); debug('Got `Readable` instance for URI'); this.cache = rs; const buf = await (0, agent_base_1.toBuffer)(rs); debug('Read %o byte PAC file from URI', buf.length); return buf.toString('utf8'); } /** * Called when the node-core HTTP client library is creating a new HTTP request. */ async connect(req, opts) { const { secureEndpoint } = opts; const isWebSocket = req.getHeader('upgrade') === 'websocket'; // First, get a generated `FindProxyForURL()` function, // either cached or retrieved from the source const resolver = await this.getResolver(); // Calculate the `url` parameter const protocol = secureEndpoint ? 'https:' : 'http:'; const host = opts.host && net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host; const defaultPort = secureEndpoint ? 443 : 80; const url = Object.assign(new url_1.URL(req.path, `${protocol}//${host}`), defaultPort ? undefined : { port: opts.port }); debug('url: %s', url); let result = await resolver(url); // Default to "DIRECT" if a falsey value was returned (or nothing) if (!result) { result = 'DIRECT'; } const proxies = String(result) .trim() .split(/\s*;\s*/g) .filter(Boolean); if (this.opts.fallbackToDirect && !proxies.includes('DIRECT')) { proxies.push('DIRECT'); } for (const proxy of proxies) { let agent = null; let socket = null; const [type, target] = proxy.split(/\s+/); debug('Attempting to use proxy: %o', proxy); if (type === 'DIRECT') { // Direct connection to the destination endpoint if (secureEndpoint) { socket = tls.connect(setServernameFromNonIpHost(opts)); } else { socket = net.connect(opts); } } else if (type === 'SOCKS' || type === 'SOCKS5') { // Use a SOCKSv5h proxy const { SocksProxyAgent } = await Promise.resolve().then(() => __importStar(require('socks-proxy-agent'))); agent = new SocksProxyAgent(`socks://${target}`, this.opts); } else if (type === 'SOCKS4') { // Use a SOCKSv4a proxy const { SocksProxyAgent } = await Promise.resolve().then(() => __importStar(require('socks-proxy-agent'))); agent = new SocksProxyAgent(`socks4a://${target}`, this.opts); } else if (type === 'PROXY' || type === 'HTTP' || type === 'HTTPS') { // Use an HTTP or HTTPS proxy // http://dev.chromium.org/developers/design-documents/secure-web-proxy const proxyURL = `${type === 'HTTPS' ? 'https' : 'http'}://${target}`; if (secureEndpoint || isWebSocket) { const { HttpsProxyAgent } = await Promise.resolve().then(() => __importStar(require('https-proxy-agent'))); agent = new HttpsProxyAgent(proxyURL, this.opts); } else { const { HttpProxyAgent } = await Promise.resolve().then(() => __importStar(require('http-proxy-agent'))); agent = new HttpProxyAgent(proxyURL, this.opts); } } try { if (socket) { // "DIRECT" connection, wait for connection confirmation await (0, events_1.once)(socket, 'connect'); req.emit('proxy', { proxy, socket }); return socket; } if (agent) { const s = await agent.connect(req, opts); if (!(s instanceof net.Socket)) { throw new Error('Expected a `net.Socket` to be returned from agent'); } req.emit('proxy', { proxy, socket: s }); return s; } throw new Error(`Could not determine proxy type for: ${proxy}`); } catch (err) { debug('Got error for proxy %o: %o', proxy, err); req.emit('proxy', { proxy, error: err }); } } throw new Error(`Failed to establish a socket connection to proxies: ${JSON.stringify(proxies)}`); } } PacProxyAgent.protocols = [ 'pac+data', 'pac+file', 'pac+ftp', 'pac+http', 'pac+https', ]; exports.PacProxyAgent = PacProxyAgent; //# sourceMappingURL=index.js.map