123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- /**
- * Module dependencies.
- */
- var net = require('net');
- var tls = require('tls');
- var url = require('url');
- var extend = require('extend');
- var Agent = require('agent-base');
- var inherits = require('util').inherits;
- var debug = require('debug')('https-proxy-agent');
- /**
- * Module exports.
- */
- module.exports = HttpsProxyAgent;
- /**
- * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to the
- * specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
- *
- * @api public
- */
- function HttpsProxyAgent (opts) {
- if (!(this instanceof HttpsProxyAgent)) return new HttpsProxyAgent(opts);
- if ('string' == typeof opts) opts = url.parse(opts);
- if (!opts) throw new Error('an HTTP(S) proxy server `host` and `port` must be specified!');
- debug('creating new HttpsProxyAgent instance: %o', opts);
- Agent.call(this, connect);
- var proxy = extend({}, opts);
- // if `true`, then connect to the proxy server over TLS. defaults to `false`.
- this.secureProxy = proxy.protocol ? /^https:?$/i.test(proxy.protocol) : false;
- // prefer `hostname` over `host`, and set the `port` if needed
- proxy.host = proxy.hostname || proxy.host;
- proxy.port = +proxy.port || (this.secureProxy ? 443 : 80);
- if (proxy.host && proxy.path) {
- // if both a `host` and `path` are specified then it's most likely the
- // result of a `url.parse()` call... we need to remove the `path` portion so
- // that `net.connect()` doesn't attempt to open that as a unix socket file.
- delete proxy.path;
- delete proxy.pathname;
- }
- this.proxy = proxy;
- }
- inherits(HttpsProxyAgent, Agent);
- /**
- * Called when the node-core HTTP client library is creating a new HTTP request.
- *
- * @api public
- */
- function connect (req, opts, fn) {
- var proxy = this.proxy;
- // create a socket connection to the proxy server
- var socket;
- if (this.secureProxy) {
- socket = tls.connect(proxy);
- } else {
- socket = net.connect(proxy);
- }
- // we need to buffer any HTTP traffic that happens with the proxy before we get
- // the CONNECT response, so that if the response is anything other than an "200"
- // response code, then we can re-play the "data" events on the socket once the
- // HTTP parser is hooked up...
- var buffers = [];
- var buffersLength = 0;
- function read () {
- var b = socket.read();
- if (b) ondata(b);
- else socket.once('readable', read);
- }
- function cleanup () {
- socket.removeListener('data', ondata);
- socket.removeListener('end', onend);
- socket.removeListener('error', onerror);
- socket.removeListener('close', onclose);
- socket.removeListener('readable', read);
- }
- function onclose (err) {
- debug('onclose had error %o', err);
- }
- function onend () {
- debug('onend');
- }
- function onerror (err) {
- cleanup();
- fn(err);
- }
- function ondata (b) {
- buffers.push(b);
- buffersLength += b.length;
- var buffered = Buffer.concat(buffers, buffersLength);
- var str = buffered.toString('ascii');
- if (!~str.indexOf('\r\n\r\n')) {
- // keep buffering
- debug('have not received end of HTTP headers yet...');
- if (socket.read) {
- read();
- } else {
- socket.once('data', ondata);
- }
- return;
- }
- var firstLine = str.substring(0, str.indexOf('\r\n'));
- var statusCode = +firstLine.split(' ')[1];
- debug('got proxy server response: %o', firstLine);
- if (200 == statusCode) {
- // 200 Connected status code!
- var sock = socket;
- // nullify the buffered data since we won't be needing it
- buffers = buffered = null;
- if (opts.secureEndpoint) {
- // since the proxy is connecting to an SSL server, we have
- // to upgrade this socket connection to an SSL connection
- debug('upgrading proxy-connected socket to TLS connection: %o', opts.host);
- opts.socket = socket;
- opts.servername = opts.host;
- opts.host = null;
- opts.hostname = null;
- opts.port = null;
- sock = tls.connect(opts);
- }
- cleanup();
- fn(null, sock);
- } else {
- // some other status code that's not 200... need to re-play the HTTP header
- // "data" events onto the socket once the HTTP machinery is attached so that
- // the user can parse and handle the error status code
- cleanup();
- // save a reference to the concat'd Buffer for the `onsocket` callback
- buffers = buffered;
- // need to wait for the "socket" event to re-play the "data" events
- req.once('socket', onsocket);
- fn(null, socket);
- }
- }
- function onsocket (socket) {
- // replay the "buffers" Buffer onto the `socket`, since at this point
- // the HTTP module machinery has been hooked up for the user
- if ('function' == typeof socket.ondata) {
- // node <= v0.11.3, the `ondata` function is set on the socket
- socket.ondata(buffers, 0, buffers.length);
- } else if (socket.listeners('data').length > 0) {
- // node > v0.11.3, the "data" event is listened for directly
- socket.emit('data', buffers);
- } else {
- // never?
- throw new Error('should not happen...');
- }
- // nullify the cached Buffer instance
- buffers = null;
- }
- socket.on('error', onerror);
- socket.on('close', onclose);
- socket.on('end', onend);
- if (socket.read) {
- read();
- } else {
- socket.once('data', ondata);
- }
- var hostname = opts.host + ':' + opts.port;
- var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n';
- var auth = proxy.auth;
- if (auth) {
- msg += 'Proxy-Authorization: Basic ' + new Buffer(auth).toString('base64') + '\r\n';
- }
- msg += 'Host: ' + hostname + '\r\n' +
- 'Connection: close\r\n' +
- '\r\n';
- socket.write(msg);
- };
|