agent.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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 url_1 = __importDefault(require("url"));
  18. const assert_1 = __importDefault(require("assert"));
  19. const debug_1 = __importDefault(require("debug"));
  20. const agent_base_1 = require("agent-base");
  21. const parse_proxy_response_1 = __importDefault(require("./parse-proxy-response"));
  22. const debug = debug_1.default('https-proxy-agent:agent');
  23. /**
  24. * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to
  25. * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
  26. *
  27. * Outgoing HTTP requests are first tunneled through the proxy server using the
  28. * `CONNECT` HTTP request method to establish a connection to the proxy server,
  29. * and then the proxy server connects to the destination target and issues the
  30. * HTTP request from the proxy server.
  31. *
  32. * `https:` requests have their socket connection upgraded to TLS once
  33. * the connection to the proxy server has been established.
  34. *
  35. * @api public
  36. */
  37. class HttpsProxyAgent extends agent_base_1.Agent {
  38. constructor(_opts) {
  39. let opts;
  40. if (typeof _opts === 'string') {
  41. opts = url_1.default.parse(_opts);
  42. }
  43. else {
  44. opts = _opts;
  45. }
  46. if (!opts) {
  47. throw new Error('an HTTP(S) proxy server `host` and `port` must be specified!');
  48. }
  49. debug('creating new HttpsProxyAgent instance: %o', opts);
  50. super(opts);
  51. const proxy = Object.assign({}, opts);
  52. // If `true`, then connect to the proxy server over TLS.
  53. // Defaults to `false`.
  54. this.secureProxy = opts.secureProxy || isHTTPS(proxy.protocol);
  55. // Prefer `hostname` over `host`, and set the `port` if needed.
  56. proxy.host = proxy.hostname || proxy.host;
  57. if (typeof proxy.port === 'string') {
  58. proxy.port = parseInt(proxy.port, 10);
  59. }
  60. if (!proxy.port && proxy.host) {
  61. proxy.port = this.secureProxy ? 443 : 80;
  62. }
  63. // ALPN is supported by Node.js >= v5.
  64. // attempt to negotiate http/1.1 for proxy servers that support http/2
  65. if (this.secureProxy && !('ALPNProtocols' in proxy)) {
  66. proxy.ALPNProtocols = ['http 1.1'];
  67. }
  68. if (proxy.host && proxy.path) {
  69. // If both a `host` and `path` are specified then it's most likely
  70. // the result of a `url.parse()` call... we need to remove the
  71. // `path` portion so that `net.connect()` doesn't attempt to open
  72. // that as a Unix socket file.
  73. delete proxy.path;
  74. delete proxy.pathname;
  75. }
  76. this.proxy = proxy;
  77. }
  78. /**
  79. * Called when the node-core HTTP client library is creating a
  80. * new HTTP request.
  81. *
  82. * @api protected
  83. */
  84. callback(req, opts) {
  85. return __awaiter(this, void 0, void 0, function* () {
  86. const { proxy, secureProxy } = this;
  87. // Create a socket connection to the proxy server.
  88. let socket;
  89. if (secureProxy) {
  90. debug('Creating `tls.Socket`: %o', proxy);
  91. socket = tls_1.default.connect(proxy);
  92. }
  93. else {
  94. debug('Creating `net.Socket`: %o', proxy);
  95. socket = net_1.default.connect(proxy);
  96. }
  97. const headers = Object.assign({}, proxy.headers);
  98. const hostname = `${opts.host}:${opts.port}`;
  99. let payload = `CONNECT ${hostname} HTTP/1.1\r\n`;
  100. // Inject the `Proxy-Authorization` header if necessary.
  101. if (proxy.auth) {
  102. headers['Proxy-Authorization'] = `Basic ${Buffer.from(proxy.auth).toString('base64')}`;
  103. }
  104. // The `Host` header should only include the port
  105. // number when it is not the default port.
  106. let { host, port, secureEndpoint } = opts;
  107. if (!isDefaultPort(port, secureEndpoint)) {
  108. host += `:${port}`;
  109. }
  110. headers.Host = host;
  111. headers.Connection = 'close';
  112. for (const name of Object.keys(headers)) {
  113. payload += `${name}: ${headers[name]}\r\n`;
  114. }
  115. const proxyResponsePromise = parse_proxy_response_1.default(socket);
  116. socket.write(`${payload}\r\n`);
  117. const { statusCode, buffered } = yield proxyResponsePromise;
  118. if (statusCode === 200) {
  119. req.once('socket', resume);
  120. if (opts.secureEndpoint) {
  121. const servername = opts.servername || opts.host;
  122. if (!servername) {
  123. throw new Error('Could not determine "servername"');
  124. }
  125. // The proxy is connecting to a TLS server, so upgrade
  126. // this socket connection to a TLS connection.
  127. debug('Upgrading socket connection to TLS');
  128. return tls_1.default.connect(Object.assign(Object.assign({}, omit(opts, 'host', 'hostname', 'path', 'port')), { socket,
  129. servername }));
  130. }
  131. return socket;
  132. }
  133. // Some other status code that's not 200... need to re-play the HTTP
  134. // header "data" events onto the socket once the HTTP machinery is
  135. // attached so that the node core `http` can parse and handle the
  136. // error status code.
  137. // Close the original socket, and a new "fake" socket is returned
  138. // instead, so that the proxy doesn't get the HTTP request
  139. // written to it (which may contain `Authorization` headers or other
  140. // sensitive data).
  141. //
  142. // See: https://hackerone.com/reports/541502
  143. socket.destroy();
  144. const fakeSocket = new net_1.default.Socket();
  145. fakeSocket.readable = true;
  146. // Need to wait for the "socket" event to re-play the "data" events.
  147. req.once('socket', (s) => {
  148. debug('replaying proxy buffer for failed request');
  149. assert_1.default(s.listenerCount('data') > 0);
  150. // Replay the "buffered" Buffer onto the fake `socket`, since at
  151. // this point the HTTP module machinery has been hooked up for
  152. // the user.
  153. s.push(buffered);
  154. s.push(null);
  155. });
  156. return fakeSocket;
  157. });
  158. }
  159. }
  160. exports.default = HttpsProxyAgent;
  161. function resume(socket) {
  162. socket.resume();
  163. }
  164. function isDefaultPort(port, secure) {
  165. return Boolean((!secure && port === 80) || (secure && port === 443));
  166. }
  167. function isHTTPS(protocol) {
  168. return typeof protocol === 'string' ? /^https:?$/i.test(protocol) : false;
  169. }
  170. function omit(obj, ...keys) {
  171. const ret = {};
  172. let key;
  173. for (key in obj) {
  174. if (!keys.includes(key)) {
  175. ret[key] = obj[key];
  176. }
  177. }
  178. return ret;
  179. }
  180. //# sourceMappingURL=agent.js.map