You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					215 lines
				
				9.4 KiB
			
		
		
			
		
	
	
					215 lines
				
				9.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"use strict";
							 | 
						||
| 
								 | 
							
								var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
							 | 
						||
| 
								 | 
							
								    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
							 | 
						||
| 
								 | 
							
								    return new (P || (P = Promise))(function (resolve, reject) {
							 | 
						||
| 
								 | 
							
								        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
							 | 
						||
| 
								 | 
							
								        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
							 | 
						||
| 
								 | 
							
								        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
							 | 
						||
| 
								 | 
							
								        step((generator = generator.apply(thisArg, _arguments || [])).next());
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								var __importDefault = (this && this.__importDefault) || function (mod) {
							 | 
						||
| 
								 | 
							
								    return (mod && mod.__esModule) ? mod : { "default": mod };
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								Object.defineProperty(exports, "__esModule", { value: true });
							 | 
						||
| 
								 | 
							
								const net_1 = __importDefault(require("net"));
							 | 
						||
| 
								 | 
							
								const tls_1 = __importDefault(require("tls"));
							 | 
						||
| 
								 | 
							
								const once_1 = __importDefault(require("@tootallnate/once"));
							 | 
						||
| 
								 | 
							
								const crypto_1 = __importDefault(require("crypto"));
							 | 
						||
| 
								 | 
							
								const get_uri_1 = __importDefault(require("get-uri"));
							 | 
						||
| 
								 | 
							
								const debug_1 = __importDefault(require("debug"));
							 | 
						||
| 
								 | 
							
								const raw_body_1 = __importDefault(require("raw-body"));
							 | 
						||
| 
								 | 
							
								const url_1 = require("url");
							 | 
						||
| 
								 | 
							
								const http_proxy_agent_1 = require("http-proxy-agent");
							 | 
						||
| 
								 | 
							
								const https_proxy_agent_1 = require("https-proxy-agent");
							 | 
						||
| 
								 | 
							
								const socks_proxy_agent_1 = require("socks-proxy-agent");
							 | 
						||
| 
								 | 
							
								const pac_resolver_1 = __importDefault(require("pac-resolver"));
							 | 
						||
| 
								 | 
							
								const agent_base_1 = require("agent-base");
							 | 
						||
| 
								 | 
							
								const debug = debug_1.default('pac-proxy-agent');
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * 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
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @api public
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								class PacProxyAgent extends agent_base_1.Agent {
							 | 
						||
| 
								 | 
							
								    constructor(uri, opts = {}) {
							 | 
						||
| 
								 | 
							
								        super(opts);
							 | 
						||
| 
								 | 
							
								        this.clearResolverPromise = () => {
							 | 
						||
| 
								 | 
							
								            this.resolverPromise = undefined;
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								        debug('Creating PacProxyAgent with URI %o and options %o', uri, opts);
							 | 
						||
| 
								 | 
							
								        // Strip the "pac+" prefix
							 | 
						||
| 
								 | 
							
								        this.uri = uri.replace(/^pac\+/i, '');
							 | 
						||
| 
								 | 
							
								        this.opts = Object.assign({}, opts);
							 | 
						||
| 
								 | 
							
								        this.cache = undefined;
							 | 
						||
| 
								 | 
							
								        this.resolver = undefined;
							 | 
						||
| 
								 | 
							
								        this.resolverHash = '';
							 | 
						||
| 
								 | 
							
								        this.resolverPromise = undefined;
							 | 
						||
| 
								 | 
							
								        // For `PacResolver`
							 | 
						||
| 
								 | 
							
								        if (!this.opts.filename) {
							 | 
						||
| 
								 | 
							
								            this.opts.filename = uri;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Loads the PAC proxy file from the source if necessary, and returns
							 | 
						||
| 
								 | 
							
								     * a generated `FindProxyForURL()` resolver function to use.
							 | 
						||
| 
								 | 
							
								     *
							 | 
						||
| 
								 | 
							
								     * @api private
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    getResolver() {
							 | 
						||
| 
								 | 
							
								        if (!this.resolverPromise) {
							 | 
						||
| 
								 | 
							
								            this.resolverPromise = this.loadResolver();
							 | 
						||
| 
								 | 
							
								            this.resolverPromise.then(this.clearResolverPromise, this.clearResolverPromise);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return this.resolverPromise;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    loadResolver() {
							 | 
						||
| 
								 | 
							
								        return __awaiter(this, void 0, void 0, function* () {
							 | 
						||
| 
								 | 
							
								            try {
							 | 
						||
| 
								 | 
							
								                // (Re)load the contents of the PAC file URI
							 | 
						||
| 
								 | 
							
								                const code = yield this.loadPacFile();
							 | 
						||
| 
								 | 
							
								                // Create a sha1 hash of the JS code
							 | 
						||
| 
								 | 
							
								                const hash = crypto_1.default
							 | 
						||
| 
								 | 
							
								                    .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 = pac_resolver_1.default(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
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    loadPacFile() {
							 | 
						||
| 
								 | 
							
								        return __awaiter(this, void 0, void 0, function* () {
							 | 
						||
| 
								 | 
							
								            debug('Loading PAC file: %o', this.uri);
							 | 
						||
| 
								 | 
							
								            const rs = yield get_uri_1.default(this.uri, { cache: this.cache });
							 | 
						||
| 
								 | 
							
								            debug('Got `Readable` instance for URI');
							 | 
						||
| 
								 | 
							
								            this.cache = rs;
							 | 
						||
| 
								 | 
							
								            const buf = yield raw_body_1.default(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.
							 | 
						||
| 
								 | 
							
								     *
							 | 
						||
| 
								 | 
							
								     * @api protected
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    callback(req, opts) {
							 | 
						||
| 
								 | 
							
								        return __awaiter(this, void 0, void 0, function* () {
							 | 
						||
| 
								 | 
							
								            const { secureEndpoint } = opts;
							 | 
						||
| 
								 | 
							
								            // First, get a generated `FindProxyForURL()` function,
							 | 
						||
| 
								 | 
							
								            // either cached or retrieved from the source
							 | 
						||
| 
								 | 
							
								            const resolver = yield this.getResolver();
							 | 
						||
| 
								 | 
							
								            // Calculate the `url` parameter
							 | 
						||
| 
								 | 
							
								            const defaultPort = secureEndpoint ? 443 : 80;
							 | 
						||
| 
								 | 
							
								            let path = req.path;
							 | 
						||
| 
								 | 
							
								            let search = null;
							 | 
						||
| 
								 | 
							
								            const firstQuestion = path.indexOf('?');
							 | 
						||
| 
								 | 
							
								            if (firstQuestion !== -1) {
							 | 
						||
| 
								 | 
							
								                search = path.substring(firstQuestion);
							 | 
						||
| 
								 | 
							
								                path = path.substring(0, firstQuestion);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            const urlOpts = Object.assign(Object.assign({}, opts), { protocol: secureEndpoint ? 'https:' : 'http:', pathname: path, search, 
							 | 
						||
| 
								 | 
							
								                // need to use `hostname` instead of `host` otherwise `port` is ignored
							 | 
						||
| 
								 | 
							
								                hostname: opts.host, host: null, href: null, 
							 | 
						||
| 
								 | 
							
								                // set `port` to null when it is the protocol default port (80 / 443)
							 | 
						||
| 
								 | 
							
								                port: defaultPort === opts.port ? null : opts.port });
							 | 
						||
| 
								 | 
							
								            const url = url_1.format(urlOpts);
							 | 
						||
| 
								 | 
							
								            debug('url: %o', url);
							 | 
						||
| 
								 | 
							
								            let result = yield 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
							 | 
						||
| 
								 | 
							
								                    socket = secureEndpoint ? tls_1.default.connect(opts) : net_1.default.connect(opts);
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                else if (type === 'SOCKS' || type === 'SOCKS5') {
							 | 
						||
| 
								 | 
							
								                    // Use a SOCKSv5h proxy
							 | 
						||
| 
								 | 
							
								                    agent = new socks_proxy_agent_1.SocksProxyAgent(`socks://${target}`);
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                else if (type === 'SOCKS4') {
							 | 
						||
| 
								 | 
							
								                    // Use a SOCKSv4a proxy
							 | 
						||
| 
								 | 
							
								                    agent = new socks_proxy_agent_1.SocksProxyAgent(`socks4a://${target}`);
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                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}`;
							 | 
						||
| 
								 | 
							
								                    const proxyOpts = Object.assign(Object.assign({}, this.opts), url_1.parse(proxyURL));
							 | 
						||
| 
								 | 
							
								                    if (secureEndpoint) {
							 | 
						||
| 
								 | 
							
								                        agent = new https_proxy_agent_1.HttpsProxyAgent(proxyOpts);
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                    else {
							 | 
						||
| 
								 | 
							
								                        agent = new http_proxy_agent_1.HttpProxyAgent(proxyOpts);
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                try {
							 | 
						||
| 
								 | 
							
								                    if (socket) {
							 | 
						||
| 
								 | 
							
								                        // "DIRECT" connection, wait for connection confirmation
							 | 
						||
| 
								 | 
							
								                        yield once_1.default(socket, 'connect');
							 | 
						||
| 
								 | 
							
								                        req.emit('proxy', { proxy, socket });
							 | 
						||
| 
								 | 
							
								                        return socket;
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                    if (agent) {
							 | 
						||
| 
								 | 
							
								                        const s = yield agent.callback(req, opts);
							 | 
						||
| 
								 | 
							
								                        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)}`);
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								exports.default = PacProxyAgent;
							 | 
						||
| 
								 | 
							
								//# sourceMappingURL=agent.js.map
							 |