https-proxy-agent.js 5.6 KB

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