@otterhttp/proxy-address
Version:
proxy-addr rewrite with TypeScript and ESM support
120 lines (119 loc) • 4.08 kB
JavaScript
// src/index.ts
import { forwarded } from "@otterhttp/forwarded";
import ipaddr from "ipaddr.js";
var DIGIT_REGEXP = /^[0-9]+$/;
var isIp = ipaddr.isValid;
var parseIp = ipaddr.parse;
var 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"]
};
function isIPRangeName(val) {
return Object.prototype.hasOwnProperty.call(IP_RANGES, val);
}
var isIPv4 = (val) => val.kind() === "ipv4";
var isIPv6 = (val) => val.kind() === "ipv6";
var trustNone = () => false;
function allAddresses(req, trust) {
const addressGenerator = forwarded(req);
if (trust == null) return Array.from(addressGenerator);
if (typeof trust !== "function") trust = compile(trust);
let i = 0;
const addresses = [];
for (const address of addressGenerator) {
addresses.push(address);
if (!trust(address, i)) break;
i += 1;
}
return addresses;
}
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;
const namedRange = IP_RANGES[element];
trust.splice(i, 1, ...namedRange);
i += namedRange.length - 1;
}
return compileTrust(compileRangeSubnets(trust));
}
function compileHopsTrust(hops) {
return (_, i) => i < hops;
}
function compileRangeSubnets(arr) {
return arr.map((ip) => parseIPNotation(ip));
}
function compileTrust(rangeSubnets) {
const len = rangeSubnets.length;
return len === 0 ? trustNone : len === 1 ? trustSingle(rangeSubnets[0]) : trustMulti(rangeSubnets);
}
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 };
}
function parseNetmask(netmask) {
const ip = parseIp(netmask);
return ip.kind() === "ipv4" ? ip.prefixLengthFromSubnetMask() : null;
}
function proxyAddress(req, trust) {
if (trust == null) throw new TypeError("trust argument cannot be null-ish");
const addresses = allAddresses(req, trust);
return addresses[addresses.length - 1];
}
function trustMulti(subnets) {
return function trust(ip) {
if (ip == null) return false;
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;
};
}
function trustSingle(subnet) {
const subnetKind = subnet.ip.kind();
const subnetIsIPv4 = subnetKind === "ipv4";
return function trust(ip) {
if (ip == null) return false;
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 {
allAddresses,
compile,
parseIPNotation,
proxyAddress
};