123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /**
- * Module exports.
- */
- module.exports = exports = PacProxyAgent;
- /**
- * Supported "protocols". Delegates out to the `get-uri` module.
- */
- var getUri = require('get-uri');
- Object.defineProperty(exports, 'protocols', {
- enumerable: true,
- configurable: true,
- get: function () { return Object.keys(getUri.protocols); }
- });
- /**
- * Module dependencies.
- */
- var net = require('net');
- var tls = require('tls');
- var crypto = require('crypto');
- var parse = require('url').parse;
- var format = require('url').format;
- var extend = require('extend');
- var Agent = require('agent-base');
- var HttpProxyAgent = require('http-proxy-agent');
- var HttpsProxyAgent = require('https-proxy-agent');
- var SocksProxyAgent = require('socks-proxy-agent');
- var PacResolver = require('pac-resolver');
- var toBuffer = require('stream-to-buffer');
- var inherits = require('util').inherits;
- var debug = require('debug')('pac-proxy-agent');
- /**
- * The `PacProxyAgent` class.
- *
- * A few different "protocol" modes are supported (supported protocols are
- * backed by the `get-uri` module):
- *
- * - "pac+data", "data" - refers to an embedded "data:" URI
- * - "pac+file", "file" - refers to a local file
- * - "pac+ftp", "ftp" - refers to a file located on an FTP server
- * - "pac+http", "http" - refers to an HTTP endpoint
- * - "pac+https", "https" - refers to an HTTPS endpoint
- *
- * @api public
- */
- function PacProxyAgent (uri, opts) {
- if (!(this instanceof PacProxyAgent)) return new PacProxyAgent(uri, opts);
- // was an options object passed in first?
- if ('object' === typeof uri) {
- opts = uri;
- // result of a url.parse() call?
- if (opts.href) {
- if (opts.path && !opts.pathname) {
- opts.pathname = opts.path;
- }
- opts.slashes = true;
- uri = format(opts);
- } else {
- uri = opts.uri;
- }
- }
- if (!opts) opts = {};
- if (!uri) throw new Error('a PAC file URI must be specified!');
- debug('creating PacProxyAgent with URI %o and options %o', uri, opts);
- Agent.call(this, connect);
- // strip the "pac+" prefix
- this.uri = uri.replace(/^pac\+/i, '');
- this.sandbox = opts.sandox;
- this.proxy = opts;
- this.cache = this._resolver = null;
- }
- inherits(PacProxyAgent, Agent);
- /**
- * Loads the PAC proxy file from the source if necessary, and returns
- * a generated `FindProxyForURL()` resolver function to use.
- *
- * @param {Function} fn callback function
- * @api private
- */
- PacProxyAgent.prototype.loadResolver = function (fn) {
- var self = this;
- // kick things off by attempting to (re)load the contents of the PAC file URI
- this.loadPacFile(onpacfile);
- // loadPacFile() callback function
- function onpacfile (err, code) {
- if (err) {
- if ('ENOTMODIFIED' == err.code) {
- debug('got ENOTMODIFIED response, reusing previous proxy resolver');
- fn(null, self._resolver);
- } else {
- fn(err);
- }
- return;
- }
- // create a sha1 hash of the JS code
- var hash = crypto.createHash('sha1').update(code).digest('hex');
- if (self._resolver && self._resolver.hash == hash) {
- debug('same sha1 hash for code - contents have not changed, reusing previous proxy resolver');
- fn(null, self._resolver);
- return;
- }
- // cache the resolver
- debug('creating new proxy resolver instance');
- self._resolver = new PacResolver(code, {
- filename: self.uri,
- sandbox: self.sandbox
- });
- // store that sha1 hash on the resolver instance
- // for future comparison purposes
- self._resolver.hash = hash;
- fn(null, self._resolver);
- }
- };
- /**
- * Loads the contents of the PAC proxy file.
- *
- * @param {Function} fn callback function
- * @api private
- */
- PacProxyAgent.prototype.loadPacFile = function (fn) {
- debug('loading PAC file: %o', this.uri);
- var self = this;
- // delegate out to the `get-uri` module
- var opts = {};
- if (this.cache) {
- opts.cache = this.cache;
- }
- getUri(this.uri, opts, onstream);
- function onstream (err, rs) {
- if (err) return fn(err);
- debug('got stream.Readable instance for URI');
- self.cache = rs;
- toBuffer(rs, onbuffer);
- }
- function onbuffer (err, buf) {
- if (err) return fn(err);
- debug('read %o byte PAC file from URI', buf.length);
- fn(null, buf.toString('utf8'));
- }
- };
- /**
- * Called when the node-core HTTP client library is creating a new HTTP request.
- *
- * @api public
- */
- function connect (req, opts, fn) {
- var url;
- var host;
- var self = this;
- var secure = Boolean(opts.secureEndpoint);
- // first we need get a generated FindProxyForURL() function,
- // either cached or retreived from the source
- this.loadResolver(onresolver);
- // `loadResolver()` callback function
- function onresolver (err, FindProxyForURL) {
- if (err) return fn(err);
- // calculate the `url` parameter
- var defaultPort = secure ? 443 : 80;
- var path = req.path;
- var firstQuestion = path.indexOf('?');
- var search;
- if (-1 != firstQuestion) {
- search = path.substring(firstQuestion);
- path = path.substring(0, firstQuestion);
- }
- url = format(extend({}, opts, {
- protocol: secure ? 'https:' : 'http:',
- pathname: path,
- search: search,
- // need to use `hostname` instead of `host` otherwise `port` is ignored
- hostname: opts.host,
- host: null,
- // set `port` to null when it is the protocol default port (80 / 443)
- port: defaultPort == opts.port ? null : opts.port
- }));
- // calculate the `host` parameter
- host = parse(url).hostname;
- debug('url: %o, host: %o', url, host);
- FindProxyForURL(url, host, onproxy);
- }
- // `FindProxyForURL()` callback function
- function onproxy (err, proxy) {
- if (err) return fn(err);
- // default to "DIRECT" if a falsey value was returned (or nothing)
- if (!proxy) proxy = 'DIRECT';
- var proxies = String(proxy).trim().split(/\s*;\s*/g).filter(Boolean);
- // XXX: right now, only the first proxy specified will be used
- var first = proxies[0];
- debug('using proxy: %o', first);
- var agent;
- var parts = first.split(/\s+/);
- var type = parts[0];
- if ('DIRECT' == type) {
- // direct connection to the destination endpoint
- var socket;
- if (secure) {
- socket = tls.connect(opts);
- } else {
- socket = net.connect(opts);
- }
- return fn(null, socket);
- } else if ('SOCKS' == type) {
- // use a SOCKS proxy
- agent = new SocksProxyAgent('socks://' + parts[1]);
- } else if ('PROXY' == type || 'HTTPS' == type) {
- // use an HTTP or HTTPS proxy
- // http://dev.chromium.org/developers/design-documents/secure-web-proxy
- var proxyURL = ('HTTPS' === type ? 'https' : 'http') + '://' + parts[1];
- var proxy = extend({}, self.proxy, parse(proxyURL));
- if (secure) {
- agent = new HttpsProxyAgent(proxy);
- } else {
- agent = new HttpProxyAgent(proxy);
- }
- } else {
- throw new Error('Unknown proxy type: ' + type);
- }
- if (agent) agent.callback(req, opts, fn);
- }
- }
|