common.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. var common = exports,
  2. url = require('url'),
  3. extend = require('util')._extend,
  4. required = require('requires-port');
  5. var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i,
  6. isSSL = /^https|wss/;
  7. /**
  8. * Simple Regex for testing if protocol is https
  9. */
  10. common.isSSL = isSSL;
  11. /**
  12. * Copies the right headers from `options` and `req` to
  13. * `outgoing` which is then used to fire the proxied
  14. * request.
  15. *
  16. * Examples:
  17. *
  18. * common.setupOutgoing(outgoing, options, req)
  19. * // => { host: ..., hostname: ...}
  20. *
  21. * @param {Object} Outgoing Base object to be filled with required properties
  22. * @param {Object} Options Config object passed to the proxy
  23. * @param {ClientRequest} Req Request Object
  24. * @param {String} Forward String to select forward or target
  25. * 
  26. * @return {Object} Outgoing Object with all required properties set
  27. *
  28. * @api private
  29. */
  30. common.setupOutgoing = function(outgoing, options, req, forward) {
  31. outgoing.port = options[forward || 'target'].port ||
  32. (isSSL.test(options[forward || 'target'].protocol) ? 443 : 80);
  33. ['host', 'hostname', 'socketPath', 'pfx', 'key',
  34. 'passphrase', 'cert', 'ca', 'ciphers', 'secureProtocol'].forEach(
  35. function(e) { outgoing[e] = options[forward || 'target'][e]; }
  36. );
  37. outgoing.method = options.method || req.method;
  38. outgoing.headers = extend({}, req.headers);
  39. if (options.headers){
  40. extend(outgoing.headers, options.headers);
  41. }
  42. if (options.auth) {
  43. outgoing.auth = options.auth;
  44. }
  45. if (options.ca) {
  46. outgoing.ca = options.ca;
  47. }
  48. if (isSSL.test(options[forward || 'target'].protocol)) {
  49. outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure;
  50. }
  51. outgoing.agent = options.agent || false;
  52. outgoing.localAddress = options.localAddress;
  53. //
  54. // Remark: If we are false and not upgrading, set the connection: close. This is the right thing to do
  55. // as node core doesn't handle this COMPLETELY properly yet.
  56. //
  57. if (!outgoing.agent) {
  58. outgoing.headers = outgoing.headers || {};
  59. if (typeof outgoing.headers.connection !== 'string'
  60. || !upgradeHeader.test(outgoing.headers.connection)
  61. ) { outgoing.headers.connection = 'close'; }
  62. }
  63. // the final path is target path + relative path requested by user:
  64. var target = options[forward || 'target'];
  65. var targetPath = target && options.prependPath !== false
  66. ? (target.path || '')
  67. : '';
  68. //
  69. // Remark: Can we somehow not use url.parse as a perf optimization?
  70. //
  71. var outgoingPath = !options.toProxy
  72. ? (url.parse(req.url).path || '')
  73. : req.url;
  74. //
  75. // Remark: ignorePath will just straight up ignore whatever the request's
  76. // path is. This can be labeled as FOOT-GUN material if you do not know what
  77. // you are doing and are using conflicting options.
  78. //
  79. outgoingPath = !options.ignorePath ? outgoingPath : '';
  80. outgoing.path = common.urlJoin(targetPath, outgoingPath);
  81. if (options.changeOrigin) {
  82. outgoing.headers.host =
  83. required(outgoing.port, options[forward || 'target'].protocol) && !hasPort(outgoing.host)
  84. ? outgoing.host + ':' + outgoing.port
  85. : outgoing.host;
  86. }
  87. return outgoing;
  88. };
  89. /**
  90. * Set the proper configuration for sockets,
  91. * set no delay and set keep alive, also set
  92. * the timeout to 0.
  93. *
  94. * Examples:
  95. *
  96. * common.setupSocket(socket)
  97. * // => Socket
  98. *
  99. * @param {Socket} Socket instance to setup
  100. * 
  101. * @return {Socket} Return the configured socket.
  102. *
  103. * @api private
  104. */
  105. common.setupSocket = function(socket) {
  106. socket.setTimeout(0);
  107. socket.setNoDelay(true);
  108. socket.setKeepAlive(true, 0);
  109. return socket;
  110. };
  111. /**
  112. * Get the port number from the host. Or guess it based on the connection type.
  113. *
  114. * @param {Request} req Incoming HTTP request.
  115. *
  116. * @return {String} The port number.
  117. *
  118. * @api private
  119. */
  120. common.getPort = function(req) {
  121. var res = req.headers.host ? req.headers.host.match(/:(\d+)/) : '';
  122. return res ?
  123. res[1] :
  124. common.hasEncryptedConnection(req) ? '443' : '80';
  125. };
  126. /**
  127. * Check if the request has an encrypted connection.
  128. *
  129. * @param {Request} req Incoming HTTP request.
  130. *
  131. * @return {Boolean} Whether the connection is encrypted or not.
  132. *
  133. * @api private
  134. */
  135. common.hasEncryptedConnection = function(req) {
  136. return Boolean(req.connection.encrypted || req.connection.pair);
  137. };
  138. /**
  139. * OS-agnostic join (doesn't break on URLs like path.join does on Windows)>
  140. *
  141. * @return {String} The generated path.
  142. *
  143. * @api private
  144. */
  145. common.urlJoin = function() {
  146. //
  147. // We do not want to mess with the query string. All we want to touch is the path.
  148. //
  149. var args = Array.prototype.slice.call(arguments),
  150. lastIndex = args.length - 1,
  151. last = args[lastIndex],
  152. lastSegs = last.split('?'),
  153. retSegs;
  154. args[lastIndex] = lastSegs.shift();
  155. //
  156. // Join all strings, but remove empty strings so we don't get extra slashes from
  157. // joining e.g. ['', 'am']
  158. //
  159. retSegs = [
  160. args.filter(Boolean).join('/')
  161. .replace(/\/+/g, '/')
  162. .replace('http:/', 'http://')
  163. .replace('https:/', 'https://')
  164. ];
  165. // Only join the query string if it exists so we don't have trailing a '?'
  166. // on every request
  167. // Handle case where there could be multiple ? in the URL.
  168. retSegs.push.apply(retSegs, lastSegs);
  169. return retSegs.join('?')
  170. };
  171. /**
  172. * Rewrites or removes the domain of a cookie header
  173. *
  174. * @param {String|Array} Header
  175. * @param {Object} Config, mapping of domain to rewritten domain.
  176. * '*' key to match any domain, null value to remove the domain.
  177. *
  178. * @api private
  179. */
  180. common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) {
  181. if (Array.isArray(header)) {
  182. return header.map(function (headerElement) {
  183. return rewriteCookieProperty(headerElement, config, property);
  184. });
  185. }
  186. return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) {
  187. var newValue;
  188. if (previousValue in config) {
  189. newValue = config[previousValue];
  190. } else if ('*' in config) {
  191. newValue = config['*'];
  192. } else {
  193. //no match, return previous value
  194. return match;
  195. }
  196. if (newValue) {
  197. //replace value
  198. return prefix + newValue;
  199. } else {
  200. //remove value
  201. return '';
  202. }
  203. });
  204. };
  205. /**
  206. * Check the host and see if it potentially has a port in it (keep it simple)
  207. *
  208. * @returns {Boolean} Whether we have one or not
  209. *
  210. * @api private
  211. */
  212. function hasPort(host) {
  213. return !!~host.indexOf(':');
  214. };