| 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(':'); |
| }; |