import { forwarded } from '@tinyhttp/forwarded'; import ipaddr from 'ipaddr.js'; const DIGIT_REGEXP = /^[0-9]+$/; const isip = ipaddr.isValid; const parseip = ipaddr.parse; /** * Pre-defined IP ranges. */ const IP_RANGES = { 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'] }; /** * Type-guard to determine whether a string value represents a pre-defined IP range. * * @param val */ function isIPRangeName(val) { return Object.prototype.hasOwnProperty.call(IP_RANGES, val); } /** * Type-guard to determine whether an IP address is a v4 address. * @param val */ const isIPv4 = (val) => val.kind() === 'ipv4'; /** * Type-guard to determine whether an IP address is a v6 address. * @param val */ const isIPv6 = (val) => val.kind() === 'ipv6'; /** * Static trust function to trust nothing. */ const trustNone = () => false; /** * Get all addresses in the request, optionally stopping * at the first untrusted. * * @param req * @param trust */ function alladdrs(req, trust) { // get addresses const addrs = forwarded(req); if (trust == null) return addrs; if (typeof trust !== 'function') trust = compile(trust); for (let 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 val */ function compile(val) { let trust; if (typeof val === 'string') trust = [val]; else if (typeof val === 'number') return compileHopsTrust(val); else if (Array.isArray(val)) trust = val.slice(); else throw new TypeError('unsupported trust argument'); for (let i = 0; i < trust.length; i++) { const element = trust[i]; if (!isIPRangeName(element)) continue; // Splice in pre-defined range const namedRange = IP_RANGES[element]; trust.splice(i, 1, ...namedRange); i += namedRange.length - 1; } return compileTrust(compileRangeSubnets(trust)); } /** * Compile 'hops' number into trust function. * * @param hops */ function compileHopsTrust(hops) { return (_, i) => i < hops; } /** * Compile `arr` elements into range subnets. */ function compileRangeSubnets(arr) { return arr.map((ip) => parseIPNotation(ip)); } /** * Compile range subnet array into trust function. * * @param rangeSubnets */ function compileTrust(rangeSubnets) { // Return optimized function based on length const len = rangeSubnets.length; return len === 0 ? trustNone : len === 1 ? trustSingle(rangeSubnets[0]) : trustMulti(rangeSubnets); } /** * Parse IP notation string into range subnet. * * @param {String} note * @private */ export function parseIPNotation(note) { const pos = note.lastIndexOf('/'); const str = pos !== -1 ? note.substring(0, pos) : note; if (!isip(str)) throw new TypeError(`invalid IP address: ${str}`); let ip = parseip(str); const max = ip.kind() === 'ipv6' ? 128 : 32; if (pos === -1) { if (isIPv6(ip) && ip.isIPv4MappedAddress()) ip = ip.toIPv4Address(); return { ip, range: max }; } const rangeString = note.substring(pos + 1, note.length); let range = null; if (DIGIT_REGEXP.test(rangeString)) range = Number.parseInt(rangeString, 10); else if (ip.kind() === 'ipv4' && isip(rangeString)) range = parseNetmask(rangeString); if (range == null || range <= 0 || range > max) throw new TypeError(`invalid range on address: ${note}`); return { ip, range }; } /** * Parse netmask string into CIDR range. * * @param netmask * @private */ function parseNetmask(netmask) { const ip = parseip(netmask); return ip.kind() === 'ipv4' ? ip.prefixLengthFromSubnetMask() : null; } /** * Determine address of proxied request. * * @param req * @param trust * @public */ export function proxyaddr(req, trust) { const addrs = alladdrs(req, trust); return addrs[addrs.length - 1]; } /** * Compile trust function for multiple subnets. */ function trustMulti(subnets) { return function trust(addr) { if (!isip(addr)) return false; const ip = parseip(addr); let ipconv = null; const kind = ip.kind(); for (let i = 0; i < subnets.length; i++) { const subnet = subnets[i]; const subnetKind = subnet.ip.kind(); let trusted = ip; if (kind !== subnetKind) { if (isIPv6(ip) && !ip.isIPv4MappedAddress()) continue; if (!ipconv) ipconv = isIPv4(ip) ? ip.toIPv4MappedAddress() : ip.toIPv4Address(); trusted = ipconv; } if (trusted.match(subnet.ip, subnet.range)) return true; } return false; }; } /** * Compile trust function for single subnet. * * @param subnet */ function trustSingle(subnet) { const subnetKind = subnet.ip.kind(); const subnetIsIPv4 = subnetKind === 'ipv4'; return function trust(addr) { if (!isip(addr)) return false; let ip = parseip(addr); const kind = ip.kind(); if (kind !== subnetKind) { if (subnetIsIPv4 && !ip.isIPv4MappedAddress()) return false; ip = subnetIsIPv4 ? ip.toIPv4Address() : ip.toIPv4MappedAddress(); } return ip.match(subnet.ip, subnet.range); }; } export { alladdrs as all }; export { compile }; //# sourceMappingURL=index.js.map