mirror of
https://github.com/lionsoul2014/ip2region.git
synced 2025-12-08 19:25:22 +00:00
javascript is Ready for IPv6
This commit is contained in:
parent
ad78dbe547
commit
4507c2f706
0
binding/javascript/ReadMe.md
Normal file
0
binding/javascript/ReadMe.md
Normal file
7
binding/javascript/ip2region.js
Normal file
7
binding/javascript/ip2region.js
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ip2region.js
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "ip2region",
|
||||
"version": "3.0.0",
|
||||
"description": "javascript (ES6) binding for ip2region with both IPv4 and IPv6 supported ",
|
||||
"main": "index.js",
|
||||
"description": "javascript binding for ip2region with both IPv4 and IPv6 supported ",
|
||||
"type": "module",
|
||||
"main": "ip2region.js",
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
@ -13,10 +14,20 @@
|
||||
"keywords": [
|
||||
"ip2region",
|
||||
"ip-address",
|
||||
"ip-lookup",
|
||||
"ip-region",
|
||||
"ip-location",
|
||||
"ip-lookup",
|
||||
"ip-search",
|
||||
"ipv4-address",
|
||||
"ipv4-region",
|
||||
"ipv4-location",
|
||||
"ipv4-lookup",
|
||||
"ipv6-lookup"
|
||||
"ipv4-search",
|
||||
"ipv6-address",
|
||||
"ipv6-region",
|
||||
"ipv6-location",
|
||||
"ipv6-lookup",
|
||||
"ipv6-search"
|
||||
],
|
||||
"author": "lionsoul2014",
|
||||
"license": "ISC",
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
const fs = require('fs');
|
||||
const {
|
||||
parseIP,
|
||||
HeaderInfoLength, VectorIndexCols, VectorIndexSize
|
||||
HeaderInfoLength, VectorIndexCols, VectorIndexSize,
|
||||
ipToString
|
||||
} = require('./util');
|
||||
|
||||
class Searcher {
|
||||
@ -30,17 +31,13 @@ class Searcher {
|
||||
return this.ioCount;
|
||||
}
|
||||
|
||||
async search(ip) {
|
||||
let ipBytes;
|
||||
if (Buffer.isBuffer(ip)) {
|
||||
ipBytes = ip;
|
||||
} else {
|
||||
ipBytes = parseIP(ip);
|
||||
}
|
||||
search(ip) {
|
||||
// check and parse the string ip
|
||||
const ipBytes = Buffer.isBuffer(ip) ? ip : parseIP(ip);
|
||||
|
||||
// ip version check
|
||||
if (ipBytes.length != this.version.bytes) {
|
||||
throw new Error(`invalid ip address (${this.version.name} expected)`);
|
||||
throw new Error(`invalid ip address '${ipToString(ipBytes)}' (${this.version.name} expected)`);
|
||||
}
|
||||
|
||||
// reset the global counter
|
||||
@ -48,14 +45,14 @@ class Searcher {
|
||||
|
||||
// located the segment index block based on the vector index
|
||||
let sPtr = 0, ePtr = 0;
|
||||
let il0 = ipBytes[0] & 0xFF, il1 = ipBytes[1] & 0xFF;
|
||||
let il0 = ipBytes[0], il1 = ipBytes[1];
|
||||
let idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
|
||||
if (this.vectorIndex != null) {
|
||||
sPtr = this.vectorIndex.readUint32LE(idx);
|
||||
ePtr = this.vectorIndex.readUint32LE(idx + 4);
|
||||
} else if (this.cBuffer != null) {
|
||||
sPtr = this.vectorIndex.readUint32LE(HeaderInfoLength + idx);
|
||||
ePtr = this.vectorIndex.readUint32LE(HeaderInfoLength + idx + 4);
|
||||
sPtr = this.cBuffer.readUint32LE(HeaderInfoLength + idx);
|
||||
ePtr = this.cBuffer.readUint32LE(HeaderInfoLength + idx + 4);
|
||||
} else {
|
||||
const buff = Buffer.alloc(VectorIndexSize);
|
||||
this.read(HeaderInfoLength + idx, buff);
|
||||
@ -67,7 +64,7 @@ class Searcher {
|
||||
// binary search the segment index block to get the region info
|
||||
const bytes = ipBytes.length, dBytes = ipBytes.length << 1;
|
||||
const indexSize = this.version.indexSize;
|
||||
let buff = Buffer.alloc(indexSize);
|
||||
const buff = Buffer.alloc(indexSize);
|
||||
let dLen = -1, dPtr = -1, l = 0, h = (ePtr - sPtr) / indexSize;
|
||||
while (l <= h) {
|
||||
const m = (l + h) >> 1;
|
||||
@ -77,7 +74,7 @@ class Searcher {
|
||||
this.read(p, buff);
|
||||
if (this.version.ipSubCompare(ipBytes, buff, 0) < 0) {
|
||||
h = m - 1;
|
||||
} else if (this.version.ipSubCompare(ip, buff, bytes) > 0) {
|
||||
} else if (this.version.ipSubCompare(ipBytes, buff, bytes) > 0) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
dLen = buff.readUint16LE(dBytes);
|
||||
@ -87,12 +84,6 @@ class Searcher {
|
||||
}
|
||||
|
||||
// console.log(`dLen: ${dLen}, dPtr: ${dPtr}`);
|
||||
// empty match interception
|
||||
// @Note: could this even be a case ?
|
||||
if (dPtr < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const region = Buffer.alloc(dLen);
|
||||
this.read(dPtr, region);
|
||||
return region.toString('utf-8');
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// util test script
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const util = require('../util');
|
||||
const util = require('../util.js');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v4.xdb')
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// util test script
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const util = require('../util');
|
||||
const util = require('../util.js');
|
||||
|
||||
test('parse ip address', () => {
|
||||
let ip_list = [
|
||||
|
||||
56
binding/javascript/tests/search.test.js
Normal file
56
binding/javascript/tests/search.test.js
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// searcher search tester
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const {IPv4, IPv6, parseIP, ipToString} = require('../util');
|
||||
const {newWithFileOnly} = require('../searcher');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = {
|
||||
v4: path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v4.xdb'),
|
||||
v6: path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v6.xdb')
|
||||
}
|
||||
|
||||
test('ipv4 search test', () => {
|
||||
try {
|
||||
let searcher = newWithFileOnly(IPv4, dbPath.v4);
|
||||
let ip_list = [
|
||||
'1.0.0.0',
|
||||
parseIP('113.118.112.93'),
|
||||
'240e:3b7::'
|
||||
];
|
||||
|
||||
for (var i = 0; i < ip_list.length; i++) {
|
||||
let ip = ip_list[i];
|
||||
let region = searcher.search(ip);
|
||||
let ipStr = Buffer.isBuffer(ip) ? ipToString(ip) : ip;
|
||||
console.log(`search(${ipStr}): {region: ${region}, ioCount: ${searcher.getIOCount()}}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`${e.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('ipv6 search test', async () => {
|
||||
try {
|
||||
let searcher = newWithFileOnly(IPv6, dbPath.v6);
|
||||
let ip_list = [
|
||||
'2a02:26f7:c409:4001::',
|
||||
parseIP('2a11:8080:200::a:a05c'),
|
||||
'240e:3b7::',
|
||||
'120.229.45.92'
|
||||
];
|
||||
|
||||
for (var i = 0; i < ip_list.length; i++) {
|
||||
let ip = ip_list[i];
|
||||
let region = searcher.search(ip);
|
||||
let ipStr = Buffer.isBuffer(ip) ? ipToString(ip) : ip;
|
||||
console.log(`search(${ipStr}): {region: ${region}, ioCount: ${searcher.getIOCount()}}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`${e.message}`);
|
||||
}
|
||||
});
|
||||
@ -2,11 +2,11 @@
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// searcher tester
|
||||
// searcher new tester
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
|
||||
const {IPv4, parseIP, ipToString} = require('../util');
|
||||
const {newWithFileOnly} = require('../searcher');
|
||||
const {IPv4, IPv6, loadVectorIndexFromFile, XdbIPv4Id, loadContentFromFile} = require('../util');
|
||||
const {newWithFileOnly, newWithVectorIndex, newWithBuffer} = require('../searcher');
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = {
|
||||
@ -14,22 +14,53 @@ const dbPath = {
|
||||
v6: path.join(__dirname, '..', '..', '..', 'data', 'ip2region_v6.xdb')
|
||||
}
|
||||
|
||||
test('ipv4 searcher test', async () => {
|
||||
try {
|
||||
let searcher = newWithFileOnly(IPv4, dbPath.v4);
|
||||
let ip_list = [
|
||||
'1.0.0.0',
|
||||
parseIP('113.118.112.93'),
|
||||
'240e:3b7::'
|
||||
];
|
||||
function _get_creater_list(version) {
|
||||
let dbFile = version.id == XdbIPv4Id ? dbPath.v4 : dbPath.v6;
|
||||
return [function(){
|
||||
return ["newWithFileOnly", newWithFileOnly(version, dbFile)];
|
||||
}, function(){
|
||||
const vIndex = loadVectorIndexFromFile(dbFile);
|
||||
return ["newWithVectorIndex", newWithVectorIndex(version, dbFile, vIndex)];
|
||||
}, function(){
|
||||
const cBuffer = loadContentFromFile(dbFile);
|
||||
return ["newWithBuffer", newWithBuffer(version, cBuffer)];
|
||||
}];
|
||||
}
|
||||
|
||||
for (var i = 0; i < ip_list.length; i++) {
|
||||
let ip = ip_list[i];
|
||||
let region = await searcher.search(ip);
|
||||
let ipStr = Buffer.isBuffer(ip) ? ipToString(ip) : ip;
|
||||
console.log(`search(${ipStr}): {region: ${region}, ioCount: ${searcher.getIOCount()}}`);
|
||||
test('ipv4 searcher test', () => {
|
||||
const ip_Str = '120.229.45.92';
|
||||
const c_list = _get_creater_list(IPv4);
|
||||
try {
|
||||
let bRegion = null;
|
||||
for (var i = 0; i < c_list.length; i++) {
|
||||
const meta = c_list[i]();
|
||||
const region = meta[1].search(ip_Str);
|
||||
if (bRegion != null) {
|
||||
expect(region).toBe(region);
|
||||
}
|
||||
bRegion = region;
|
||||
console.log(`${meta[0]}.search(${ip_Str}): ${region}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`${e.message}`);
|
||||
console.error(`${e.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('ipv6 searcher test', async () => {
|
||||
const ip_Str = '240e:57f:32ff:ffff:ffff:ffff:ffff:ffff';
|
||||
const c_list = _get_creater_list(IPv6);
|
||||
try {
|
||||
let bRegion = null;
|
||||
for (var i = 0; i < c_list.length; i++) {
|
||||
const meta = c_list[i]();
|
||||
const region = meta[1].search(ip_Str);
|
||||
if (bRegion != null) {
|
||||
expect(region).toBe(region);
|
||||
}
|
||||
bRegion = region;
|
||||
console.log(`${meta[0]}.search(${ip_Str}): ${region}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`${e.message}`);
|
||||
}
|
||||
});
|
||||
@ -47,87 +47,6 @@ class Header {
|
||||
|
||||
// ---
|
||||
|
||||
class Version {
|
||||
constructor(id, name, bytes, indexSize, ipCompareFunc) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.bytes = bytes;
|
||||
this.indexSize = indexSize;
|
||||
this.ipCompareFunc = ipCompareFunc;
|
||||
}
|
||||
|
||||
ipCompare(ip1, ip2) {
|
||||
return this.ipCompareFunc(ip1, ip2, 0);
|
||||
}
|
||||
|
||||
ipSubCompare(ip1, ip2, offset) {
|
||||
return this.ipCompareFunc(ip1, ip2, offset);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `{"id": ${this.id}, "name": "${this.name}", "bytes":${this.bytes}, "index_size": ${this.indexSize}}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 14 = 4 + 4 + 2 + 4
|
||||
const IPv4 = new Version(XdbIPv4Id, "IPv4", 4, 14, function(ip1, buff, offset){
|
||||
// ip1: Big endian byte order parsed from input
|
||||
// ip2: Little endian byte order read from xdb index.
|
||||
// @Note: to compatible with the old Litten endian index encode implementation.
|
||||
let i, j = offset + ip1.length - 1;
|
||||
for (i = 0; i < ip1.length; i++, j--) {
|
||||
const i1 = ip1[i] & 0xFF;
|
||||
const i2 = buff[j] & 0xFF;
|
||||
if (i1 < i2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (i1 > i2) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 38 = 16 + 16 + 2 + 4
|
||||
const IPv6 = new Version(XdbIPv6Id, "IPv6", 6, 38, ipSubCompare);
|
||||
|
||||
function versionFromName(name) {
|
||||
let n = name.toUpperCase();
|
||||
if (n == "V4" || n == "IPV4") {
|
||||
return IPv4;
|
||||
} else if (n == "V6" || n == "IPV6") {
|
||||
return IPv6;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function versionFromHeader(h) {
|
||||
let v = h.version();
|
||||
|
||||
// old structure with ONLY IPv4 supporting
|
||||
if (v == XdbStructure20) {
|
||||
return IPv4;
|
||||
}
|
||||
|
||||
// structure 3.0 with IPv6 supporting
|
||||
if (v != XdbStructure30) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let ipVer = h.ipVersion();
|
||||
if (ipVer == XdbIPv4Id) {
|
||||
return IPv4;
|
||||
} else if (ipVer == XdbIPv6Id) {
|
||||
return IPv6;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// ---
|
||||
|
||||
// parse ipv4 address
|
||||
function _parse_ipv4_addr(v4String) {
|
||||
let ps = v4String.split('.', 4);
|
||||
@ -223,7 +142,7 @@ function parseIP(ipString) {
|
||||
} else if (cDot > -1) {
|
||||
return _parse_ipv6_addr(ipString);
|
||||
} else {
|
||||
return null;
|
||||
throw new Error(`invalid ip address '${ipString}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,6 +243,90 @@ function ipCompare(ip1, ip2) {
|
||||
return ipSubCompare(ip1, ip2, 0);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
class Version {
|
||||
constructor(id, name, bytes, indexSize, ipCompareFunc) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.bytes = bytes;
|
||||
this.indexSize = indexSize;
|
||||
this.ipCompareFunc = ipCompareFunc;
|
||||
}
|
||||
|
||||
ipCompare(ip1, ip2) {
|
||||
return this.ipCompareFunc(ip1, ip2, 0);
|
||||
}
|
||||
|
||||
ipSubCompare(ip1, ip2, offset) {
|
||||
return this.ipCompareFunc(ip1, ip2, offset);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `{"id": ${this.id}, "name": "${this.name}", "bytes":${this.bytes}, "index_size": ${this.indexSize}}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 14 = 4 + 4 + 2 + 4
|
||||
const IPv4 = new Version(XdbIPv4Id, "IPv4", 4, 14, function(ip1, buff, offset){
|
||||
// ip1: Big endian byte order parsed from input
|
||||
// ip2: Little endian byte order read from xdb index.
|
||||
// @Note: to compatible with the old Litten endian index encode implementation.
|
||||
let i, j = offset + ip1.length - 1;
|
||||
for (i = 0; i < ip1.length; i++, j--) {
|
||||
const i1 = ip1[i] & 0xFF;
|
||||
const i2 = buff[j] & 0xFF;
|
||||
if (i1 < i2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (i1 > i2) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 38 = 16 + 16 + 2 + 4
|
||||
const IPv6 = new Version(XdbIPv6Id, "IPv6", 16, 38, ipSubCompare);
|
||||
|
||||
function versionFromName(name) {
|
||||
let n = name.toUpperCase();
|
||||
if (n == "V4" || n == "IPV4") {
|
||||
return IPv4;
|
||||
} else if (n == "V6" || n == "IPV6") {
|
||||
return IPv6;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function versionFromHeader(h) {
|
||||
let v = h.version();
|
||||
|
||||
// old structure with ONLY IPv4 supporting
|
||||
if (v == XdbStructure20) {
|
||||
return IPv4;
|
||||
}
|
||||
|
||||
// structure 3.0 with IPv6 supporting
|
||||
if (v != XdbStructure30) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let ipVer = h.ipVersion();
|
||||
if (ipVer == XdbIPv4Id) {
|
||||
return IPv4;
|
||||
} else if (ipVer == XdbIPv6Id) {
|
||||
return IPv6;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
function loadHeader(fd) {
|
||||
const buffer = Buffer.alloc(HeaderInfoLength);
|
||||
const rBytes = fs.readSync(fd, buffer, 0, HeaderInfoLength, 0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user