index.js 9.9 KB

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