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
 |