1
|
/*!
|
2
|
* proxy-addr
|
3
|
* Copyright(c) 2014-2016 Douglas Christopher Wilson
|
4
|
* MIT Licensed
|
5
|
*/
|
6
|
|
7
|
'use strict'
|
8
|
|
9
|
/**
|
10
|
* Module exports.
|
11
|
* @public
|
12
|
*/
|
13
|
|
14
|
module.exports = proxyaddr
|
15
|
module.exports.all = alladdrs
|
16
|
module.exports.compile = compile
|
17
|
|
18
|
/**
|
19
|
* Module dependencies.
|
20
|
* @private
|
21
|
*/
|
22
|
|
23
|
var forwarded = require('forwarded')
|
24
|
var ipaddr = require('ipaddr.js')
|
25
|
|
26
|
/**
|
27
|
* Variables.
|
28
|
* @private
|
29
|
*/
|
30
|
|
31
|
var DIGIT_REGEXP = /^[0-9]+$/
|
32
|
var isip = ipaddr.isValid
|
33
|
var parseip = ipaddr.parse
|
34
|
|
35
|
/**
|
36
|
* Pre-defined IP ranges.
|
37
|
* @private
|
38
|
*/
|
39
|
|
40
|
var IP_RANGES = {
|
41
|
linklocal: ['169.254.0.0/16', 'fe80::/10'],
|
42
|
loopback: ['127.0.0.1/8', '::1/128'],
|
43
|
uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7']
|
44
|
}
|
45
|
|
46
|
/**
|
47
|
* Get all addresses in the request, optionally stopping
|
48
|
* at the first untrusted.
|
49
|
*
|
50
|
* @param {Object} request
|
51
|
* @param {Function|Array|String} [trust]
|
52
|
* @public
|
53
|
*/
|
54
|
|
55
|
function alladdrs (req, trust) {
|
56
|
// get addresses
|
57
|
var addrs = forwarded(req)
|
58
|
|
59
|
if (!trust) {
|
60
|
// Return all addresses
|
61
|
return addrs
|
62
|
}
|
63
|
|
64
|
if (typeof trust !== 'function') {
|
65
|
trust = compile(trust)
|
66
|
}
|
67
|
|
68
|
for (var i = 0; i < addrs.length - 1; i++) {
|
69
|
if (trust(addrs[i], i)) continue
|
70
|
|
71
|
addrs.length = i + 1
|
72
|
}
|
73
|
|
74
|
return addrs
|
75
|
}
|
76
|
|
77
|
/**
|
78
|
* Compile argument into trust function.
|
79
|
*
|
80
|
* @param {Array|String} val
|
81
|
* @private
|
82
|
*/
|
83
|
|
84
|
function compile (val) {
|
85
|
if (!val) {
|
86
|
throw new TypeError('argument is required')
|
87
|
}
|
88
|
|
89
|
var trust
|
90
|
|
91
|
if (typeof val === 'string') {
|
92
|
trust = [val]
|
93
|
} else if (Array.isArray(val)) {
|
94
|
trust = val.slice()
|
95
|
} else {
|
96
|
throw new TypeError('unsupported trust argument')
|
97
|
}
|
98
|
|
99
|
for (var i = 0; i < trust.length; i++) {
|
100
|
val = trust[i]
|
101
|
|
102
|
if (!Object.prototype.hasOwnProperty.call(IP_RANGES, val)) {
|
103
|
continue
|
104
|
}
|
105
|
|
106
|
// Splice in pre-defined range
|
107
|
val = IP_RANGES[val]
|
108
|
trust.splice.apply(trust, [i, 1].concat(val))
|
109
|
i += val.length - 1
|
110
|
}
|
111
|
|
112
|
return compileTrust(compileRangeSubnets(trust))
|
113
|
}
|
114
|
|
115
|
/**
|
116
|
* Compile `arr` elements into range subnets.
|
117
|
*
|
118
|
* @param {Array} arr
|
119
|
* @private
|
120
|
*/
|
121
|
|
122
|
function compileRangeSubnets (arr) {
|
123
|
var rangeSubnets = new Array(arr.length)
|
124
|
|
125
|
for (var i = 0; i < arr.length; i++) {
|
126
|
rangeSubnets[i] = parseipNotation(arr[i])
|
127
|
}
|
128
|
|
129
|
return rangeSubnets
|
130
|
}
|
131
|
|
132
|
/**
|
133
|
* Compile range subnet array into trust function.
|
134
|
*
|
135
|
* @param {Array} rangeSubnets
|
136
|
* @private
|
137
|
*/
|
138
|
|
139
|
function compileTrust (rangeSubnets) {
|
140
|
// Return optimized function based on length
|
141
|
var len = rangeSubnets.length
|
142
|
return len === 0
|
143
|
? trustNone
|
144
|
: len === 1
|
145
|
? trustSingle(rangeSubnets[0])
|
146
|
: trustMulti(rangeSubnets)
|
147
|
}
|
148
|
|
149
|
/**
|
150
|
* Parse IP notation string into range subnet.
|
151
|
*
|
152
|
* @param {String} note
|
153
|
* @private
|
154
|
*/
|
155
|
|
156
|
function parseipNotation (note) {
|
157
|
var pos = note.lastIndexOf('/')
|
158
|
var str = pos !== -1
|
159
|
? note.substring(0, pos)
|
160
|
: note
|
161
|
|
162
|
if (!isip(str)) {
|
163
|
throw new TypeError('invalid IP address: ' + str)
|
164
|
}
|
165
|
|
166
|
var ip = parseip(str)
|
167
|
|
168
|
if (pos === -1 && ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
|
169
|
// Store as IPv4
|
170
|
ip = ip.toIPv4Address()
|
171
|
}
|
172
|
|
173
|
var max = ip.kind() === 'ipv6'
|
174
|
? 128
|
175
|
: 32
|
176
|
|
177
|
var range = pos !== -1
|
178
|
? note.substring(pos + 1, note.length)
|
179
|
: null
|
180
|
|
181
|
if (range === null) {
|
182
|
range = max
|
183
|
} else if (DIGIT_REGEXP.test(range)) {
|
184
|
range = parseInt(range, 10)
|
185
|
} else if (ip.kind() === 'ipv4' && isip(range)) {
|
186
|
range = parseNetmask(range)
|
187
|
} else {
|
188
|
range = null
|
189
|
}
|
190
|
|
191
|
if (range <= 0 || range > max) {
|
192
|
throw new TypeError('invalid range on address: ' + note)
|
193
|
}
|
194
|
|
195
|
return [ip, range]
|
196
|
}
|
197
|
|
198
|
/**
|
199
|
* Parse netmask string into CIDR range.
|
200
|
*
|
201
|
* @param {String} netmask
|
202
|
* @private
|
203
|
*/
|
204
|
|
205
|
function parseNetmask (netmask) {
|
206
|
var ip = parseip(netmask)
|
207
|
var kind = ip.kind()
|
208
|
|
209
|
return kind === 'ipv4'
|
210
|
? ip.prefixLengthFromSubnetMask()
|
211
|
: null
|
212
|
}
|
213
|
|
214
|
/**
|
215
|
* Determine address of proxied request.
|
216
|
*
|
217
|
* @param {Object} request
|
218
|
* @param {Function|Array|String} trust
|
219
|
* @public
|
220
|
*/
|
221
|
|
222
|
function proxyaddr (req, trust) {
|
223
|
if (!req) {
|
224
|
throw new TypeError('req argument is required')
|
225
|
}
|
226
|
|
227
|
if (!trust) {
|
228
|
throw new TypeError('trust argument is required')
|
229
|
}
|
230
|
|
231
|
var addrs = alladdrs(req, trust)
|
232
|
var addr = addrs[addrs.length - 1]
|
233
|
|
234
|
return addr
|
235
|
}
|
236
|
|
237
|
/**
|
238
|
* Static trust function to trust nothing.
|
239
|
*
|
240
|
* @private
|
241
|
*/
|
242
|
|
243
|
function trustNone () {
|
244
|
return false
|
245
|
}
|
246
|
|
247
|
/**
|
248
|
* Compile trust function for multiple subnets.
|
249
|
*
|
250
|
* @param {Array} subnets
|
251
|
* @private
|
252
|
*/
|
253
|
|
254
|
function trustMulti (subnets) {
|
255
|
return function trust (addr) {
|
256
|
if (!isip(addr)) return false
|
257
|
|
258
|
var ip = parseip(addr)
|
259
|
var ipconv
|
260
|
var kind = ip.kind()
|
261
|
|
262
|
for (var i = 0; i < subnets.length; i++) {
|
263
|
var subnet = subnets[i]
|
264
|
var subnetip = subnet[0]
|
265
|
var subnetkind = subnetip.kind()
|
266
|
var subnetrange = subnet[1]
|
267
|
var trusted = ip
|
268
|
|
269
|
if (kind !== subnetkind) {
|
270
|
if (subnetkind === 'ipv4' && !ip.isIPv4MappedAddress()) {
|
271
|
// Incompatible IP addresses
|
272
|
continue
|
273
|
}
|
274
|
|
275
|
if (!ipconv) {
|
276
|
// Convert IP to match subnet IP kind
|
277
|
ipconv = subnetkind === 'ipv4'
|
278
|
? ip.toIPv4Address()
|
279
|
: ip.toIPv4MappedAddress()
|
280
|
}
|
281
|
|
282
|
trusted = ipconv
|
283
|
}
|
284
|
|
285
|
if (trusted.match(subnetip, subnetrange)) {
|
286
|
return true
|
287
|
}
|
288
|
}
|
289
|
|
290
|
return false
|
291
|
}
|
292
|
}
|
293
|
|
294
|
/**
|
295
|
* Compile trust function for single subnet.
|
296
|
*
|
297
|
* @param {Object} subnet
|
298
|
* @private
|
299
|
*/
|
300
|
|
301
|
function trustSingle (subnet) {
|
302
|
var subnetip = subnet[0]
|
303
|
var subnetkind = subnetip.kind()
|
304
|
var subnetisipv4 = subnetkind === 'ipv4'
|
305
|
var subnetrange = subnet[1]
|
306
|
|
307
|
return function trust (addr) {
|
308
|
if (!isip(addr)) return false
|
309
|
|
310
|
var ip = parseip(addr)
|
311
|
var kind = ip.kind()
|
312
|
|
313
|
if (kind !== subnetkind) {
|
314
|
if (subnetisipv4 && !ip.isIPv4MappedAddress()) {
|
315
|
// Incompatible IP addresses
|
316
|
return false
|
317
|
}
|
318
|
|
319
|
// Convert IP to match subnet IP kind
|
320
|
ip = subnetisipv4
|
321
|
? ip.toIPv4Address()
|
322
|
: ip.toIPv4MappedAddress()
|
323
|
}
|
324
|
|
325
|
return ip.match(subnetip, subnetrange)
|
326
|
}
|
327
|
}
|