1 2/** 3 * Module dependencies. 4 */ 5 6var net = require('net'); 7var tls = require('tls'); 8var url = require('url'); 9var Agent = require('agent-base'); 10var inherits = require('util').inherits; 11var debug = require('debug')('http-proxy-agent'); 12 13/** 14 * Module exports. 15 */ 16 17module.exports = HttpProxyAgent; 18 19/** 20 * The `HttpProxyAgent` implements an HTTP Agent subclass that connects to the 21 * specified "HTTP proxy server" in order to proxy HTTP requests. 22 * 23 * @api public 24 */ 25 26function HttpProxyAgent (opts) { 27 if (!(this instanceof HttpProxyAgent)) return new HttpProxyAgent(opts); 28 if ('string' == typeof opts) opts = url.parse(opts); 29 if (!opts) throw new Error('an HTTP(S) proxy server `host` and `port` must be specified!'); 30 debug('creating new HttpProxyAgent instance: %o', opts); 31 Agent.call(this, opts); 32 33 var proxy = Object.assign({}, opts); 34 35 // if `true`, then connect to the proxy server over TLS. defaults to `false`. 36 this.secureProxy = proxy.protocol ? /^https:?$/i.test(proxy.protocol) : false; 37 38 // prefer `hostname` over `host`, and set the `port` if needed 39 proxy.host = proxy.hostname || proxy.host; 40 proxy.port = +proxy.port || (this.secureProxy ? 443 : 80); 41 42 if (proxy.host && proxy.path) { 43 // if both a `host` and `path` are specified then it's most likely the 44 // result of a `url.parse()` call... we need to remove the `path` portion so 45 // that `net.connect()` doesn't attempt to open that as a unix socket file. 46 delete proxy.path; 47 delete proxy.pathname; 48 } 49 50 this.proxy = proxy; 51} 52inherits(HttpProxyAgent, Agent); 53 54/** 55 * Called when the node-core HTTP client library is creating a new HTTP request. 56 * 57 * @api public 58 */ 59 60HttpProxyAgent.prototype.callback = function connect (req, opts, fn) { 61 var proxy = this.proxy; 62 63 // change the `http.ClientRequest` instance's "path" field 64 // to the absolute path of the URL that will be requested 65 var parsed = url.parse(req.path); 66 if (null == parsed.protocol) parsed.protocol = 'http:'; 67 if (null == parsed.hostname) parsed.hostname = opts.hostname || opts.host; 68 if (null == parsed.port) parsed.port = opts.port; 69 if (parsed.port == 80) { 70 // if port is 80, then we can remove the port so that the 71 // ":80" portion is not on the produced URL 72 delete parsed.port; 73 } 74 var absolute = url.format(parsed); 75 req.path = absolute; 76 77 // inject the `Proxy-Authorization` header if necessary 78 if (proxy.auth) { 79 req.setHeader( 80 'Proxy-Authorization', 81 'Basic ' + Buffer.from(proxy.auth).toString('base64') 82 ); 83 } 84 85 // create a socket connection to the proxy server 86 var socket; 87 if (this.secureProxy) { 88 socket = tls.connect(proxy); 89 } else { 90 socket = net.connect(proxy); 91 } 92 93 // at this point, the http ClientRequest's internal `_header` field might have 94 // already been set. If this is the case then we'll need to re-generate the 95 // string since we just changed the `req.path` 96 if (req._header) { 97 debug('regenerating stored HTTP header string for request'); 98 req._header = null; 99 req._implicitHeader(); 100 if (req.output && req.output.length > 0) { 101 debug('patching connection write() output buffer with updated header'); 102 // the _header has already been queued to be written to the socket 103 var first = req.output[0]; 104 var endOfHeaders = first.indexOf('\r\n\r\n') + 4; 105 req.output[0] = req._header + first.substring(endOfHeaders); 106 debug('output buffer: %o', req.output); 107 } 108 } 109 110 fn(null, socket); 111}; 112