UNPKG

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