index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * Module dependencies.
  3. */
  4. var net = require('net');
  5. var tls = require('tls');
  6. var url = require('url');
  7. var Agent = require('agent-base');
  8. var inherits = require('util').inherits;
  9. var debug = require('debug')('https-proxy-agent');
  10. /**
  11. * Module exports.
  12. */
  13. module.exports = HttpsProxyAgent;
  14. /**
  15. * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to the
  16. * specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
  17. *
  18. * @api public
  19. */
  20. function HttpsProxyAgent(opts) {
  21. if (!(this instanceof HttpsProxyAgent)) return new HttpsProxyAgent(opts);
  22. if ('string' == typeof opts) opts = url.parse(opts);
  23. if (!opts)
  24. throw new Error(
  25. 'an HTTP(S) proxy server `host` and `port` must be specified!'
  26. );
  27. debug('creating new HttpsProxyAgent instance: %o', opts);
  28. Agent.call(this, opts);
  29. var proxy = Object.assign({}, opts);
  30. // if `true`, then connect to the proxy server over TLS. defaults to `false`.
  31. this.secureProxy = proxy.protocol ? /^https:?$/i.test(proxy.protocol) : false;
  32. // prefer `hostname` over `host`, and set the `port` if needed
  33. proxy.host = proxy.hostname || proxy.host;
  34. proxy.port = +proxy.port || (this.secureProxy ? 443 : 80);
  35. // ALPN is supported by Node.js >= v5.
  36. // attempt to negotiate http/1.1 for proxy servers that support http/2
  37. if (this.secureProxy && !('ALPNProtocols' in proxy)) {
  38. proxy.ALPNProtocols = ['http 1.1']
  39. }
  40. if (proxy.host && proxy.path) {
  41. // if both a `host` and `path` are specified then it's most likely the
  42. // result of a `url.parse()` call... we need to remove the `path` portion so
  43. // that `net.connect()` doesn't attempt to open that as a unix socket file.
  44. delete proxy.path;
  45. delete proxy.pathname;
  46. }
  47. this.proxy = proxy;
  48. this.defaultPort = 443;
  49. }
  50. inherits(HttpsProxyAgent, Agent);
  51. /**
  52. * Called when the node-core HTTP client library is creating a new HTTP request.
  53. *
  54. * @api public
  55. */
  56. HttpsProxyAgent.prototype.callback = function connect(req, opts, fn) {
  57. var proxy = this.proxy;
  58. // create a socket connection to the proxy server
  59. var socket;
  60. if (this.secureProxy) {
  61. socket = tls.connect(proxy);
  62. } else {
  63. socket = net.connect(proxy);
  64. }
  65. // we need to buffer any HTTP traffic that happens with the proxy before we get
  66. // the CONNECT response, so that if the response is anything other than an "200"
  67. // response code, then we can re-play the "data" events on the socket once the
  68. // HTTP parser is hooked up...
  69. var buffers = [];
  70. var buffersLength = 0;
  71. function read() {
  72. var b = socket.read();
  73. if (b) ondata(b);
  74. else socket.once('readable', read);
  75. }
  76. function cleanup() {
  77. socket.removeListener('data', ondata);
  78. socket.removeListener('end', onend);
  79. socket.removeListener('error', onerror);
  80. socket.removeListener('close', onclose);
  81. socket.removeListener('readable', read);
  82. }
  83. function onclose(err) {
  84. debug('onclose had error %o', err);
  85. }
  86. function onend() {
  87. debug('onend');
  88. }
  89. function onerror(err) {
  90. cleanup();
  91. fn(err);
  92. }
  93. function ondata(b) {
  94. buffers.push(b);
  95. buffersLength += b.length;
  96. var buffered = Buffer.concat(buffers, buffersLength);
  97. var str = buffered.toString('ascii');
  98. if (!~str.indexOf('\r\n\r\n')) {
  99. // keep buffering
  100. debug('have not received end of HTTP headers yet...');
  101. if (socket.read) {
  102. read();
  103. } else {
  104. socket.once('data', ondata);
  105. }
  106. return;
  107. }
  108. var firstLine = str.substring(0, str.indexOf('\r\n'));
  109. var statusCode = +firstLine.split(' ')[1];
  110. debug('got proxy server response: %o', firstLine);
  111. if (200 == statusCode) {
  112. // 200 Connected status code!
  113. var sock = socket;
  114. // nullify the buffered data since we won't be needing it
  115. buffers = buffered = null;
  116. if (opts.secureEndpoint) {
  117. // since the proxy is connecting to an SSL server, we have
  118. // to upgrade this socket connection to an SSL connection
  119. debug(
  120. 'upgrading proxy-connected socket to TLS connection: %o',
  121. opts.host
  122. );
  123. opts.socket = socket;
  124. opts.servername = opts.servername || opts.host;
  125. opts.host = null;
  126. opts.hostname = null;
  127. opts.port = null;
  128. sock = tls.connect(opts);
  129. }
  130. cleanup();
  131. fn(null, sock);
  132. } else {
  133. // some other status code that's not 200... need to re-play the HTTP header
  134. // "data" events onto the socket once the HTTP machinery is attached so that
  135. // the user can parse and handle the error status code
  136. cleanup();
  137. // save a reference to the concat'd Buffer for the `onsocket` callback
  138. buffers = buffered;
  139. // need to wait for the "socket" event to re-play the "data" events
  140. req.once('socket', onsocket);
  141. fn(null, socket);
  142. }
  143. }
  144. function onsocket(socket) {
  145. // replay the "buffers" Buffer onto the `socket`, since at this point
  146. // the HTTP module machinery has been hooked up for the user
  147. if ('function' == typeof socket.ondata) {
  148. // node <= v0.11.3, the `ondata` function is set on the socket
  149. socket.ondata(buffers, 0, buffers.length);
  150. } else if (socket.listeners('data').length > 0) {
  151. // node > v0.11.3, the "data" event is listened for directly
  152. socket.emit('data', buffers);
  153. } else {
  154. // never?
  155. throw new Error('should not happen...');
  156. }
  157. // nullify the cached Buffer instance
  158. buffers = null;
  159. }
  160. socket.on('error', onerror);
  161. socket.on('close', onclose);
  162. socket.on('end', onend);
  163. if (socket.read) {
  164. read();
  165. } else {
  166. socket.once('data', ondata);
  167. }
  168. var hostname = opts.host + ':' + opts.port;
  169. var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n';
  170. var headers = Object.assign({}, proxy.headers);
  171. if (proxy.auth) {
  172. headers['Proxy-Authorization'] =
  173. 'Basic ' + Buffer.from(proxy.auth).toString('base64');
  174. }
  175. // the Host header should only include the port
  176. // number when it is a non-standard port
  177. var host = opts.host;
  178. if (!isDefaultPort(opts.port, opts.secureEndpoint)) {
  179. host += ':' + opts.port;
  180. }
  181. headers['Host'] = host;
  182. headers['Connection'] = 'close';
  183. Object.keys(headers).forEach(function(name) {
  184. msg += name + ': ' + headers[name] + '\r\n';
  185. });
  186. socket.write(msg + '\r\n');
  187. };
  188. function isDefaultPort(port, secure) {
  189. return Boolean((!secure && port === 80) || (secure && port === 443));
  190. }