web_reader / src /utils /ip.ts
nomagick's picture
fix: add 0.0.0.0/32 to the non Public CIDRs (#1181)
b58a2db unverified
import { isIPv4, isIPv6 } from 'net';
export function parseIp(ip: string): Buffer {
if (isIPv4(ip)) {
const [a, b, c, d] = ip.split('.').map(Number);
const buf = Buffer.alloc(4);
buf.writeUInt8(a, 0);
buf.writeUInt8(b, 1);
buf.writeUInt8(c, 2);
buf.writeUInt8(d, 3);
return buf;
}
if (isIPv6(ip)) {
if (ip.includes('.')) {
const parts = ip.split(':');
const ipv4Part = parts.pop();
if (!ipv4Part) throw new Error('Invalid IPv6 address');
const ipv4Bytes = parseIp(ipv4Part);
parts.push('0');
const ipv6Bytes = parseIp(parts.join(':'));
ipv6Bytes.writeUInt32BE(ipv4Bytes.readUInt32BE(0), 12);
return ipv6Bytes;
}
const buf = Buffer.alloc(16);
// Expand :: notation
let expanded = ip;
if (ip.includes('::')) {
const sides = ip.split('::');
const left = sides[0] ? sides[0].split(':') : [];
const right = sides[1] ? sides[1].split(':') : [];
const middle = Array(8 - left.length - right.length).fill('0');
expanded = [...left, ...middle, ...right].join(':');
}
// Convert to buffer
const parts = expanded.split(':');
let offset = 0;
for (const part of parts) {
buf.writeUInt16BE(parseInt(part, 16), offset);
offset += 2;
}
return buf;
}
throw new Error('Invalid IP address');
}
export function parseCIDR(cidr: string): [Buffer, Buffer] {
const [ip, prefixTxt] = cidr.split('/');
const buf = parseIp(ip);
const maskBuf = Buffer.alloc(buf.byteLength, 0xff);
const prefixBits = parseInt(prefixTxt);
let offsetBits = 0;
while (offsetBits < (buf.byteLength * 8)) {
if (offsetBits <= (prefixBits - 8)) {
offsetBits += 8;
continue;
}
const bitsRemain = prefixBits - offsetBits;
const byteOffset = Math.floor(offsetBits / 8);
if (bitsRemain > 0) {
const theByte = buf[byteOffset];
const mask = 0xff << (8 - bitsRemain);
maskBuf[byteOffset] = mask;
buf[byteOffset] = theByte & mask;
offsetBits += 8;
continue;
};
buf[byteOffset] = 0;
maskBuf[byteOffset] = 0;
offsetBits += 8;
}
return [buf, maskBuf];
}
export class CIDR {
buff: Buffer;
mask: Buffer;
text: string;
constructor(cidr: string) {
this.text = cidr;
[this.buff, this.mask] = parseCIDR(cidr);
}
toString() {
return this.text;
}
get family() {
return this.buff.byteLength === 4 ? 4 : 6;
}
test(ip: string | Buffer): boolean {
const parsedIp = typeof ip === 'string' ? parseIp(ip) : ip;
if (parsedIp.byteLength !== this.buff.byteLength) {
return false;
}
for (const i of Array(this.buff.byteLength).keys()) {
const t = parsedIp[i];
const m = this.mask[i];
if (m === 0) {
return true;
}
const r = this.buff[i];
if ((t & m) !== r) {
return false;
}
}
return true;
}
}
const nonPublicNetworks4 = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'255.255.255.255/32',
'169.254.0.0/16',
'224.0.0.0/4',
'100.64.0.0/10',
'0.0.0.0/32',
];
const nonPublicNetworks6 = [
'fc00::/7',
'fe80::/10',
'ff00::/8',
'::127.0.0.0/104',
'::/128',
];
const nonPublicCIDRs = [...nonPublicNetworks4, ...nonPublicNetworks6].map(cidr => new CIDR(cidr));
export function isIPInNonPublicRange(ip: string) {
const parsed = parseIp(ip);
for (const cidr of nonPublicCIDRs) {
if (cidr.test(parsed)) {
return true;
}
}
return false;
}