W / src /net-safety.js
Ac66's picture
Upload folder using huggingface_hub
2b64d42 verified
import net from 'node:net';
import { lookup as dnsLookup } from 'node:dns';
function ipv4ToInt(ip) {
const parts = ip.split('.').map(n => Number(n));
if (parts.length !== 4 || parts.some(n => !Number.isInteger(n) || n < 0 || n > 255)) return null;
return (((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3]) >>> 0;
}
function ipv4InCidr(ip, base, bits) {
const n = ipv4ToInt(ip);
const b = ipv4ToInt(base);
if (n == null || b == null) return false;
const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;
return (n & mask) === (b & mask);
}
function expandIpv6(ip) {
let input = ip.toLowerCase();
const zone = input.indexOf('%');
if (zone !== -1) input = input.slice(0, zone);
if (input === '::') return Array(8).fill(0);
const [leftRaw, rightRaw] = input.split('::');
const left = leftRaw ? leftRaw.split(':').filter(Boolean) : [];
const right = rightRaw ? rightRaw.split(':').filter(Boolean) : [];
const parsePart = (part) => {
if (part.includes('.')) {
const n = ipv4ToInt(part);
if (n == null) return [];
return [(n >>> 16) & 0xffff, n & 0xffff];
}
return [parseInt(part || '0', 16)];
};
const leftNums = left.flatMap(parsePart);
const rightNums = right.flatMap(parsePart);
const missing = 8 - leftNums.length - rightNums.length;
if (missing < 0) return null;
return [...leftNums, ...Array(missing).fill(0), ...rightNums].map(n => Number.isFinite(n) ? n : 0);
}
function ipv6StartsWith(ip, prefix, bits) {
const a = expandIpv6(ip);
const p = expandIpv6(prefix);
if (!a || !p) return false;
let remaining = bits;
for (let i = 0; i < 8 && remaining > 0; i++) {
const take = Math.min(16, remaining);
const mask = (0xffff << (16 - take)) & 0xffff;
if ((a[i] & mask) !== (p[i] & mask)) return false;
remaining -= take;
}
return true;
}
function mappedIpv4(ip) {
const m = ip.toLowerCase().match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
if (m) return m[1];
const parts = expandIpv6(ip);
if (!parts) return null;
if (parts.slice(0, 5).every(n => n === 0) && parts[5] === 0xffff) {
return `${parts[6] >>> 8}.${parts[6] & 255}.${parts[7] >>> 8}.${parts[7] & 255}`;
}
return null;
}
export function isPrivateIp(address) {
if (!address) return false;
const ip = String(address).replace(/^\[|\]$/g, '').toLowerCase();
const mapped = mappedIpv4(ip);
if (mapped) return isPrivateIp(mapped);
const family = net.isIP(ip);
if (family === 4) {
return ipv4InCidr(ip, '0.0.0.0', 8)
|| ipv4InCidr(ip, '10.0.0.0', 8)
|| ipv4InCidr(ip, '100.64.0.0', 10)
|| ipv4InCidr(ip, '127.0.0.0', 8)
|| ipv4InCidr(ip, '169.254.0.0', 16)
|| ipv4InCidr(ip, '172.16.0.0', 12)
|| ipv4InCidr(ip, '192.168.0.0', 16);
}
if (family === 6) {
return ip === '::' || ip === '::1'
|| ipv6StartsWith(ip, 'fc00::', 7)
|| ipv6StartsWith(ip, 'fe80::', 10);
}
return false;
}
export async function resolvePublicAddresses(hostname, lookupFn = dnsLookup) {
const host = String(hostname || '').replace(/^\[|\]$/g, '');
if (!host || host.toLowerCase() === 'localhost') throw new Error('ERR_PROXY_PRIVATE_HOST');
if (net.isIP(host)) {
if (isPrivateIp(host)) throw new Error('ERR_PROXY_PRIVATE_IP');
return [{ address: host, family: net.isIP(host) }];
}
const result = await new Promise((resolve, reject) => {
lookupFn(host, { all: true }, (err, addrs) => err ? reject(err) : resolve(addrs));
});
const addrs = Array.isArray(result) ? result : [result];
for (const a of addrs) {
if (isPrivateIp(a.address)) throw new Error('ERR_PROXY_PRIVATE_IP');
}
return addrs;
}
export async function validateHostFormat(hostname, lookupFn = dnsLookup) {
const host = String(hostname || '').replace(/^\[|\]$/g, '');
if (!host) throw new Error('ERR_INVALID_HOST');
if (net.isIP(host)) {
return [{ address: host, family: net.isIP(host) }];
}
const result = await new Promise((resolve, reject) => {
lookupFn(host, { all: true }, (err, addrs) => err ? reject(err) : resolve(addrs));
});
return Array.isArray(result) ? result : [result];
}