| /*! |
| * proxy-addr |
| * Copyright(c) 2014 Douglas Christopher Wilson |
| * MIT Licensed |
| */ |
| |
| 'use strict' |
| |
| /** |
| * Module exports. |
| */ |
| |
| module.exports = proxyaddr; |
| module.exports.all = alladdrs; |
| module.exports.compile = compile; |
| |
| /** |
| * Module dependencies. |
| */ |
| |
| var forwarded = require('forwarded'); |
| var ipaddr = require('ipaddr.js'); |
| |
| /** |
| * Variables. |
| */ |
| |
| var digitre = /^[0-9]+$/; |
| var isip = ipaddr.isValid; |
| var parseip = ipaddr.parse; |
| |
| /** |
| * Pre-defined IP ranges. |
| */ |
| |
| var ipranges = { |
| linklocal: ['169.254.0.0/16', 'fe80::/10'], |
| loopback: ['127.0.0.1/8', '::1/128'], |
| uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'] |
| }; |
| |
| /** |
| * Get all addresses in the request, optionally stopping |
| * at the first untrusted. |
| * |
| * @param {Object} request |
| * @param {Function|Array|String} [trust] |
| * @api public |
| */ |
| |
| function alladdrs(req, trust) { |
| // get addresses |
| var addrs = forwarded(req); |
| |
| if (!trust) { |
| // Return all addresses |
| return addrs; |
| } |
| |
| if (typeof trust !== 'function') { |
| trust = compile(trust); |
| } |
| |
| for (var i = 0; i < addrs.length - 1; i++) { |
| if (trust(addrs[i], i)) continue; |
| |
| addrs.length = i + 1; |
| } |
| |
| return addrs; |
| } |
| |
| /** |
| * Compile argument into trust function. |
| * |
| * @param {Array|String} val |
| * @api private |
| */ |
| |
| function compile(val) { |
| if (!val) { |
| throw new TypeError('argument is required'); |
| } |
| |
| var trust = typeof val === 'string' |
| ? [val] |
| : val; |
| |
| if (!Array.isArray(trust)) { |
| throw new TypeError('unsupported trust argument'); |
| } |
| |
| for (var i = 0; i < trust.length; i++) { |
| val = trust[i]; |
| |
| if (!ipranges.hasOwnProperty(val)) { |
| continue; |
| } |
| |
| // Splice in pre-defined range |
| val = ipranges[val]; |
| trust.splice.apply(trust, [i, 1].concat(val)); |
| i += val.length - 1; |
| } |
| |
| return compileTrust(compileRangeSubnets(trust)); |
| } |
| |
| /** |
| * Compile `arr` elements into range subnets. |
| * |
| * @param {Array} arr |
| * @api private |
| */ |
| |
| function compileRangeSubnets(arr) { |
| var rangeSubnets = new Array(arr.length); |
| |
| for (var i = 0; i < arr.length; i++) { |
| rangeSubnets[i] = parseipNotation(arr[i]); |
| } |
| |
| return rangeSubnets; |
| } |
| |
| /** |
| * Compile range subnet array into trust function. |
| * |
| * @param {Array} rangeSubnets |
| * @api private |
| */ |
| |
| function compileTrust(rangeSubnets) { |
| // Return optimized function based on length |
| var len = rangeSubnets.length; |
| return len === 0 |
| ? trustNone |
| : len === 1 |
| ? trustSingle(rangeSubnets[0]) |
| : trustMulti(rangeSubnets); |
| } |
| |
| /** |
| * Parse IP notation string into range subnet. |
| * |
| * @param {String} note |
| * @api private |
| */ |
| |
| function parseipNotation(note) { |
| var ip; |
| var kind; |
| var max; |
| var pos = note.lastIndexOf('/'); |
| var range; |
| |
| ip = pos !== -1 |
| ? note.substring(0, pos) |
| : note; |
| |
| if (!isip(ip)) { |
| throw new TypeError('invalid IP address: ' + ip); |
| } |
| |
| ip = parseip(ip); |
| |
| kind = ip.kind(); |
| max = kind === 'ipv6' |
| ? 128 |
| : 32; |
| |
| range = pos !== -1 |
| ? note.substring(pos + 1, note.length) |
| : max; |
| |
| if (typeof range !== 'number') { |
| range = digitre.test(range) |
| ? parseInt(range, 10) |
| : isip(range) |
| ? parseNetmask(range) |
| : 0; |
| } |
| |
| if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) { |
| // Store as IPv4 |
| ip = ip.toIPv4Address(); |
| range = range <= max |
| ? range - 96 |
| : range; |
| } |
| |
| if (range <= 0 || range > max) { |
| throw new TypeError('invalid range on address: ' + note); |
| } |
| |
| return [ip, range]; |
| } |
| |
| /** |
| * Parse netmask string into CIDR range. |
| * |
| * @param {String} note |
| * @api private |
| */ |
| |
| function parseNetmask(netmask) { |
| var ip = parseip(netmask); |
| var parts; |
| var size; |
| |
| switch (ip.kind()) { |
| case 'ipv4': |
| parts = ip.octets; |
| size = 8; |
| break; |
| case 'ipv6': |
| parts = ip.parts; |
| size = 16; |
| break; |
| } |
| |
| var max = Math.pow(2, size) - 1; |
| var part; |
| var range = 0; |
| |
| for (var i = 0; i < parts.length; i++) { |
| part = parts[i] & max; |
| |
| if (part === max) { |
| range += size; |
| continue; |
| } |
| |
| while (part) { |
| part = (part << 1) & max; |
| range += 1; |
| } |
| |
| break; |
| } |
| |
| return range; |
| } |
| |
| /** |
| * Determine address of proxied request. |
| * |
| * @param {Object} request |
| * @param {Function|Array|String} trust |
| * @api public |
| */ |
| |
| function proxyaddr(req, trust) { |
| if (!req) { |
| throw new TypeError('req argument is required'); |
| } |
| |
| if (!trust) { |
| throw new TypeError('trust argument is required'); |
| } |
| |
| var addrs = alladdrs(req, trust); |
| var addr = addrs[addrs.length - 1]; |
| |
| return addr; |
| } |
| |
| /** |
| * Static trust function to trust nothing. |
| * |
| * @api private |
| */ |
| |
| function trustNone() { |
| return false; |
| } |
| |
| /** |
| * Compile trust function for multiple subnets. |
| * |
| * @param {Array} subnets |
| * @api private |
| */ |
| |
| function trustMulti(subnets) { |
| return function trust(addr) { |
| if (!isip(addr)) return false; |
| |
| var ip = parseip(addr); |
| var ipv4; |
| var kind = ip.kind(); |
| var subnet; |
| var subnetip; |
| var subnetkind; |
| var subnetrange; |
| var trusted; |
| |
| for (var i = 0; i < subnets.length; i++) { |
| subnet = subnets[i]; |
| subnetip = subnet[0]; |
| subnetkind = subnetip.kind(); |
| subnetrange = subnet[1]; |
| trusted = ip; |
| |
| if (kind !== subnetkind) { |
| if (kind !== 'ipv6' || subnetkind !== 'ipv4' || !ip.isIPv4MappedAddress()) { |
| continue; |
| } |
| |
| // Store addr as IPv4 |
| ipv4 = ipv4 || ip.toIPv4Address(); |
| trusted = ipv4; |
| } |
| |
| if (trusted.match(subnetip, subnetrange)) return true; |
| } |
| |
| return false; |
| }; |
| } |
| |
| /** |
| * Compile trust function for single subnet. |
| * |
| * @param {Object} subnet |
| * @api private |
| */ |
| |
| function trustSingle(subnet) { |
| var subnetip = subnet[0]; |
| var subnetkind = subnetip.kind(); |
| var subnetisipv4 = subnetkind === 'ipv4'; |
| var subnetrange = subnet[1]; |
| |
| return function trust(addr) { |
| if (!isip(addr)) return false; |
| |
| var ip = parseip(addr); |
| var kind = ip.kind(); |
| |
| return kind === subnetkind |
| ? ip.match(subnetip, subnetrange) |
| : subnetisipv4 && kind === 'ipv6' && ip.isIPv4MappedAddress() |
| ? ip.toIPv4Address().match(subnetip, subnetrange) |
| : false; |
| }; |
| } |