UNPKG

@maxmind/geoip2-node

Version:

Node.js API for GeoIP2 webservice client and database reader

164 lines (163 loc) 4.99 kB
"use strict"; 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; }