@maxmind/geoip2-node
Version:
Node.js API for GeoIP2 webservice client and database reader
164 lines (163 loc) • 4.99 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.snakeToCamelCase = snakeToCamelCase;
exports.toCidr = toCidr;
exports.camelcaseKeys = camelcaseKeys;
function snakeToCamelCase(input) {
return input.replace(/_+(\w?)/g, (_, p, o) => o === 0 ? p : p.toUpperCase());
}
const isObject = (value) => typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
!(value instanceof RegExp) &&
!(value instanceof Error) &&
!(value instanceof Date);
function toCidr(ip, prefixLen) {
const isV6 = ip.includes(':');
const isV4Mapped = isV6 && ip.includes('.');
const cleanIp = isV4Mapped
? ip.replace(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/, (_, v4) => binaryToV6Part(ipToBinary(v4, false)))
: ip;
const binaryIp = ipToBinary(cleanIp, isV6);
const binaryMasked = maskPrefix(binaryIp, prefixLen);
const masked = isV6 ? binaryToV6(binaryMasked) : binaryToV4(binaryMasked);
return `${masked}/${prefixLen}`;
}
function ipToBinary(ip, isV6) {
if (isV6) {
const octets = new Uint8Array(16);
const fullSegments = expandIPv6(ip);
for (let i = 0; i < 8; i++) {
const s = parseInt(fullSegments[i], 16);
octets[2 * i] = s >> 8;
octets[2 * i + 1] = s & 0xff;
}
return octets;
}
else {
return new Uint8Array(ip.split('.').map((octet) => parseInt(octet)));
}
}
function expandIPv6(ip) {
const segments = ip.split(':');
if (segments.length < 8) {
const emptyIndex = segments.indexOf('');
const zerosToAdd = 8 - segments.length + 1;
return [
...segments.slice(0, emptyIndex),
...Array(zerosToAdd).fill('0'),
...segments.slice(emptyIndex + 1),
];
}
return segments;
}
function maskPrefix(octets, prefix) {
const unchanged = Math.floor(prefix / 8);
if (octets.length == unchanged) {
return octets;
}
const masked = new Uint8Array(octets.length);
for (let i = 0; i < unchanged; i++) {
masked[i] = octets[i];
}
masked[unchanged] = octets[unchanged] & (0xff << (8 - (prefix % 8)));
return masked;
}
function binaryToV4(octets) {
return octets.join('.');
}
function binaryToV6(octets) {
if (octets.length !== 16) {
throw new Error('Invalid input, IPv6 address requires exactly 16 octets.');
}
if (isV4Mapped(octets)) {
return '::ffff:' + binaryToV4(octets.slice(12));
}
const [zeroStart, zeroEnd] = findLongestZeroRun(octets);
if (zeroStart == zeroEnd) {
return binaryToV6Part(octets);
}
return (binaryToV6Part(octets.slice(0, zeroStart)) +
'::' +
binaryToV6Part(octets.slice(zeroEnd)));
}
function isV4Mapped(octets) {
return (octets[0] === 0 &&
octets[1] === 0 &&
octets[2] === 0 &&
octets[3] === 0 &&
octets[4] === 0 &&
octets[5] === 0 &&
octets[6] === 0 &&
octets[7] === 0 &&
octets[8] === 0 &&
octets[9] === 0 &&
octets[10] === 0xff &&
octets[11] === 0xff);
}
function binaryToV6Part(octets) {
if (octets.length == 0) {
return '';
}
const parts = new Array(octets.length / 2);
for (let i = 0; i < parts.length; i++) {
const first = octets[2 * i];
const second = octets[2 * i + 1];
if (first == 0) {
parts[i] = second.toString(16);
}
else {
parts[i] = first.toString(16) + second.toString(16).padStart(2, '0');
}
}
return parts.join(':');
}
function findLongestZeroRun(octets) {
let maxStart = 0;
let maxLength = 0;
let curStart = 0;
let curLength = 0;
for (let i = 0; i < octets.length / 2; i++) {
if (octets[2 * i] === 0 && octets[2 * i + 1] === 0) {
if (curLength === 0) {
curStart = 2 * i;
}
curLength++;
}
else {
if (curLength > maxLength) {
maxLength = curLength;
maxStart = curStart;
}
curLength = 0;
}
}
if (curLength > maxLength) {
maxLength = curLength;
maxStart = curStart;
}
return [maxStart, maxStart + 2 * maxLength];
}
const processArray = (arr) => arr.map((el) => Array.isArray(el)
? processArray(el)
: isObject(el)
? camelcaseKeys(el)
: el);
function camelcaseKeys(input) {
if (Array.isArray(input)) {
return processArray(input);
}
const output = {};
for (const [key, value] of Object.entries(input)) {
if (Array.isArray(value)) {
output[snakeToCamelCase(key)] = processArray(value);
}
else if (isObject(value)) {
output[snakeToCamelCase(key)] = camelcaseKeys(value);
}
else {
output[snakeToCamelCase(key)] = value;
}
}
return output;
}
;