agent.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. var __importDefault = (this && this.__importDefault) || function (mod) {
  12. return (mod && mod.__esModule) ? mod : { "default": mod };
  13. };
  14. Object.defineProperty(exports, "__esModule", { value: true });
  15. const net_1 = __importDefault(require("net"));
  16. const tls_1 = __importDefault(require("tls"));
  17. const once_1 = __importDefault(require("@tootallnate/once"));
  18. const crypto_1 = __importDefault(require("crypto"));
  19. const get_uri_1 = __importDefault(require("get-uri"));
  20. const debug_1 = __importDefault(require("debug"));
  21. const raw_body_1 = __importDefault(require("raw-body"));
  22. const url_1 = require("url");
  23. const http_proxy_agent_1 = require("http-proxy-agent");
  24. const https_proxy_agent_1 = require("https-proxy-agent");
  25. const socks_proxy_agent_1 = require("socks-proxy-agent");
  26. const pac_resolver_1 = __importDefault(require("pac-resolver"));
  27. const agent_base_1 = require("agent-base");
  28. const debug = debug_1.default('pac-proxy-agent');
  29. /**
  30. * The `PacProxyAgent` class.
  31. *
  32. * A few different "protocol" modes are supported (supported protocols are
  33. * backed by the `get-uri` module):
  34. *
  35. * - "pac+data", "data" - refers to an embedded "data:" URI
  36. * - "pac+file", "file" - refers to a local file
  37. * - "pac+ftp", "ftp" - refers to a file located on an FTP server
  38. * - "pac+http", "http" - refers to an HTTP endpoint
  39. * - "pac+https", "https" - refers to an HTTPS endpoint
  40. *
  41. * @api public
  42. */
  43. class PacProxyAgent extends agent_base_1.Agent {
  44. constructor(uri, opts = {}) {
  45. super(opts);
  46. this.clearResolverPromise = () => {
  47. this.resolverPromise = undefined;
  48. };
  49. debug('Creating PacProxyAgent with URI %o and options %o', uri, opts);
  50. // Strip the "pac+" prefix
  51. this.uri = uri.replace(/^pac\+/i, '');
  52. this.opts = Object.assign({}, opts);
  53. this.cache = undefined;
  54. this.resolver = undefined;
  55. this.resolverHash = '';
  56. this.resolverPromise = undefined;
  57. // For `PacResolver`
  58. if (!this.opts.filename) {
  59. this.opts.filename = uri;
  60. }
  61. }
  62. /**
  63. * Loads the PAC proxy file from the source if necessary, and returns
  64. * a generated `FindProxyForURL()` resolver function to use.
  65. *
  66. * @api private
  67. */
  68. getResolver() {
  69. if (!this.resolverPromise) {
  70. this.resolverPromise = this.loadResolver();
  71. this.resolverPromise.then(this.clearResolverPromise, this.clearResolverPromise);
  72. }
  73. return this.resolverPromise;
  74. }
  75. loadResolver() {
  76. return __awaiter(this, void 0, void 0, function* () {
  77. try {
  78. // (Re)load the contents of the PAC file URI
  79. const code = yield this.loadPacFile();
  80. // Create a sha1 hash of the JS code
  81. const hash = crypto_1.default
  82. .createHash('sha1')
  83. .update(code)
  84. .digest('hex');
  85. if (this.resolver && this.resolverHash === hash) {
  86. debug('Same sha1 hash for code - contents have not changed, reusing previous proxy resolver');
  87. return this.resolver;
  88. }
  89. // Cache the resolver
  90. debug('Creating new proxy resolver instance');
  91. this.resolver = pac_resolver_1.default(code, this.opts);
  92. // Store that sha1 hash for future comparison purposes
  93. this.resolverHash = hash;
  94. return this.resolver;
  95. }
  96. catch (err) {
  97. if (this.resolver && err.code === 'ENOTMODIFIED') {
  98. debug('Got ENOTMODIFIED response, reusing previous proxy resolver');
  99. return this.resolver;
  100. }
  101. throw err;
  102. }
  103. });
  104. }
  105. /**
  106. * Loads the contents of the PAC proxy file.
  107. *
  108. * @api private
  109. */
  110. loadPacFile() {
  111. return __awaiter(this, void 0, void 0, function* () {
  112. debug('Loading PAC file: %o', this.uri);
  113. const rs = yield get_uri_1.default(this.uri, { cache: this.cache });
  114. debug('Got `Readable` instance for URI');
  115. this.cache = rs;
  116. const buf = yield raw_body_1.default(rs);
  117. debug('Read %o byte PAC file from URI', buf.length);
  118. return buf.toString('utf8');
  119. });
  120. }
  121. /**
  122. * Called when the node-core HTTP client library is creating a new HTTP request.
  123. *
  124. * @api protected
  125. */
  126. callback(req, opts) {
  127. return __awaiter(this, void 0, void 0, function* () {
  128. const { secureEndpoint } = opts;
  129. // First, get a generated `FindProxyForURL()` function,
  130. // either cached or retrieved from the source
  131. const resolver = yield this.getResolver();
  132. // Calculate the `url` parameter
  133. const defaultPort = secureEndpoint ? 443 : 80;
  134. let path = req.path;
  135. let search = null;
  136. const firstQuestion = path.indexOf('?');
  137. if (firstQuestion !== -1) {
  138. search = path.substring(firstQuestion);
  139. path = path.substring(0, firstQuestion);
  140. }
  141. const urlOpts = Object.assign(Object.assign({}, opts), { protocol: secureEndpoint ? 'https:' : 'http:', pathname: path, search,
  142. // need to use `hostname` instead of `host` otherwise `port` is ignored
  143. hostname: opts.host, host: null, href: null,
  144. // set `port` to null when it is the protocol default port (80 / 443)
  145. port: defaultPort === opts.port ? null : opts.port });
  146. const url = url_1.format(urlOpts);
  147. debug('url: %o', url);
  148. let result = yield resolver(url);
  149. // Default to "DIRECT" if a falsey value was returned (or nothing)
  150. if (!result) {
  151. result = 'DIRECT';
  152. }
  153. const proxies = String(result)
  154. .trim()
  155. .split(/\s*;\s*/g)
  156. .filter(Boolean);
  157. if (this.opts.fallbackToDirect && !proxies.includes('DIRECT')) {
  158. proxies.push('DIRECT');
  159. }
  160. for (const proxy of proxies) {
  161. let agent = null;
  162. let socket = null;
  163. const [type, target] = proxy.split(/\s+/);
  164. debug('Attempting to use proxy: %o', proxy);
  165. if (type === 'DIRECT') {
  166. // Direct connection to the destination endpoint
  167. socket = secureEndpoint ? tls_1.default.connect(opts) : net_1.default.connect(opts);
  168. }
  169. else if (type === 'SOCKS' || type === 'SOCKS5') {
  170. // Use a SOCKSv5h proxy
  171. agent = new socks_proxy_agent_1.SocksProxyAgent(`socks://${target}`);
  172. }
  173. else if (type === 'SOCKS4') {
  174. // Use a SOCKSv4a proxy
  175. agent = new socks_proxy_agent_1.SocksProxyAgent(`socks4a://${target}`);
  176. }
  177. else if (type === 'PROXY' ||
  178. type === 'HTTP' ||
  179. type === 'HTTPS') {
  180. // Use an HTTP or HTTPS proxy
  181. // http://dev.chromium.org/developers/design-documents/secure-web-proxy
  182. const proxyURL = `${type === 'HTTPS' ? 'https' : 'http'}://${target}`;
  183. const proxyOpts = Object.assign(Object.assign({}, this.opts), url_1.parse(proxyURL));
  184. if (secureEndpoint) {
  185. agent = new https_proxy_agent_1.HttpsProxyAgent(proxyOpts);
  186. }
  187. else {
  188. agent = new http_proxy_agent_1.HttpProxyAgent(proxyOpts);
  189. }
  190. }
  191. try {
  192. if (socket) {
  193. // "DIRECT" connection, wait for connection confirmation
  194. yield once_1.default(socket, 'connect');
  195. req.emit('proxy', { proxy, socket });
  196. return socket;
  197. }
  198. if (agent) {
  199. const s = yield agent.callback(req, opts);
  200. req.emit('proxy', { proxy, socket: s });
  201. return s;
  202. }
  203. throw new Error(`Could not determine proxy type for: ${proxy}`);
  204. }
  205. catch (err) {
  206. debug('Got error for proxy %o: %o', proxy, err);
  207. req.emit('proxy', { proxy, error: err });
  208. }
  209. }
  210. throw new Error(`Failed to establish a socket connection to proxies: ${JSON.stringify(proxies)}`);
  211. });
  212. }
  213. }
  214. exports.default = PacProxyAgent;
  215. //# sourceMappingURL=agent.js.map