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
						
					
					
				| "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
 |