// This is a minimal subset of node-ip for handling IPMatch // https://github.com/indutny/node-ip/blob/master/lib/ip.js // // ### License // // This software is licensed under the MIT License. // // Copyright Fedor Indutny, 2012. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. import { Buffer } from 'buffer'; const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/; const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; export const ip = { toBuffer: function (ip: string, buff?: Buffer, offset?: number): Buffer { offset = offset ? offset : 0; let result; if (this.isV4Format(ip)) { result = buff || new Buffer(offset + 4); ip.split(/\./g).map(function (byte) { offset = offset ? offset : 0; result[offset++] = parseInt(byte, 10) & 0xff; }); } else if (this.isV6Format(ip)) { const sections = ip.split(':', 8); let i; for (i = 0; i < sections.length; i++) { const isv4 = this.isV4Format(sections[i]); let v4Buffer; if (isv4) { v4Buffer = this.toBuffer(sections[i]); sections[i] = v4Buffer.slice(0, 2).toString('hex'); } if (v4Buffer && ++i < 8) { sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex')); } } if (sections[0] === '') { while (sections.length < 8) sections.unshift('0'); } else if (sections[sections.length - 1] === '') { while (sections.length < 8) sections.push('0'); } else if (sections.length < 8) { for (i = 0; i < sections.length && sections[i] !== ''; i++) {} const argv = [i, 1]; for (i = 9 - sections.length; i > 0; i--) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore argv.push('0'); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line prefer-spread sections.splice.apply(sections, argv); } result = buff || new Buffer(offset + 16); for (i = 0; i < sections.length; i++) { const word = parseInt(sections[i], 16); result[offset++] = (word >> 8) & 0xff; result[offset++] = word & 0xff; } } if (!result) { throw Error('Invalid ip address: ' + ip); } return result; }, toString: function (buff: Buffer, offset?: number, length?: number): string { offset = offset ? offset : 0; length = length || buff.length - offset; let result = []; if (length === 4) { // IPv4 for (let i = 0; i < length; i++) { result.push(buff[offset + i]); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore result = result.join('.'); } else if (length === 16) { // IPv6 for (let i = 0; i < length; i += 2) { result.push(buff.readUInt16BE(offset + i).toString(16)); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore result = result.join(':'); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore result = (result as string).replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore result = (result as string).replace(/:{3,4}/, '::'); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return result as string; }, isV4Format: function (ip: string): boolean { return ipv4Regex.test(ip); }, isV6Format: function (ip: string): boolean { return ipv6Regex.test(ip); }, fromPrefixLen: function (prefixlen: number, family?: string): string { if (prefixlen > 32) { family = 'ipv6'; } else { family = _normalizeFamily(typeof family === 'string' ? family : ''); } let len = 4; if (family === 'ipv6') { len = 16; } const buff = new Buffer(len); for (let i = 0, n = buff.length; i < n; ++i) { let bits = 8; if (prefixlen < 8) { bits = prefixlen; } prefixlen -= bits; buff[i] = ~(0xff >> bits) & 0xff; } return ip.toString(buff); }, mask: function (addr: string, mask: string): string { const addrBuffer = ip.toBuffer(addr); const maskBuffer = ip.toBuffer(mask); const result = new Buffer(Math.max(addrBuffer.length, maskBuffer.length)); let i; // Same protocol - do bitwise and if (addrBuffer.length === maskBuffer.length) { for (i = 0; i < addrBuffer.length; i++) { result[i] = addrBuffer[i] & maskBuffer[i]; } } else if (maskBuffer.length === 4) { // IPv6 address and IPv4 mask // (Mask low bits) for (i = 0; i < maskBuffer.length; i++) { result[i] = addrBuffer[addrBuffer.length - 4 + i] & maskBuffer[i]; } } else { // IPv6 mask and IPv4 addr for (let i = 0; i < result.length - 6; i++) { result[i] = 0; } // ::ffff:ipv4 result[10] = 0xff; result[11] = 0xff; for (i = 0; i < addrBuffer.length; i++) { result[i + 12] = addrBuffer[i] & maskBuffer[i + 12]; } i = i + 12; } for (; i < result.length; i++) result[i] = 0; return ip.toString(result); }, subnet: function (addr: string, mask: string): any { const networkAddress = ip.toLong(ip.mask(addr, mask)); // Calculate the mask's length. const maskBuffer = ip.toBuffer(mask); let maskLength = 0; for (let i = 0; i < maskBuffer.length; i++) { if (maskBuffer[i] === 0xff) { maskLength += 8; } else { let octet = maskBuffer[i] & 0xff; while (octet) { octet = (octet << 1) & 0xff; maskLength++; } } } return { contains: function (other: string) { return networkAddress === ip.toLong(ip.mask(other, mask)); }, }; }, cidrSubnet: function (cidrString: string): any { const cidrParts = cidrString.split('/'); const addr = cidrParts[0]; if (cidrParts.length !== 2) throw new Error('invalid CIDR subnet: ' + addr); const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); return ip.subnet(addr, mask); }, isEqual: function (a: string, b: string): boolean { let aBuffer = ip.toBuffer(a); let bBuffer = ip.toBuffer(b); // Same protocol if (aBuffer.length === bBuffer.length) { for (let i = 0; i < aBuffer.length; i++) { if (aBuffer[i] !== bBuffer[i]) return false; } return true; } // Swap if (bBuffer.length === 4) { const t = bBuffer; bBuffer = aBuffer; aBuffer = t; } // a - IPv4, b - IPv6 for (let i = 0; i < 10; i++) { if (bBuffer[i] !== 0) return false; } const word = bBuffer.readUInt16BE(10); if (word !== 0 && word !== 0xffff) return false; for (let i = 0; i < 4; i++) { if (aBuffer[i] !== bBuffer[i + 12]) return false; } return true; }, toLong: function (ip: string): number { let ipl = 0; ip.split('.').forEach(function (octet) { ipl <<= 8; ipl += parseInt(octet); }); return ipl >>> 0; }, }; function _normalizeFamily(family: string): string { return family ? family.toLowerCase() : 'ipv4'; }