-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.js
193 lines (174 loc) · 5.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
'use strict';
var net = require('net');
/**
* Forwarded instance.
*
* @constructor
* @param {String} ip The IP address.
* @param {Number} port The port number.
* @param {Boolean} secured The connection was secured.
* @api private
*/
function Forwarded(ip, port, secured) {
this.ip = ip || '127.0.0.1';
this.secure = !!secured;
this.port = +port || 0;
}
/**
* List of possible proxy headers that should be checked for the original client
* IP address and forwarded port.
*
* @type {Array}
* @private
*/
var proxies = [
{
ip: 'fastly-client-ip',
port: 'fastly-client-port', // Estimated guess, no standard header available.
proto: 'fastly-ssl'
},
{
ip: 'x-forwarded-for',
port: 'x-forwarded-port',
proto: 'x-forwarded-proto'
}, {
ip: 'z-forwarded-for',
port: 'z-forwarded-port', // Estimated guess, no standard header available.
proto: 'z-forwarded-proto' // Estimated guess, no standard header available.
}, {
ip: 'forwarded',
port: 'forwarded-port',
proto: 'forwarded-proto' // Estimated guess, no standard header available.
}, {
ip: 'x-real-ip',
port: 'x-real-port', // Estimated guess, no standard header available.
proto: 'x-real-proto' // Estimated guess, no standard header available.
}
];
/**
* Search the headers for a possible match against a known proxy header.
*
* @param {Object} headers The received HTTP headers.
* @param {Array} whitelist White list of proxies that should be checked.
* @returns {Forwarded|Undefined} A Forwarded address object or nothing.
* @api private
*/
function forwarded(headers, whitelist) {
var parts, ports, port, proto, ips, ip, length = proxies.length, i = 0;
for (; i < length; i++) {
if (!(proxies[i].ip in headers)) continue;
ports = (headers[proxies[i].port] || '').split(',');
ips = (headers[proxies[i].ip] || '')
.split(',')
.map((entry, j) => {
if (net.isIPv6(entry))
return entry.trim();
else {
parts = entry.split(':');
if (parts[1]) {
ports.length = Math.max(j+1, ports.length);
ports[j] = parts[1].trim();
}
return parts[0].trim();
}
});
proto = (headers[proxies[i].proto] || 'http');
//
// As these headers can potentially be set by a 1337H4X0R we need to ensure
// that all supplied values are valid IP addresses. If we receive a none
// IP value inside the IP header field we are going to assume that this
// header has been compromised and should be ignored
//
if (!ips || !ips.every(net.isIP)) return;
port = ports.shift(); // Extract the first port as it's the "source" port.
ip = ips.shift(); // Extract the first IP as it's the "source" IP.
//
// If we were given a white list, we need to ensure that the proxies that
// we're given are known and allowed.
//
if (whitelist && whitelist.length && !ips.every(function every(ip) {
return ~whitelist.indexOf(ip);
})) return;
//
// Shift the most recently found proxy header to the front of the proxies
// array. This optimizes future calls, placing the most commonly found headers
// near the front of the array.
//
if (i !== 0) {
proxies.unshift(proxies.splice(i, 1)[0]);
}
//
// We've gotten a match on a HTTP header, we need to parse it further as it
// could consist of multiple hops. The pattern for multiple hops is:
//
// client, proxy, proxy, proxy, etc.
//
// So extracting the first IP should be sufficient. There are SSL
// terminators like the once's that is used by `fastly.com` which set their
// HTTPS header to `1` as an indication that the connection was secure.
// (This reduces bandwidth)
//
return new Forwarded(ip, port, proto === '1' || proto === 'https');
}
}
/**
* Parse out the address information..
*
* @param {Object} obj A socket like object that could contain a `remoteAddress`.
* @param {Object} headers The received HTTP headers.
* @param {Array} whitelist White list
* @returns {Forwarded} A Forwarded address object.
* @api private
*/
function parse(obj, headers, whitelist) {
var proxied = forwarded(headers || {}, whitelist)
, connection = obj.connection
, socket = connection
? connection.socket
: obj.socket;
//
// We should always be testing for HTTP headers as remoteAddress would point
// to proxies.
//
if (proxied) return proxied;
// Check for the property on our given object.
if ('object' === typeof obj) {
if ('remoteAddress' in obj) {
return new Forwarded(
obj.remoteAddress,
obj.remotePort,
'secure' in obj ? obj.secure : obj.encrypted
);
}
// Edge case for Socket.IO 0.9
if ('object' === typeof obj.address && obj.address.address) {
return new Forwarded(
obj.address.address,
obj.address.port,
'secure' in obj ? obj.secure : obj.encrypted
);
}
}
if ('object' === typeof connection && 'remoteAddress' in connection) {
return new Forwarded(
connection.remoteAddress,
connection.remotePort,
'secure' in connection ? connection.secure : connection.encrypted
);
}
if ('object' === typeof socket && 'remoteAddress' in socket) {
return new Forwarded(
socket.remoteAddress,
socket.remoteAddress,
'secure' in socket ? socket.secure : socket.encrypted
);
}
return new Forwarded();
}
//
// Expose the module and all of it's interfaces.
//
parse.Forwarded = Forwarded;
parse.forwarded = forwarded;
parse.proxies = proxies;
module.exports = parse;