index.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. var {
  2. _nullishCoalesce,
  3. _optionalChain
  4. } = require('@sentry/utils');
  5. Object.defineProperty(exports, '__esModule', { value: true });
  6. const assert = require('assert');
  7. const net = require('net');
  8. const tls = require('tls');
  9. const url = require('url');
  10. const utils = require('@sentry/utils');
  11. const base = require('./base.js');
  12. const parseProxyResponse = require('./parse-proxy-response.js');
  13. /**
  14. * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7
  15. * With the following licence:
  16. *
  17. * (The MIT License)
  18. *
  19. * Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>*
  20. *
  21. * Permission is hereby granted, free of charge, to any person obtaining
  22. * a copy of this software and associated documentation files (the
  23. * 'Software'), to deal in the Software without restriction, including
  24. * without limitation the rights to use, copy, modify, merge, publish,
  25. * distribute, sublicense, and/or sell copies of the Software, and to
  26. * permit persons to whom the Software is furnished to do so, subject to
  27. * the following conditions:*
  28. *
  29. * The above copyright notice and this permission notice shall be
  30. * included in all copies or substantial portions of the Software.*
  31. *
  32. * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  33. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  34. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  35. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  36. * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  37. * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  38. * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  39. */
  40. function debug(...args) {
  41. utils.logger.log('[https-proxy-agent]', ...args);
  42. }
  43. /**
  44. * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to
  45. * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
  46. *
  47. * Outgoing HTTP requests are first tunneled through the proxy server using the
  48. * `CONNECT` HTTP request method to establish a connection to the proxy server,
  49. * and then the proxy server connects to the destination target and issues the
  50. * HTTP request from the proxy server.
  51. *
  52. * `https:` requests have their socket connection upgraded to TLS once
  53. * the connection to the proxy server has been established.
  54. */
  55. class HttpsProxyAgent extends base.Agent {
  56. static __initStatic() {this.protocols = ['http', 'https']; }
  57. constructor(proxy, opts) {
  58. super(opts);
  59. this.options = {};
  60. this.proxy = typeof proxy === 'string' ? new url.URL(proxy) : proxy;
  61. this.proxyHeaders = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _2 => _2.headers]), () => ( {}));
  62. debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href);
  63. // Trim off the brackets from IPv6 addresses
  64. const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, '');
  65. const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.proxy.protocol === 'https:' ? 443 : 80;
  66. this.connectOpts = {
  67. // Attempt to negotiate http/1.1 for proxy servers that support http/2
  68. ALPNProtocols: ['http/1.1'],
  69. ...(opts ? omit(opts, 'headers') : null),
  70. host,
  71. port,
  72. };
  73. }
  74. /**
  75. * Called when the node-core HTTP client library is creating a
  76. * new HTTP request.
  77. */
  78. async connect(req, opts) {
  79. const { proxy } = this;
  80. if (!opts.host) {
  81. throw new TypeError('No "host" provided');
  82. }
  83. // Create a socket connection to the proxy server.
  84. let socket;
  85. if (proxy.protocol === 'https:') {
  86. debug('Creating `tls.Socket`: %o', this.connectOpts);
  87. const servername = this.connectOpts.servername || this.connectOpts.host;
  88. socket = tls.connect({
  89. ...this.connectOpts,
  90. servername: servername && net.isIP(servername) ? undefined : servername,
  91. });
  92. } else {
  93. debug('Creating `net.Socket`: %o', this.connectOpts);
  94. socket = net.connect(this.connectOpts);
  95. }
  96. const headers =
  97. typeof this.proxyHeaders === 'function' ? this.proxyHeaders() : { ...this.proxyHeaders };
  98. const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host;
  99. let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`;
  100. // Inject the `Proxy-Authorization` header if necessary.
  101. if (proxy.username || proxy.password) {
  102. const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
  103. headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`;
  104. }
  105. headers.Host = `${host}:${opts.port}`;
  106. if (!headers['Proxy-Connection']) {
  107. headers['Proxy-Connection'] = this.keepAlive ? 'Keep-Alive' : 'close';
  108. }
  109. for (const name of Object.keys(headers)) {
  110. payload += `${name}: ${headers[name]}\r\n`;
  111. }
  112. const proxyResponsePromise = parseProxyResponse.parseProxyResponse(socket);
  113. socket.write(`${payload}\r\n`);
  114. const { connect, buffered } = await proxyResponsePromise;
  115. req.emit('proxyConnect', connect);
  116. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  117. // @ts-ignore Not EventEmitter in Node types
  118. this.emit('proxyConnect', connect, req);
  119. if (connect.statusCode === 200) {
  120. req.once('socket', resume);
  121. if (opts.secureEndpoint) {
  122. // The proxy is connecting to a TLS server, so upgrade
  123. // this socket connection to a TLS connection.
  124. debug('Upgrading socket connection to TLS');
  125. const servername = opts.servername || opts.host;
  126. return tls.connect({
  127. ...omit(opts, 'host', 'path', 'port'),
  128. socket,
  129. servername: net.isIP(servername) ? undefined : servername,
  130. });
  131. }
  132. return socket;
  133. }
  134. // Some other status code that's not 200... need to re-play the HTTP
  135. // header "data" events onto the socket once the HTTP machinery is
  136. // attached so that the node core `http` can parse and handle the
  137. // error status code.
  138. // Close the original socket, and a new "fake" socket is returned
  139. // instead, so that the proxy doesn't get the HTTP request
  140. // written to it (which may contain `Authorization` headers or other
  141. // sensitive data).
  142. //
  143. // See: https://hackerone.com/reports/541502
  144. socket.destroy();
  145. const fakeSocket = new net.Socket({ writable: false });
  146. fakeSocket.readable = true;
  147. // Need to wait for the "socket" event to re-play the "data" events.
  148. req.once('socket', (s) => {
  149. debug('Replaying proxy buffer for failed request');
  150. assert.default(s.listenerCount('data') > 0);
  151. // Replay the "buffered" Buffer onto the fake `socket`, since at
  152. // this point the HTTP module machinery has been hooked up for
  153. // the user.
  154. s.push(buffered);
  155. s.push(null);
  156. });
  157. return fakeSocket;
  158. }
  159. }HttpsProxyAgent.__initStatic();
  160. function resume(socket) {
  161. socket.resume();
  162. }
  163. function omit(
  164. obj,
  165. ...keys
  166. )
  167. {
  168. const ret = {}
  169. ;
  170. let key;
  171. for (key in obj) {
  172. if (!keys.includes(key)) {
  173. ret[key] = obj[key];
  174. }
  175. }
  176. return ret;
  177. }
  178. exports.HttpsProxyAgent = HttpsProxyAgent;
  179. //# sourceMappingURL=index.js.map