@dev-swarup/http-mitm-proxy 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/proxy.js ADDED
@@ -0,0 +1,1199 @@
1
+ 'use strict';
2
+
3
+ var async = require('async');
4
+ var net = require('net');
5
+ var http = require('http');
6
+ var https = require('https');
7
+ var util = require('util');
8
+ var fs = require('fs');
9
+ var path = require('path');
10
+ var events = require('events');
11
+ var WebSocket = require('ws');
12
+ var url = require('url');
13
+ var semaphore = require('semaphore');
14
+ var ca = require('./ca.js');
15
+ const nodeCommon = require('_http_common');
16
+ const debug = require('debug')('http-mitm-proxy');
17
+
18
+ module.exports = function () {
19
+ return new Proxy();
20
+ };
21
+
22
+ module.exports.gunzip = require('./middleware/gunzip');
23
+ module.exports.wildcard = require('./middleware/wildcard');
24
+
25
+ var Proxy = function () {
26
+ this.onConnectHandlers = [];
27
+ this.onRequestHandlers = [];
28
+ this.onRequestHeadersHandlers = [];
29
+ this.onWebSocketConnectionHandlers = [];
30
+ this.onWebSocketFrameHandlers = [];
31
+ this.onWebSocketCloseHandlers = [];
32
+ this.onWebSocketErrorHandlers = [];
33
+ this.onErrorHandlers = [];
34
+ this.onRequestDataHandlers = [];
35
+ this.onRequestEndHandlers = [];
36
+ this.onResponseHandlers = [];
37
+ this.onResponseHeadersHandlers = [];
38
+ this.onResponseDataHandlers = [];
39
+ this.onResponseEndHandlers = [];
40
+ this.responseContentPotentiallyModified = false;
41
+ };
42
+
43
+ module.exports.Proxy = Proxy;
44
+
45
+ Proxy.prototype.listen = function (options, callback = (e) => {}) {
46
+ var self = this;
47
+ this.options = options || {};
48
+ this.httpPort = options.port || options.port === 0 ? options.port : 8080;
49
+ this.httpHost = options.host || 'localhost';
50
+ this.timeout = options.timeout || 0;
51
+ this.keepAlive = !!options.keepAlive;
52
+ this.httpAgent = typeof options.httpAgent !== 'undefined' ? options.httpAgent : new http.Agent({ keepAlive: this.keepAlive });
53
+ this.httpsAgent = typeof options.httpsAgent !== 'undefined' ? options.httpsAgent : new https.Agent({ keepAlive: this.keepAlive });
54
+ this.forceSNI = !!options.forceSNI;
55
+ if (this.forceSNI) {
56
+ debug('SNI enabled. Clients not supporting SNI may fail');
57
+ }
58
+ this.httpsPort = this.forceSNI ? options.httpsPort : undefined;
59
+ this.sslCaDir = options.sslCaDir || path.resolve(process.cwd(), '.http-mitm-proxy');
60
+ ca.create(this.sslCaDir, function (err, ca) {
61
+ if (err) {
62
+ return callback(err);
63
+ }
64
+ self.ca = ca;
65
+ self.sslCertFiles = {};
66
+ self.sslServers = {};
67
+ self.sslSemaphores = {};
68
+ self.httpServer = http.createServer();
69
+ self.httpServer.timeout = self.timeout;
70
+ self.httpServer.on('connect', self._onHttpServerConnect.bind(self));
71
+ self.httpServer.on('request', self._onHttpServerRequest.bind(self, false));
72
+ self.wsServer = new WebSocket.Server({ server: self.httpServer });
73
+ self.wsServer.on('error', self._onError.bind(self, 'HTTP_SERVER_ERROR', null));
74
+ self.wsServer.on('connection', (ws, req) => {
75
+ ws.upgradeReq = req;
76
+ self._onWebSocketServerConnect.call(self, false, ws, req);
77
+ });
78
+ const listenOptions = {
79
+ host: self.httpHost,
80
+ port: self.httpPort,
81
+ };
82
+ if (self.forceSNI) {
83
+ // start the single HTTPS server now
84
+ self._createHttpsServer({}, function (port, httpsServer, wssServer) {
85
+ debug('https server started on ' + port);
86
+ self.httpsServer = httpsServer;
87
+ self.wssServer = wssServer;
88
+ self.httpsPort = port;
89
+ self.httpServer.listen(listenOptions, () => {
90
+ self.httpPort = self.httpServer.address().port;
91
+ callback();
92
+ });
93
+ });
94
+ } else {
95
+ self.httpServer.listen(listenOptions, () => {
96
+ self.httpPort = self.httpServer.address().port;
97
+ callback();
98
+ });
99
+ }
100
+ });
101
+ return this;
102
+ };
103
+
104
+ Proxy.prototype.address = function () {
105
+ if (this.httpServer) {
106
+ return this.httpServer.address();
107
+ }
108
+ return null;
109
+ };
110
+
111
+ Proxy.prototype._createHttpsServer = function (options, callback) {
112
+ var httpsServer = https.createServer(options);
113
+ httpsServer.timeout = this.timeout;
114
+ httpsServer.on('error', this._onError.bind(this, 'HTTPS_SERVER_ERROR', null));
115
+ httpsServer.on('clientError', this._onError.bind(this, 'HTTPS_CLIENT_ERROR', null));
116
+ httpsServer.on('connect', this._onHttpServerConnect.bind(this));
117
+ httpsServer.on('request', this._onHttpServerRequest.bind(this, true));
118
+ var self = this;
119
+ var wssServer = new WebSocket.Server({ server: httpsServer });
120
+ wssServer.on('connection', function (ws, req) {
121
+ ws.upgradeReq = req;
122
+ self._onWebSocketServerConnect.call(self, true, ws, req);
123
+ });
124
+ var listenArgs = [
125
+ function () {
126
+ if (callback) callback(httpsServer.address().port, httpsServer, wssServer);
127
+ },
128
+ ];
129
+ // Using listenOptions to bind the server to a particular IP if requested via options.host
130
+ // port 0 to get the first available port
131
+ var listenOptions = {
132
+ port: 0,
133
+ };
134
+ if (this.httpsPort && !options.hosts) {
135
+ listenOptions.port = this.httpsPort;
136
+ }
137
+ if (this.httpHost) listenOptions.host = this.httpHost;
138
+ listenArgs.unshift(listenOptions);
139
+
140
+ httpsServer.listen.apply(httpsServer, listenArgs);
141
+ };
142
+
143
+ Proxy.prototype.close = function () {
144
+ var self = this;
145
+ this.httpServer.close();
146
+ delete this.httpServer;
147
+ if (this.httpsServer) {
148
+ this.httpsServer.close();
149
+ delete this.httpsServer;
150
+ delete this.wssServer;
151
+ delete this.sslServers;
152
+ }
153
+ if (this.sslServers) {
154
+ Object.keys(this.sslServers).forEach(function (srvName) {
155
+ var server = self.sslServers[srvName].server;
156
+ if (server) server.close();
157
+ delete self.sslServers[srvName];
158
+ });
159
+ }
160
+ return this;
161
+ };
162
+
163
+ Proxy.prototype.onError = function (fn) {
164
+ this.onErrorHandlers.push(fn);
165
+ return this;
166
+ };
167
+ /**
168
+ * Add custom handler for CONNECT method
169
+ * @augments fn(req,socket,head,callback) be called on receiving CONNECT method
170
+ */
171
+ Proxy.prototype.onConnect = function (fn) {
172
+ this.onConnectHandlers.push(fn);
173
+ return this;
174
+ };
175
+
176
+ Proxy.prototype.onRequestHeaders = function (fn) {
177
+ this.onRequestHeadersHandlers.push(fn);
178
+ return this;
179
+ };
180
+
181
+ Proxy.prototype.onRequest = function (fn) {
182
+ this.onRequestHandlers.push(fn);
183
+ return this;
184
+ };
185
+
186
+ Proxy.prototype.onWebSocketConnection = function (fn) {
187
+ this.onWebSocketConnectionHandlers.push(fn);
188
+ return this;
189
+ };
190
+
191
+ Proxy.prototype.onWebSocketSend = function (fn) {
192
+ this.onWebSocketFrameHandlers.push(
193
+ function (ctx, type, fromServer, data, flags, callback) {
194
+ if (!fromServer && type === 'message') return this(ctx, data, flags, callback);
195
+ else callback(null, data, flags);
196
+ }.bind(fn)
197
+ );
198
+ return this;
199
+ };
200
+
201
+ Proxy.prototype.onWebSocketMessage = function (fn) {
202
+ this.onWebSocketFrameHandlers.push(
203
+ function (ctx, type, fromServer, data, flags, callback) {
204
+ if (fromServer && type === 'message') return this(ctx, data, flags, callback);
205
+ else callback(null, data, flags);
206
+ }.bind(fn)
207
+ );
208
+ return this;
209
+ };
210
+
211
+ Proxy.prototype.onWebSocketFrame = function (fn) {
212
+ this.onWebSocketFrameHandlers.push(fn);
213
+ return this;
214
+ };
215
+
216
+ Proxy.prototype.onWebSocketClose = function (fn) {
217
+ this.onWebSocketCloseHandlers.push(fn);
218
+ return this;
219
+ };
220
+
221
+ Proxy.prototype.onWebSocketError = function (fn) {
222
+ this.onWebSocketErrorHandlers.push(fn);
223
+ return this;
224
+ };
225
+
226
+ Proxy.prototype.onRequestData = function (fn) {
227
+ this.onRequestDataHandlers.push(fn);
228
+ return this;
229
+ };
230
+
231
+ Proxy.prototype.onRequestEnd = function (fn) {
232
+ this.onRequestEndHandlers.push(fn);
233
+ return this;
234
+ };
235
+
236
+ Proxy.prototype.onResponse = function (fn) {
237
+ this.onResponseHandlers.push(fn);
238
+ return this;
239
+ };
240
+
241
+ Proxy.prototype.onResponseHeaders = function (fn) {
242
+ this.onResponseHeadersHandlers.push(fn);
243
+ return this;
244
+ };
245
+
246
+ Proxy.prototype.onResponseData = function (fn) {
247
+ this.onResponseDataHandlers.push(fn);
248
+ this.responseContentPotentiallyModified = true;
249
+ return this;
250
+ };
251
+
252
+ Proxy.prototype.onResponseEnd = function (fn) {
253
+ this.onResponseEndHandlers.push(fn);
254
+ return this;
255
+ };
256
+
257
+ Proxy.prototype.use = function (mod) {
258
+ if (mod.onError) {
259
+ this.onError(mod.onError);
260
+ }
261
+ if (mod.onCertificateRequired) {
262
+ this.onCertificateRequired = mod.onCertificateRequired;
263
+ }
264
+ if (mod.onCertificateMissing) {
265
+ this.onCertificateMissing = mod.onCertificateMissing;
266
+ }
267
+ if (mod.onConnect) {
268
+ this.onConnect(mod.onConnect);
269
+ }
270
+ if (mod.onRequest) {
271
+ this.onRequest(mod.onRequest);
272
+ }
273
+ if (mod.onRequestHeaders) {
274
+ this.onRequestHeaders(mod.onRequestHeaders);
275
+ }
276
+ if (mod.onRequestData) {
277
+ this.onRequestData(mod.onRequestData);
278
+ }
279
+ if (mod.onResponse) {
280
+ this.onResponse(mod.onResponse);
281
+ }
282
+ if (mod.onResponseHeaders) {
283
+ this.onResponseHeaders(mod.onResponseHeaders);
284
+ }
285
+ if (mod.onResponseData) {
286
+ this.onResponseData(mod.onResponseData);
287
+ }
288
+ if (mod.onWebSocketConnection) {
289
+ this.onWebSocketConnection(mod.onWebSocketConnection);
290
+ }
291
+ if (mod.onWebSocketSend) {
292
+ this.onWebSocketFrame(
293
+ function (ctx, type, fromServer, data, flags, callback) {
294
+ if (!fromServer && type === 'message') return this(ctx, data, flags, callback);
295
+ else callback(null, data, flags);
296
+ }.bind(mod.onWebSocketSend)
297
+ );
298
+ }
299
+ if (mod.onWebSocketMessage) {
300
+ this.onWebSocketFrame(
301
+ function (ctx, type, fromServer, data, flags, callback) {
302
+ if (fromServer && type === 'message') return this(ctx, data, flags, callback);
303
+ else callback(null, data, flags);
304
+ }.bind(mod.onWebSocketMessage)
305
+ );
306
+ }
307
+ if (mod.onWebSocketFrame) {
308
+ this.onWebSocketFrame(mod.onWebSocketFrame);
309
+ }
310
+ if (mod.onWebSocketClose) {
311
+ this.onWebSocketClose(mod.onWebSocketClose);
312
+ }
313
+ if (mod.onWebSocketError) {
314
+ this.onWebSocketError(mod.onWebSocketError);
315
+ }
316
+ return this;
317
+ };
318
+
319
+ // Since node 0.9.9, ECONNRESET on sockets are no longer hidden
320
+ Proxy.prototype._onSocketError = function (socketDescription, err) {
321
+ debug('Got ' + err.code + ' on ' + socketDescription + '.');
322
+ };
323
+
324
+ Proxy.prototype._onHttpServerConnect = function (req, socket, head) {
325
+ var self = this;
326
+
327
+ socket.on('error', self._onSocketError.bind(self, 'CLIENT_TO_PROXY_SOCKET'));
328
+
329
+ // you can forward HTTPS request directly by adding custom CONNECT method handler
330
+ return async.forEach(
331
+ self.onConnectHandlers,
332
+ function (fn, callback) {
333
+ return fn.call(self, req, socket, head, callback);
334
+ },
335
+ function (err) {
336
+ if (err) {
337
+ return self._onError('ON_CONNECT_ERROR', null, err);
338
+ }
339
+ // we need first byte of data to detect if request is SSL encrypted
340
+ if (!head || head.length === 0) {
341
+ socket.once('data', self._onHttpServerConnectData.bind(self, req, socket));
342
+ socket.write('HTTP/1.1 200 OK\r\n');
343
+ if (self.keepAlive && req.headers['proxy-connection'] === 'keep-alive') {
344
+ socket.write('Proxy-Connection: keep-alive\r\n');
345
+ socket.write('Connection: keep-alive\r\n');
346
+ }
347
+ return socket.write('\r\n');
348
+ } else {
349
+ self._onHttpServerConnectData(req, socket, head);
350
+ }
351
+ }
352
+ );
353
+ };
354
+
355
+ Proxy.prototype._onHttpServerConnectData = function (req, socket, head) {
356
+ var self = this;
357
+
358
+ socket.pause();
359
+
360
+ /*
361
+ * Detect TLS from first bytes of data
362
+ * Inspired from https://gist.github.com/tg-x/835636
363
+ * used heuristic:
364
+ * - an incoming connection using SSLv3/TLSv1 records should start with 0x16
365
+ * - an incoming connection using SSLv2 records should start with the record size
366
+ * and as the first record should not be very big we can expect 0x80 or 0x00 (the MSB is a flag)
367
+ * - everything else is considered to be unencrypted
368
+ */
369
+ if (head[0] == 0x16 || head[0] == 0x80 || head[0] == 0x00) {
370
+ // URL is in the form 'hostname:port'
371
+ var hostname = new URL('http://' + req.url).hostname;
372
+ var sslServer = this.sslServers[hostname];
373
+ if (sslServer) {
374
+ return makeConnection(sslServer.port, this.httpHost);
375
+ }
376
+ var wildcardHost = hostname.replace(/[^\.]+\./, '*.');
377
+ var sem = self.sslSemaphores[wildcardHost];
378
+ if (!sem) {
379
+ sem = self.sslSemaphores[wildcardHost] = semaphore(1);
380
+ }
381
+ sem.take(function () {
382
+ if (self.sslServers[hostname]) {
383
+ process.nextTick(sem.leave.bind(sem));
384
+ return makeConnection(self.sslServers[hostname].port, self.httpHost);
385
+ }
386
+ if (self.sslServers[wildcardHost]) {
387
+ process.nextTick(sem.leave.bind(sem));
388
+ self.sslServers[hostname] = {
389
+ port: self.sslServers[wildcardHost],
390
+ };
391
+ return makeConnection(self.sslServers[hostname].port, self.httpHost);
392
+ }
393
+ getHttpsServer(hostname, function (err, port) {
394
+ process.nextTick(sem.leave.bind(sem));
395
+ if (err) {
396
+ return self._onError('OPEN_HTTPS_SERVER_ERROR', null, err);
397
+ }
398
+ return makeConnection(port, self.httpHost);
399
+ });
400
+ delete self.sslSemaphores[wildcardHost];
401
+ });
402
+ } else {
403
+ return makeConnection(this.httpPort, this.httpHost);
404
+ }
405
+
406
+ function makeConnection(port, host) {
407
+ const connClosing = function (socket) {
408
+ if (!socket.readableFlowing && socket.writable) {
409
+ // Pipe not established, signal close to client
410
+ socket.end();
411
+ }
412
+ };
413
+ const socketClosing = function (conn) {
414
+ if (!conn.readableFlowing && conn.writable) {
415
+ // Pipe not established, signal close to server
416
+ conn.end();
417
+ }
418
+ };
419
+
420
+ // open a TCP connection to the remote host
421
+ var conn = net.connect(
422
+ {
423
+ host: host,
424
+ port: port,
425
+ },
426
+ function () {
427
+ // create a tunnel between the two hosts
428
+ conn.write(head);
429
+ conn.pipe(socket);
430
+ return socket.pipe(conn);
431
+ }
432
+ );
433
+ conn.on('error', self._onSocketError.bind(self, 'PROXY_TO_PROXY_SOCKET'));
434
+ conn.once('end', () => connClosing(socket));
435
+ conn.once('finish', () => connClosing(socket));
436
+ conn.once('close', () => connClosing(socket));
437
+ conn.setNoDelay();
438
+ socket.once('end', () => socketClosing(conn));
439
+ socket.once('finish', () => socketClosing(conn));
440
+ socket.once('close', () => socketClosing(conn));
441
+ socket.setNoDelay();
442
+ }
443
+
444
+ function getHttpsServer(hostname, callback) {
445
+ self.onCertificateRequired(hostname, function (err, files) {
446
+ if (err) {
447
+ return callback(err);
448
+ }
449
+
450
+ var ctx = {
451
+ hostname: hostname,
452
+ files: files
453
+ };
454
+
455
+ return self.onCertificateMissing(ctx, files, function (err, generatedData) {
456
+ if (err) {
457
+ return callback(err);
458
+ }
459
+
460
+ var hosts = generatedData.hosts || [hostname];
461
+
462
+ var httpsOptions = {
463
+ key: generatedData.keyFileData,
464
+ cert: generatedData.certFileData
465
+ };
466
+
467
+ if (self.forceSNI && !hostname.match(/^[\d\.]+$/)) {
468
+ debug('creating SNI context for ' + hostname);
469
+ hosts.forEach(function (host) {
470
+ self.httpsServer.addContext(host, httpsOptions);
471
+ self.sslServers[host] = { port: self.httpsPort };
472
+ });
473
+ return callback(null, self.httpsPort);
474
+ } else {
475
+ debug('starting server for ' + hostname);
476
+ httpsOptions.hosts = hosts;
477
+ try {
478
+ self._createHttpsServer(httpsOptions, function (port, httpsServer, wssServer) {
479
+ var sslServer = {
480
+ server: httpsServer,
481
+ wsServer: wssServer,
482
+ port: port,
483
+ };
484
+ hosts.forEach(function (host) {
485
+ self.sslServers[hostname] = sslServer;
486
+ });
487
+ return callback(null, port);
488
+ });
489
+ } catch (err) {
490
+ return callback(err);
491
+ }
492
+ }
493
+ });
494
+ });
495
+ }
496
+ };
497
+
498
+ Proxy.prototype.onCertificateRequired = function (hostname, callback) {
499
+ var self = this;
500
+ // Encode filename to handle : character and prevent filename hacking
501
+ const hostCertFilename = this.ca.hostToFilename(hostname);
502
+ return callback(null, {
503
+ keyFile: self.sslCaDir + '/keys/' + hostCertFilename + '.key',
504
+ certFile: self.sslCaDir + '/certs/' + hostCertFilename + '.pem',
505
+ hosts: [hostname],
506
+ });
507
+ };
508
+
509
+ Proxy.prototype.onCertificateMissing = function (ctx, files, callback) {
510
+ var hosts = files.hosts || [ctx.hostname];
511
+ this.ca.generateServerCertificateKeys(hosts, function (certPEM, privateKeyPEM) {
512
+ callback(null, {
513
+ certFileData: certPEM,
514
+ keyFileData: privateKeyPEM,
515
+ hosts: hosts,
516
+ });
517
+ });
518
+ return this;
519
+ };
520
+
521
+ Proxy.prototype._onError = function (kind, ctx, err) {
522
+ this.onErrorHandlers.forEach(function (handler) {
523
+ return handler(ctx, err, kind);
524
+ });
525
+ if (ctx) {
526
+ ctx.onErrorHandlers.forEach(function (handler) {
527
+ return handler(ctx, err, kind);
528
+ });
529
+
530
+ if (ctx.proxyToClientResponse && ctx.proxyToClientResponse.writable && !ctx.proxyToClientResponse.headersSent) {
531
+ ctx.proxyToClientResponse.writeHead(504, 'Proxy Error');
532
+ }
533
+ if (ctx.proxyToClientResponse && ctx.proxyToClientResponse.writable) {
534
+ ctx.proxyToClientResponse.end('' + kind + ': ' + err, 'utf8');
535
+ }
536
+ }
537
+ };
538
+
539
+ Proxy.prototype._onWebSocketServerConnect = function (isSSL, ws, upgradeReq) {
540
+ var self = this;
541
+ var ctx = {
542
+ isSSL: isSSL,
543
+ clientToProxyWebSocket: ws,
544
+ onWebSocketConnectionHandlers: [],
545
+ onWebSocketFrameHandlers: [],
546
+ onWebSocketCloseHandlers: [],
547
+ onWebSocketErrorHandlers: [],
548
+ onWebSocketConnection: function (fn) {
549
+ ctx.onWebSocketConnectionHandlers.push(fn);
550
+ return ctx;
551
+ },
552
+ onWebSocketSend: function (fn) {
553
+ ctx.onWebSocketFrameHandlers.push(
554
+ function (ctx, type, fromServer, data, flags, callback) {
555
+ if (!fromServer && type === 'message') return this(ctx, data, flags, callback);
556
+ else callback(null, data, flags);
557
+ }.bind(fn)
558
+ );
559
+ return ctx;
560
+ },
561
+ onWebSocketMessage: function (fn) {
562
+ ctx.onWebSocketFrameHandlers.push(
563
+ function (ctx, type, fromServer, data, flags, callback) {
564
+ if (fromServer && type === 'message') return this(ctx, data, flags, callback);
565
+ else callback(null, data, flags);
566
+ }.bind(fn)
567
+ );
568
+ return ctx;
569
+ },
570
+ onWebSocketFrame: function (fn) {
571
+ ctx.onWebSocketFrameHandlers.push(fn);
572
+ return ctx;
573
+ },
574
+ onWebSocketClose: function (fn) {
575
+ ctx.onWebSocketCloseHandlers.push(fn);
576
+ return ctx;
577
+ },
578
+ onWebSocketError: function (fn) {
579
+ ctx.onWebSocketErrorHandlers.push(fn);
580
+ return ctx;
581
+ },
582
+ use: function (mod) {
583
+ if (mod.onWebSocketConnection) {
584
+ ctx.onWebSocketConnection(mod.onWebSocketConnection);
585
+ }
586
+ if (mod.onWebSocketSend) {
587
+ ctx.onWebSocketFrame(
588
+ function (ctx, type, fromServer, data, flags, callback) {
589
+ if (!fromServer && type === 'message') return this(ctx, data, flags, callback);
590
+ else callback(null, data, flags);
591
+ }.bind(mod.onWebSocketSend)
592
+ );
593
+ }
594
+ if (mod.onWebSocketMessage) {
595
+ ctx.onWebSocketFrame(
596
+ function (ctx, type, fromServer, data, flags, callback) {
597
+ if (fromServer && type === 'message') return this(ctx, data, flags, callback);
598
+ else callback(null, data, flags);
599
+ }.bind(mod.onWebSocketMessage)
600
+ );
601
+ }
602
+ if (mod.onWebSocketFrame) {
603
+ ctx.onWebSocketFrame(mod.onWebSocketFrame);
604
+ }
605
+ if (mod.onWebSocketClose) {
606
+ ctx.onWebSocketClose(mod.onWebSocketClose);
607
+ }
608
+ if (mod.onWebSocketError) {
609
+ ctx.onWebSocketError(mod.onWebSocketError);
610
+ }
611
+ return ctx;
612
+ },
613
+ };
614
+ ctx.clientToProxyWebSocket.on('message', self._onWebSocketFrame.bind(self, ctx, 'message', false));
615
+ ctx.clientToProxyWebSocket.on('ping', self._onWebSocketFrame.bind(self, ctx, 'ping', false));
616
+ ctx.clientToProxyWebSocket.on('pong', self._onWebSocketFrame.bind(self, ctx, 'pong', false));
617
+ ctx.clientToProxyWebSocket.on('error', self._onWebSocketError.bind(self, ctx));
618
+ ctx.clientToProxyWebSocket._socket.on('error', self._onWebSocketError.bind(self, ctx));
619
+ ctx.clientToProxyWebSocket.on('close', self._onWebSocketClose.bind(self, ctx, false));
620
+ ctx.clientToProxyWebSocket._socket.pause();
621
+ var url;
622
+ if (upgradeReq.url == '' || /^\//.test(upgradeReq.url)) {
623
+ var hostPort = Proxy.parseHostAndPort(upgradeReq);
624
+ url = (ctx.isSSL ? 'wss' : 'ws') + '://' + hostPort.host + (hostPort.port ? ':' + hostPort.port : '') + upgradeReq.url;
625
+ } else {
626
+ const upgradeReqUrl = new URL(upgradeReq.url);
627
+ upgradeReqUrl.protocol = ctx.isSSL ? 'wss:' : 'ws:';
628
+ url = upgradeReqUrl.href;
629
+ }
630
+ var ptosHeaders = {};
631
+ var ctopHeaders = upgradeReq.headers;
632
+ for (var key in ctopHeaders) {
633
+ if (key.indexOf('sec-websocket') !== 0) {
634
+ ptosHeaders[key] = ctopHeaders[key];
635
+ }
636
+ }
637
+ ctx.proxyToServerWebSocketOptions = {
638
+ url: url,
639
+ agent: ctx.isSSL ? self.httpsAgent : self.httpAgent,
640
+ headers: ptosHeaders,
641
+ };
642
+ return self._onWebSocketConnection(ctx, function (err) {
643
+ if (err) {
644
+ return self._onWebSocketError(ctx, err);
645
+ }
646
+ return makeProxyToServerWebSocket();
647
+ });
648
+
649
+ function makeProxyToServerWebSocket() {
650
+ ctx.proxyToServerWebSocket = new WebSocket(ctx.proxyToServerWebSocketOptions.url, ctx.proxyToServerWebSocketOptions);
651
+ ctx.proxyToServerWebSocket.on('message', self._onWebSocketFrame.bind(self, ctx, 'message', true));
652
+ ctx.proxyToServerWebSocket.on('ping', self._onWebSocketFrame.bind(self, ctx, 'ping', true));
653
+ ctx.proxyToServerWebSocket.on('pong', self._onWebSocketFrame.bind(self, ctx, 'pong', true));
654
+ ctx.proxyToServerWebSocket.on('error', self._onWebSocketError.bind(self, ctx));
655
+ ctx.proxyToServerWebSocket.on('close', self._onWebSocketClose.bind(self, ctx, true));
656
+ ctx.proxyToServerWebSocket.on('open', function () {
657
+ ctx.proxyToServerWebSocket._socket.on('error', self._onWebSocketError.bind(self, ctx));
658
+ if (ctx.clientToProxyWebSocket.readyState === WebSocket.OPEN) {
659
+ ctx.clientToProxyWebSocket._socket.resume();
660
+ }
661
+ });
662
+ }
663
+ };
664
+
665
+ Proxy.prototype._onHttpServerRequest = function (isSSL, clientToProxyRequest, proxyToClientResponse) {
666
+ var self = this;
667
+ var ctx = {
668
+ isSSL: isSSL,
669
+ clientToProxyRequest: clientToProxyRequest,
670
+ proxyToClientResponse: proxyToClientResponse,
671
+ onRequestHandlers: [],
672
+ onErrorHandlers: [],
673
+ onRequestDataHandlers: [],
674
+ onRequestEndHandlers: [],
675
+ onResponseHandlers: [],
676
+ onResponseDataHandlers: [],
677
+ onResponseEndHandlers: [],
678
+ requestFilters: [],
679
+ responseFilters: [],
680
+ responseContentPotentiallyModified: false,
681
+ onRequest: function (fn) {
682
+ ctx.onRequestHandlers.push(fn);
683
+ return ctx;
684
+ },
685
+ onError: function (fn) {
686
+ ctx.onErrorHandlers.push(fn);
687
+ return ctx;
688
+ },
689
+ onRequestData: function (fn) {
690
+ ctx.onRequestDataHandlers.push(fn);
691
+ return ctx;
692
+ },
693
+ onRequestEnd: function (fn) {
694
+ ctx.onRequestEndHandlers.push(fn);
695
+ return ctx;
696
+ },
697
+ addRequestFilter: function (filter) {
698
+ ctx.requestFilters.push(filter);
699
+ return ctx;
700
+ },
701
+ onResponse: function (fn) {
702
+ ctx.onResponseHandlers.push(fn);
703
+ return ctx;
704
+ },
705
+ onResponseData: function (fn) {
706
+ ctx.onResponseDataHandlers.push(fn);
707
+ ctx.responseContentPotentiallyModified = true;
708
+ return ctx;
709
+ },
710
+ onResponseEnd: function (fn) {
711
+ ctx.onResponseEndHandlers.push(fn);
712
+ return ctx;
713
+ },
714
+ addResponseFilter: function (filter) {
715
+ ctx.responseFilters.push(filter);
716
+ ctx.responseContentPotentiallyModified = true;
717
+ return ctx;
718
+ },
719
+ use: function (mod) {
720
+ if (mod.onError) {
721
+ ctx.onError(mod.onError);
722
+ }
723
+ if (mod.onRequest) {
724
+ ctx.onRequest(mod.onRequest);
725
+ }
726
+ if (mod.onRequestHeaders) {
727
+ ctx.onRequestHeaders(mod.onRequestHeaders);
728
+ }
729
+ if (mod.onRequestData) {
730
+ ctx.onRequestData(mod.onRequestData);
731
+ }
732
+ if (mod.onResponse) {
733
+ ctx.onResponse(mod.onResponse);
734
+ }
735
+ if (mod.onResponseData) {
736
+ ctx.onResponseData(mod.onResponseData);
737
+ }
738
+ return ctx;
739
+ },
740
+ };
741
+
742
+ ctx.clientToProxyRequest.on('error', self._onError.bind(self, 'CLIENT_TO_PROXY_REQUEST_ERROR', ctx));
743
+ ctx.proxyToClientResponse.on('error', self._onError.bind(self, 'PROXY_TO_CLIENT_RESPONSE_ERROR', ctx));
744
+ ctx.clientToProxyRequest.pause();
745
+ var hostPort = Proxy.parseHostAndPort(ctx.clientToProxyRequest, ctx.isSSL ? 443 : 80);
746
+ if (hostPort === null) {
747
+ ctx.clientToProxyRequest.resume();
748
+ ctx.proxyToClientResponse.writeHeader(400, {
749
+ 'Content-Type': 'text/html; charset=utf-8',
750
+ });
751
+ ctx.proxyToClientResponse.end('Bad request: Host missing...', 'UTF-8');
752
+ } else {
753
+ var headers = {};
754
+ for (var h in ctx.clientToProxyRequest.headers) {
755
+ // don't forward proxy- headers
756
+ if (!/^proxy\-/i.test(h)) {
757
+ headers[h] = ctx.clientToProxyRequest.headers[h];
758
+ }
759
+ }
760
+ if (this.options.forceChunkedRequest) {
761
+ delete headers['content-length'];
762
+ }
763
+
764
+ ctx.proxyToServerRequestOptions = {
765
+ method: ctx.clientToProxyRequest.method,
766
+ path: ctx.clientToProxyRequest.url,
767
+ host: hostPort.hostUnescaped,
768
+ port: hostPort.port,
769
+ headers: headers,
770
+ agent: ctx.isSSL ? self.httpsAgent : self.httpAgent,
771
+ };
772
+ return self._onRequest(ctx, function (err) {
773
+ if (err) {
774
+ return self._onError('ON_REQUEST_ERROR', ctx, err);
775
+ }
776
+ return self._onRequestHeaders(ctx, function (err) {
777
+ if (err) {
778
+ return self._onError('ON_REQUESTHEADERS_ERROR', ctx, err);
779
+ }
780
+ return makeProxyToServerRequest();
781
+ });
782
+ });
783
+ }
784
+
785
+ function makeProxyToServerRequest() {
786
+ var proto = ctx.isSSL ? https : http;
787
+ ctx.proxyToServerRequest = proto.request(ctx.proxyToServerRequestOptions, proxyToServerRequestComplete);
788
+ ctx.proxyToServerRequest.on('error', self._onError.bind(self, 'PROXY_TO_SERVER_REQUEST_ERROR', ctx));
789
+ ctx.requestFilters.push(new ProxyFinalRequestFilter(self, ctx));
790
+ var prevRequestPipeElem = ctx.clientToProxyRequest;
791
+ ctx.requestFilters.forEach(function (filter) {
792
+ filter.on('error', self._onError.bind(self, 'REQUEST_FILTER_ERROR', ctx));
793
+ prevRequestPipeElem = prevRequestPipeElem.pipe(filter);
794
+ });
795
+ ctx.clientToProxyRequest.resume();
796
+ }
797
+
798
+ function proxyToServerRequestComplete(serverToProxyResponse) {
799
+ serverToProxyResponse.on('error', self._onError.bind(self, 'SERVER_TO_PROXY_RESPONSE_ERROR', ctx));
800
+ serverToProxyResponse.pause();
801
+ ctx.serverToProxyResponse = serverToProxyResponse;
802
+ return self._onResponse(ctx, function (err) {
803
+ if (err) {
804
+ return self._onError('ON_RESPONSE_ERROR', ctx, err);
805
+ }
806
+ if (self.responseContentPotentiallyModified || ctx.responseContentPotentiallyModified) {
807
+ ctx.serverToProxyResponse.headers['transfer-encoding'] = 'chunked';
808
+ delete ctx.serverToProxyResponse.headers['content-length'];
809
+ }
810
+ if (self.keepAlive) {
811
+ if (ctx.clientToProxyRequest.headers['proxy-connection']) {
812
+ ctx.serverToProxyResponse.headers['proxy-connection'] = 'keep-alive';
813
+ ctx.serverToProxyResponse.headers['connection'] = 'keep-alive';
814
+ }
815
+ } else {
816
+ ctx.serverToProxyResponse.headers['connection'] = 'close';
817
+ }
818
+ return self._onResponseHeaders(ctx, function (err) {
819
+ if (err) {
820
+ return self._onError('ON_RESPONSEHEADERS_ERROR', ctx, err);
821
+ }
822
+ ctx.proxyToClientResponse.writeHead(ctx.serverToProxyResponse.statusCode, ctx.serverToProxyResponse.statusMessage, Proxy.filterAndCanonizeHeaders(ctx.serverToProxyResponse.headers));
823
+ ctx.responseFilters.push(new ProxyFinalResponseFilter(self, ctx));
824
+ var prevResponsePipeElem = ctx.serverToProxyResponse;
825
+ ctx.responseFilters.forEach(function (filter) {
826
+ filter.on('error', self._onError.bind(self, 'RESPONSE_FILTER_ERROR', ctx));
827
+ prevResponsePipeElem = prevResponsePipeElem.pipe(filter);
828
+ });
829
+ return ctx.serverToProxyResponse.resume();
830
+ });
831
+ });
832
+ }
833
+ };
834
+
835
+ var ProxyFinalRequestFilter = function (proxy, ctx) {
836
+ events.EventEmitter.call(this);
837
+ this.writable = true;
838
+
839
+ this.write = function (chunk) {
840
+ proxy._onRequestData(ctx, chunk, function (err, chunk) {
841
+ if (err) {
842
+ return proxy._onError('ON_REQUEST_DATA_ERROR', ctx, err);
843
+ }
844
+ if (chunk) {
845
+ return ctx.proxyToServerRequest.write(chunk);
846
+ }
847
+ });
848
+ return true;
849
+ };
850
+
851
+ this.end = function (chunk) {
852
+ if (chunk) {
853
+ return proxy._onRequestData(ctx, chunk, function (err, chunk) {
854
+ if (err) {
855
+ return proxy._onError('ON_REQUEST_DATA_ERROR', ctx, err);
856
+ }
857
+
858
+ return proxy._onRequestEnd(ctx, function (err) {
859
+ if (err) {
860
+ return proxy._onError('ON_REQUEST_END_ERROR', ctx, err);
861
+ }
862
+ return ctx.proxyToServerRequest.end(chunk);
863
+ });
864
+ });
865
+ } else {
866
+ return proxy._onRequestEnd(ctx, function (err) {
867
+ if (err) {
868
+ return proxy._onError('ON_REQUEST_END_ERROR', ctx, err);
869
+ }
870
+ return ctx.proxyToServerRequest.end(chunk || undefined);
871
+ });
872
+ }
873
+ };
874
+ };
875
+ util.inherits(ProxyFinalRequestFilter, events.EventEmitter);
876
+
877
+ var ProxyFinalResponseFilter = function (proxy, ctx) {
878
+ events.EventEmitter.call(this);
879
+ this.writable = true;
880
+
881
+ this.write = function (chunk) {
882
+ proxy._onResponseData(ctx, chunk, function (err, chunk) {
883
+ if (err) {
884
+ return proxy._onError('ON_RESPONSE_DATA_ERROR', ctx, err);
885
+ }
886
+ if (chunk) {
887
+ return ctx.proxyToClientResponse.write(chunk);
888
+ }
889
+ });
890
+ return true;
891
+ };
892
+
893
+ this.end = function (chunk) {
894
+ if (chunk) {
895
+ return proxy._onResponseData(ctx, chunk, function (err, chunk) {
896
+ if (err) {
897
+ return proxy._onError('ON_RESPONSE_DATA_ERROR', ctx, err);
898
+ }
899
+
900
+ return proxy._onResponseEnd(ctx, function (err) {
901
+ if (err) {
902
+ return proxy._onError('ON_RESPONSE_END_ERROR', ctx, err);
903
+ }
904
+ return ctx.proxyToClientResponse.end(chunk || undefined);
905
+ });
906
+ });
907
+ } else {
908
+ return proxy._onResponseEnd(ctx, function (err) {
909
+ if (err) {
910
+ return proxy._onError('ON_RESPONSE_END_ERROR', ctx, err);
911
+ }
912
+ return ctx.proxyToClientResponse.end(chunk || undefined);
913
+ });
914
+ }
915
+ };
916
+
917
+ return this;
918
+ };
919
+ util.inherits(ProxyFinalResponseFilter, events.EventEmitter);
920
+
921
+ Proxy.prototype._onRequestHeaders = function (ctx, callback) {
922
+ async.forEach(
923
+ this.onRequestHeadersHandlers,
924
+ function (fn, callback) {
925
+ return fn(ctx, callback);
926
+ },
927
+ callback
928
+ );
929
+ };
930
+
931
+ Proxy.prototype._onRequest = function (ctx, callback) {
932
+ async.forEach(
933
+ this.onRequestHandlers.concat(ctx.onRequestHandlers),
934
+ function (fn, callback) {
935
+ return fn(ctx, callback);
936
+ },
937
+ callback
938
+ );
939
+ };
940
+
941
+ Proxy.prototype._onWebSocketConnection = function (ctx, callback) {
942
+ async.forEach(
943
+ this.onWebSocketConnectionHandlers.concat(ctx.onWebSocketConnectionHandlers),
944
+ function (fn, callback) {
945
+ return fn(ctx, callback);
946
+ },
947
+ callback
948
+ );
949
+ };
950
+
951
+ Proxy.prototype._onWebSocketFrame = function (ctx, type, fromServer, data, flags) {
952
+ var self = this;
953
+ async.forEach(
954
+ this.onWebSocketFrameHandlers.concat(ctx.onWebSocketFrameHandlers),
955
+ function (fn, callback) {
956
+ return fn(ctx, type, fromServer, data, flags, function (err, newData, newFlags) {
957
+ if (err) {
958
+ return callback(err);
959
+ }
960
+ data = newData;
961
+ flags = newFlags;
962
+ return callback(null, data, flags);
963
+ });
964
+ },
965
+ function (err) {
966
+ if (err) {
967
+ return self._onWebSocketError(ctx, err);
968
+ }
969
+ var destWebSocket = fromServer ? ctx.clientToProxyWebSocket : ctx.proxyToServerWebSocket;
970
+ if (destWebSocket.readyState === WebSocket.OPEN) {
971
+ switch (type) {
972
+ case 'message':
973
+ destWebSocket.send(data, { binary: flags });
974
+ break;
975
+ case 'ping':
976
+ destWebSocket.ping(data, flags);
977
+ break;
978
+ case 'pong':
979
+ destWebSocket.pong(data, flags);
980
+ break;
981
+ }
982
+ } else {
983
+ self._onWebSocketError(ctx, new Error('Cannot send ' + type + ' because ' + (fromServer ? 'clientToProxy' : 'proxyToServer') + ' WebSocket connection state is not OPEN'));
984
+ }
985
+ }
986
+ );
987
+ };
988
+
989
+ Proxy.prototype._onWebSocketClose = function (ctx, closedByServer, code, message) {
990
+ var self = this;
991
+ if (!ctx.closedByServer && !ctx.closedByClient) {
992
+ ctx.closedByServer = closedByServer;
993
+ ctx.closedByClient = !closedByServer;
994
+ async.forEach(
995
+ this.onWebSocketCloseHandlers.concat(ctx.onWebSocketCloseHandlers),
996
+ function (fn, callback) {
997
+ return fn(ctx, code, message, callback);
998
+ },
999
+ function (err) {
1000
+ if (err) {
1001
+ return self._onWebSocketError(ctx, err);
1002
+ }
1003
+ if (ctx.clientToProxyWebSocket.readyState !== ctx.proxyToServerWebSocket.readyState) {
1004
+ try {
1005
+ if (ctx.clientToProxyWebSocket.readyState === WebSocket.CLOSED && ctx.proxyToServerWebSocket.readyState === WebSocket.OPEN) {
1006
+ code === 1005 ? ctx.proxyToServerWebSocket.close() : ctx.proxyToServerWebSocket.close(code, message);
1007
+ } else if (ctx.proxyToServerWebSocket.readyState === WebSocket.CLOSED && ctx.clientToProxyWebSocket.readyState === WebSocket.OPEN) {
1008
+ code === 1005 ? ctx.proxyToServerWebSocket.close() : ctx.clientToProxyWebSocket.close(code, message);
1009
+ }
1010
+ } catch (err) {
1011
+ return self._onWebSocketError(ctx, err);
1012
+ }
1013
+ }
1014
+ }
1015
+ );
1016
+ }
1017
+ };
1018
+
1019
+ Proxy.prototype._onWebSocketError = function (ctx, err) {
1020
+ this.onWebSocketErrorHandlers.forEach(function (handler) {
1021
+ return handler(ctx, err);
1022
+ });
1023
+ if (ctx) {
1024
+ ctx.onWebSocketErrorHandlers.forEach(function (handler) {
1025
+ return handler(ctx, err);
1026
+ });
1027
+ }
1028
+ if (ctx.proxyToServerWebSocket && ctx.clientToProxyWebSocket.readyState !== ctx.proxyToServerWebSocket.readyState) {
1029
+ try {
1030
+ if (ctx.clientToProxyWebSocket.readyState === WebSocket.CLOSED && ctx.proxyToServerWebSocket.readyState === WebSocket.OPEN) {
1031
+ ctx.proxyToServerWebSocket.close();
1032
+ } else if (ctx.proxyToServerWebSocket.readyState === WebSocket.CLOSED && ctx.clientToProxyWebSocket.readyState === WebSocket.OPEN) {
1033
+ ctx.clientToProxyWebSocket.close();
1034
+ }
1035
+ } catch (err) {
1036
+ // ignore
1037
+ }
1038
+ }
1039
+ };
1040
+
1041
+ Proxy.prototype._onRequestData = function (ctx, chunk, callback) {
1042
+ var self = this;
1043
+ async.forEach(
1044
+ this.onRequestDataHandlers.concat(ctx.onRequestDataHandlers),
1045
+ function (fn, callback) {
1046
+ return fn(ctx, chunk, function (err, newChunk) {
1047
+ if (err) {
1048
+ return callback(err);
1049
+ }
1050
+ chunk = newChunk;
1051
+ return callback(null, newChunk);
1052
+ });
1053
+ },
1054
+ function (err) {
1055
+ if (err) {
1056
+ return self._onError('ON_REQUEST_DATA_ERROR', ctx, err);
1057
+ }
1058
+ return callback(null, chunk);
1059
+ }
1060
+ );
1061
+ };
1062
+
1063
+ Proxy.prototype._onRequestEnd = function (ctx, callback) {
1064
+ var self = this;
1065
+ async.forEach(
1066
+ this.onRequestEndHandlers.concat(ctx.onRequestEndHandlers),
1067
+ function (fn, callback) {
1068
+ return fn(ctx, callback);
1069
+ },
1070
+ function (err) {
1071
+ if (err) {
1072
+ return self._onError('ON_REQUEST_END_ERROR', ctx, err);
1073
+ }
1074
+ return callback(null);
1075
+ }
1076
+ );
1077
+ };
1078
+
1079
+ Proxy.prototype._onResponse = function (ctx, callback) {
1080
+ async.forEach(
1081
+ this.onResponseHandlers.concat(ctx.onResponseHandlers),
1082
+ function (fn, callback) {
1083
+ return fn(ctx, callback);
1084
+ },
1085
+ callback
1086
+ );
1087
+ };
1088
+
1089
+ Proxy.prototype._onResponseHeaders = function (ctx, callback) {
1090
+ async.forEach(
1091
+ this.onResponseHeadersHandlers,
1092
+ function (fn, callback) {
1093
+ return fn(ctx, callback);
1094
+ },
1095
+ callback
1096
+ );
1097
+ };
1098
+
1099
+ Proxy.prototype._onResponseData = function (ctx, chunk, callback) {
1100
+ var self = this;
1101
+ async.forEach(
1102
+ this.onResponseDataHandlers.concat(ctx.onResponseDataHandlers),
1103
+ function (fn, callback) {
1104
+ return fn(ctx, chunk, function (err, newChunk) {
1105
+ if (err) {
1106
+ return callback(err);
1107
+ }
1108
+ chunk = newChunk;
1109
+ return callback(null, newChunk);
1110
+ });
1111
+ },
1112
+ function (err) {
1113
+ if (err) {
1114
+ return self._onError('ON_RESPONSE_DATA_ERROR', ctx, err);
1115
+ }
1116
+ return callback(null, chunk);
1117
+ }
1118
+ );
1119
+ };
1120
+
1121
+ Proxy.prototype._onResponseEnd = function (ctx, callback) {
1122
+ var self = this;
1123
+ async.forEach(
1124
+ this.onResponseEndHandlers.concat(ctx.onResponseEndHandlers),
1125
+ function (fn, callback) {
1126
+ return fn(ctx, callback);
1127
+ },
1128
+ function (err) {
1129
+ if (err) {
1130
+ return self._onError('ON_RESPONSE_END_ERROR', ctx, err);
1131
+ }
1132
+ return callback(null);
1133
+ }
1134
+ );
1135
+ };
1136
+
1137
+ Proxy.parseHostAndPort = function (req, defaultPort) {
1138
+ var m = req.url.match(/^http:\/\/([^\/]+)(.*)/);
1139
+ if (m) {
1140
+ req.url = m[2] || '/';
1141
+ return Proxy.parseHost(m[1], defaultPort);
1142
+ } else if (req.headers.host) {
1143
+ return Proxy.parseHost(req.headers.host, defaultPort);
1144
+ } else {
1145
+ return null;
1146
+ }
1147
+ };
1148
+
1149
+ Proxy.parseHost = function (hostString, defaultPort) {
1150
+ function unescapeHost(host) {
1151
+ return host.replace('[', '').replace(']', '');
1152
+ }
1153
+ var m = hostString.match(/^http:\/\/(.*)/);
1154
+ if (m) {
1155
+ var parsedUrl = url.parse(hostString);
1156
+ return {
1157
+ host: parsedUrl.hostname,
1158
+ hostUnescaped: unescapeHost(parsedUrl.hostname),
1159
+ port: parsedUrl.port,
1160
+ };
1161
+ }
1162
+
1163
+ var host;
1164
+ if (hostString.indexOf(']') !== -1) {
1165
+ // IPv6
1166
+ host = hostString.substring(0, hostString.indexOf(']') + 1);
1167
+ } else {
1168
+ if (hostString.indexOf(':') !== -1) {
1169
+ host = hostString.substring(0, hostString.indexOf(':'));
1170
+ } else {
1171
+ host = hostString;
1172
+ }
1173
+ }
1174
+ var portString = hostString.substring(host.length + 1);
1175
+ var port = portString.length > 0 ? +portString : defaultPort;
1176
+
1177
+ return {
1178
+ host: host,
1179
+ hostUnescaped: unescapeHost(host),
1180
+ port: port,
1181
+ };
1182
+ };
1183
+
1184
+ Proxy.filterAndCanonizeHeaders = function (originalHeaders) {
1185
+ var headers = {};
1186
+ for (var key in originalHeaders) {
1187
+ var canonizedKey = key.trim();
1188
+ if (/^public\-key\-pins/i.test(canonizedKey)) {
1189
+ // HPKP header => filter
1190
+ continue;
1191
+ }
1192
+
1193
+ if (!nodeCommon._checkInvalidHeaderChar(originalHeaders[key])) {
1194
+ headers[canonizedKey] = originalHeaders[key];
1195
+ }
1196
+ }
1197
+
1198
+ return headers;
1199
+ };