123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- var common = exports,
- url = require('url'),
- extend = require('util')._extend,
- required = require('requires-port');
- var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i,
- isSSL = /^https|wss/;
- /**
- * Simple Regex for testing if protocol is https
- */
- common.isSSL = isSSL;
- /**
- * Copies the right headers from `options` and `req` to
- * `outgoing` which is then used to fire the proxied
- * request.
- *
- * Examples:
- *
- * common.setupOutgoing(outgoing, options, req)
- * // => { host: ..., hostname: ...}
- *
- * @param {Object} Outgoing Base object to be filled with required properties
- * @param {Object} Options Config object passed to the proxy
- * @param {ClientRequest} Req Request Object
- * @param {String} Forward String to select forward or target
- *
- * @return {Object} Outgoing Object with all required properties set
- *
- * @api private
- */
- common.setupOutgoing = function(outgoing, options, req, forward) {
- outgoing.port = options[forward || 'target'].port ||
- (isSSL.test(options[forward || 'target'].protocol) ? 443 : 80);
- ['host', 'hostname', 'socketPath', 'pfx', 'key',
- 'passphrase', 'cert', 'ca', 'ciphers', 'secureProtocol'].forEach(
- function(e) { outgoing[e] = options[forward || 'target'][e]; }
- );
- outgoing.method = options.method || req.method;
- outgoing.headers = extend({}, req.headers);
- if (options.headers){
- extend(outgoing.headers, options.headers);
- }
- if (options.auth) {
- outgoing.auth = options.auth;
- }
-
- if (options.ca) {
- outgoing.ca = options.ca;
- }
- if (isSSL.test(options[forward || 'target'].protocol)) {
- outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure;
- }
- outgoing.agent = options.agent || false;
- outgoing.localAddress = options.localAddress;
- //
- // Remark: If we are false and not upgrading, set the connection: close. This is the right thing to do
- // as node core doesn't handle this COMPLETELY properly yet.
- //
- if (!outgoing.agent) {
- outgoing.headers = outgoing.headers || {};
- if (typeof outgoing.headers.connection !== 'string'
- || !upgradeHeader.test(outgoing.headers.connection)
- ) { outgoing.headers.connection = 'close'; }
- }
- // the final path is target path + relative path requested by user:
- var target = options[forward || 'target'];
- var targetPath = target && options.prependPath !== false
- ? (target.path || '')
- : '';
- //
- // Remark: Can we somehow not use url.parse as a perf optimization?
- //
- var outgoingPath = !options.toProxy
- ? (url.parse(req.url).path || '')
- : req.url;
- //
- // Remark: ignorePath will just straight up ignore whatever the request's
- // path is. This can be labeled as FOOT-GUN material if you do not know what
- // you are doing and are using conflicting options.
- //
- outgoingPath = !options.ignorePath ? outgoingPath : '';
- outgoing.path = common.urlJoin(targetPath, outgoingPath);
- if (options.changeOrigin) {
- outgoing.headers.host =
- required(outgoing.port, options[forward || 'target'].protocol) && !hasPort(outgoing.host)
- ? outgoing.host + ':' + outgoing.port
- : outgoing.host;
- }
- return outgoing;
- };
- /**
- * Set the proper configuration for sockets,
- * set no delay and set keep alive, also set
- * the timeout to 0.
- *
- * Examples:
- *
- * common.setupSocket(socket)
- * // => Socket
- *
- * @param {Socket} Socket instance to setup
- *
- * @return {Socket} Return the configured socket.
- *
- * @api private
- */
- common.setupSocket = function(socket) {
- socket.setTimeout(0);
- socket.setNoDelay(true);
- socket.setKeepAlive(true, 0);
- return socket;
- };
- /**
- * Get the port number from the host. Or guess it based on the connection type.
- *
- * @param {Request} req Incoming HTTP request.
- *
- * @return {String} The port number.
- *
- * @api private
- */
- common.getPort = function(req) {
- var res = req.headers.host ? req.headers.host.match(/:(\d+)/) : '';
- return res ?
- res[1] :
- common.hasEncryptedConnection(req) ? '443' : '80';
- };
- /**
- * Check if the request has an encrypted connection.
- *
- * @param {Request} req Incoming HTTP request.
- *
- * @return {Boolean} Whether the connection is encrypted or not.
- *
- * @api private
- */
- common.hasEncryptedConnection = function(req) {
- return Boolean(req.connection.encrypted || req.connection.pair);
- };
- /**
- * OS-agnostic join (doesn't break on URLs like path.join does on Windows)>
- *
- * @return {String} The generated path.
- *
- * @api private
- */
- common.urlJoin = function() {
- //
- // We do not want to mess with the query string. All we want to touch is the path.
- //
- var args = Array.prototype.slice.call(arguments),
- lastIndex = args.length - 1,
- last = args[lastIndex],
- lastSegs = last.split('?'),
- retSegs;
- args[lastIndex] = lastSegs.shift();
- //
- // Join all strings, but remove empty strings so we don't get extra slashes from
- // joining e.g. ['', 'am']
- //
- retSegs = [
- args.filter(Boolean).join('/')
- .replace(/\/+/g, '/')
- .replace('http:/', 'http://')
- .replace('https:/', 'https://')
- ];
- // Only join the query string if it exists so we don't have trailing a '?'
- // on every request
- // Handle case where there could be multiple ? in the URL.
- retSegs.push.apply(retSegs, lastSegs);
- return retSegs.join('?')
- };
- /**
- * Rewrites or removes the domain of a cookie header
- *
- * @param {String|Array} Header
- * @param {Object} Config, mapping of domain to rewritten domain.
- * '*' key to match any domain, null value to remove the domain.
- *
- * @api private
- */
- common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) {
- if (Array.isArray(header)) {
- return header.map(function (headerElement) {
- return rewriteCookieProperty(headerElement, config, property);
- });
- }
- return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) {
- var newValue;
- if (previousValue in config) {
- newValue = config[previousValue];
- } else if ('*' in config) {
- newValue = config['*'];
- } else {
- //no match, return previous value
- return match;
- }
- if (newValue) {
- //replace value
- return prefix + newValue;
- } else {
- //remove value
- return '';
- }
- });
- };
- /**
- * Check the host and see if it potentially has a port in it (keep it simple)
- *
- * @returns {Boolean} Whether we have one or not
- *
- * @api private
- */
- function hasPort(host) {
- return !!~host.indexOf(':');
- };
|