geoip-lite2 2.2.6 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/data/city.checksum +1 -0
- package/data/country.checksum +1 -0
- package/{geoip-data → data}/geoip-city-names.dat +0 -0
- package/{geoip-data → data}/geoip-city.dat +0 -0
- package/{geoip-data → data}/geoip-city6.dat +0 -0
- package/{geoip-data → data}/geoip-country.dat +0 -0
- package/{geoip-data → data}/geoip-country6.dat +0 -0
- package/dist/fsWatcher.js +1 -0
- package/dist/main.js +1 -0
- package/index.d.ts +19 -33
- package/package.json +19 -10
- package/{utils → tools}/updatedb.js +7 -8
- package/geoip-data/city.checksum +0 -1
- package/geoip-data/country.checksum +0 -1
- package/lib/fsWatcher.js +0 -69
- package/lib/main.js +0 -501
- package/lib/utils.js +0 -86
- package/lib-minified/fsWatcher.js +0 -1
- package/lib-minified/main.js +0 -1
- package/minify.js +0 -55
- package/test/example.js +0 -7
- package/test/geo-lookup.js +0 -55
- package/test/index.test.js +0 -227
- package/test/memory_usage.js +0 -4
- package/test/other.js +0 -6
- package/test/setInterval.js +0 -16
- package/utils-minified/updatedb.js +0 -1
- /package/{lib-minified → dist}/utils.js +0 -0
package/README.md
CHANGED
|
@@ -181,7 +181,7 @@ npm run updatedb-force license_key=YOUR_LICENSE_KEY
|
|
|
181
181
|
|
|
182
182
|
You can also run it by doing:
|
|
183
183
|
```shell
|
|
184
|
-
node ./node_modules/geoip-lite2/
|
|
184
|
+
node ./node_modules/geoip-lite2/tools/updatedb.js license_key=YOUR_LICENSE_KEY
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
### Ways to reload data in your app when update finished
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9477be6926b39db95dcf6664e36bddd5900425ddb9952840ec5491d5c6ad6bdd GeoLite2-City-CSV_20250905.zip
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
952a602f47c26afc46782e468b2464fcc7a6a44418bff3819a5d1e63481de5ec GeoLite2-Country-CSV_20250905.zip
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const{access:e,constants:n,watch:t}=require('node:fs'),{join:o}=require('node:path'),l={},u=e=>l[e].close();module.exports={makeFsWatchFilter:(c,r,s,i,a)=>{let f=null;function h(){f=null,a()}'function'==typeof i&&(a=i,i=s,s=null),l[c]&&u(c),l[c]=t(r,function(t,l){if(!l)return;const u=o(r,l);s&&s!==l||e(u,n.F_OK,e=>{if(e)return console.error(e);null!==f&&(clearTimeout(f),f=null),f=setTimeout(h,i)})})},stopWatching:u};
|
package/dist/main.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const{open:e,fstat:r,read:t,close:n,openSync:i,fstatSync:a,readSync:o,closeSync:l}=require('node:fs'),{join:u,resolve:c}=require('node:path'),{isIP:f}=require('node:net'),s=require('async'),{aton4:d,aton6:y,cmp6:B,ntoa4:S,ntoa6:p,cmp:g}=require('./utils.js'),I=require('./fsWatcher.js'),{version:E}=require('../package.json'),m='dataWatcher',z=c(__dirname,global.geoDataDir||process.env.GEODATADIR||'../data/'),N={city:u(z,'geoip-city.dat'),city6:u(z,'geoip-city6.dat'),cityNames:u(z,'geoip-city-names.dat'),country:u(z,'geoip-country.dat'),country6:u(z,'geoip-country6.dat')},P=[[d('10.0.0.0'),d('10.255.255.255')],[d('172.16.0.0'),d('172.31.255.255')],[d('192.168.0.0'),d('192.168.255.255')]],h={firstIP:null,lastIP:null,lastLine:0,locationBuffer:null,locationRecordSize:88,mainBuffer:null,recordSize:24},F={firstIP:null,lastIP:null,lastLine:0,mainBuffer:null,recordSize:48};let O=JSON.parse(JSON.stringify(h)),L=JSON.parse(JSON.stringify(F));const U=e=>{let r,t,n=0,i=O.lastLine,a=O.lastIP,o=O.firstIP;const l=O.mainBuffer,u=O.locationBuffer,c=P,f=O.recordSize,s=O.locationRecordSize,d={range:[null,null],country:'',region:'',eu:'',timezone:'',city:'',ll:[null,null],metro:null,area:null};if(e>O.lastIP||e<O.firstIP)return null;for(let r=0;r<c.length;r++)if(e>=c[r][0]&&e<=c[r][1])return null;for(;;){if(r=Math.round((i-n)/2)+n,a=l.readUInt32BE(r*f),o=l.readUInt32BE(r*f+4),a<=e&&o>=e)return d.range=[a,o],10===f?d.country=l.toString('utf8',r*f+8,r*f+10):(t=l.readUInt32BE(r*f+8),-1>>>0>t&&(d.country=u.toString('utf8',t*s,t*s+2).replace(/\u0000.*/,''),d.region=u.toString('utf8',t*s+2,t*s+5).replace(/\u0000.*/,''),d.metro=u.readInt32BE(t*s+5),d.ll[0]=l.readInt32BE(r*f+12)/1e4,d.ll[1]=l.readInt32BE(r*f+16)/1e4,d.area=l.readUInt32BE(r*f+20),d.eu=u.toString('utf8',t*s+9,t*s+10).replace(/\u0000.*/,''),d.timezone=u.toString('utf8',t*s+10,t*s+42).replace(/\u0000.*/,''),d.city=u.toString('utf8',t*s+42,t*s+s).replace(/\u0000.*/,''))),d;if(n===i)return null;n===i-1?r===n?n=i:i=n:a>e?i=r:o<e&&(n=r)}},D=['0:0:0:0:0:FFFF:','::FFFF:'];function J(u){let c,f;const d=JSON.parse(JSON.stringify(h));if('function'==typeof arguments[0])s.series([i=>{s.series([r=>{e(N.cityNames,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,d.locationBuffer=Buffer.alloc(f),e(r)})},e=>{t(c,d.locationBuffer,0,f,0,e)},e=>{n(c,e)},r=>{e(N.city,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,e(r)})}],t=>{if(t){if('ENOENT'!==t.code&&'EBADF'!==t.code)throw t;e(N.country,'r',(e,t)=>{e?i(e):(c=t,r(c,(e,r)=>{f=r.size,d.recordSize=10,i()}))})}else i()})},()=>{d.mainBuffer=Buffer.alloc(f),s.series([e=>{t(c,d.mainBuffer,0,f,0,e)},e=>{n(c,e)}],e=>{e||(d.lastLine=f/d.recordSize-1,d.lastIP=d.mainBuffer.readUInt32BE(d.lastLine*d.recordSize+4),d.firstIP=d.mainBuffer.readUInt32BE(0),O=d),u(e)})}]);else{try{if(c=i(N.cityNames,'r'),f=a(c).size,0===f)throw{code:'EMPTY_FILE'};O.locationBuffer=Buffer.alloc(f),o(c,O.locationBuffer,0,f,0),l(c),c=i(N.city,'r'),f=a(c).size}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;c=i(N.country,'r'),f=a(c).size,O.recordSize=10}O.mainBuffer=Buffer.alloc(f),o(c,O.mainBuffer,0,f,0),l(c),O.lastLine=f/O.recordSize-1,O.lastIP=O.mainBuffer.readUInt32BE(O.lastLine*O.recordSize+4),O.firstIP=O.mainBuffer.readUInt32BE(0)}}function T(u){let c,f;const d=JSON.parse(JSON.stringify(F));if('function'==typeof arguments[0])s.series([t=>{s.series([r=>{e(N.city6,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,e(r)})}],n=>{if(n){if('ENOENT'!==n.code&&'EBADF'!==n.code)throw n;e(N.country6,'r',(e,n)=>{e?t(e):(c=n,r(c,(e,r)=>{f=r.size,d.recordSize=34,t()}))})}else t()})},()=>{d.mainBuffer=Buffer.alloc(f),s.series([e=>{t(c,d.mainBuffer,0,f,0,e)},e=>{n(c,e)}],e=>{e||(d.lastLine=f/d.recordSize-1,L=d),u(e)})}]);else{try{if(c=i(N.city6,'r'),f=a(c).size,0===f)throw{code:'EMPTY_FILE'}}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;c=i(N.country6,'r'),f=a(c).size,L.recordSize=34}L.mainBuffer=Buffer.alloc(f),o(c,L.mainBuffer,0,f,0),l(c),L.lastLine=f/L.recordSize-1}}module.exports={cmp:g,lookup:e=>{if(!e)return null;if('number'==typeof e)return U(e);if(4===f(e))return U(d(e));if(6===f(e)){const r=(e=>{const r=e.toUpperCase();for(let e=0;e<D.length;e++){const t=D[e];if(0===r.indexOf(t))return r.substring(t.length)}return null})(e);return r?U(d(r)):(e=>{const r=L.mainBuffer,t=L.recordSize,n=O.locationBuffer,i=O.locationRecordSize,a={range:[null,null],country:'',region:'',city:'',ll:[0,0],metro:null,area:null,eu:'',timezone:''},o=(e,n)=>{const i=[];for(let a=0;a<2;a++)i.push(r.readUInt32BE(e*t+16*n+4*a));return i};L.lastIP=o(L.lastLine,1),L.firstIP=o(0,0);let l,u,c=0,f=L.lastLine,s=L.lastIP,d=L.firstIP;if(B(e,L.lastIP)>0||B(e,L.firstIP)<0)return null;for(;;){if(l=Math.round((f-c)/2)+c,s=o(l,0),d=o(l,1),B(s,e)<=0&&B(d,e)>=0)return 34===t?a.country=r.toString('utf8',l*t+32,l*t+34).replace(/\u0000.*/,''):(u=r.readUInt32BE(l*t+32),-1>>>0>u&&(a.country=n.toString('utf8',u*i,u*i+2).replace(/\u0000.*/,''),a.region=n.toString('utf8',u*i+2,u*i+5).replace(/\u0000.*/,''),a.metro=n.readInt32BE(u*i+5),a.ll[0]=r.readInt32BE(l*t+36)/1e4,a.ll[1]=r.readInt32BE(l*t+40)/1e4,a.area=r.readUInt32BE(l*t+44),a.eu=n.toString('utf8',u*i+9,u*i+10).replace(/\u0000.*/,''),a.timezone=n.toString('utf8',u*i+10,u*i+42).replace(/\u0000.*/,''),a.city=n.toString('utf8',u*i+42,u*i+i).replace(/\u0000.*/,''))),a;if(c===f)return null;c===f-1?l===c?c=f:f=c:B(s,e)>0?f=l:B(d,e)<0&&(c=l)}})(y(e))}return null},pretty:e=>'string'==typeof e?e:'number'==typeof e?S(e):Array.isArray(e)?p(e):e,startWatchingDataUpdate:e=>{I.makeFsWatchFilter(m,z,6e4,async()=>{await s.series([e=>{J(e)},e=>{T(e)}],e)})},stopWatchingDataUpdate:()=>I.stopWatching(m),clear:()=>{O=JSON.parse(JSON.stringify(h)),L=JSON.parse(JSON.stringify(F))},reloadDataSync:()=>{J(),T()},reloadData:async e=>{await s.series([e=>{J(e)},e=>{T(e)}],e)},version:E},J(),T();
|
package/index.d.ts
CHANGED
|
@@ -1,34 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
interface GeoIp2Location {
|
|
2
|
+
range: [number | null, number | null];
|
|
3
|
+
country: string;
|
|
4
|
+
region: string;
|
|
5
|
+
eu: '0' | '1';
|
|
6
|
+
timezone: string;
|
|
7
|
+
city: string;
|
|
8
|
+
ll: [number | null, number | null];
|
|
9
|
+
metro: number;
|
|
10
|
+
area: number;
|
|
11
|
+
}
|
|
3
12
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
metro: number;
|
|
13
|
-
area: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function lookup(ip: string | number): GeoIp2Location | null;
|
|
17
|
-
function pretty(n: string | number | any[]): string;
|
|
18
|
-
|
|
19
|
-
function reloadDataSync(): void;
|
|
20
|
-
function reloadData(callback: (err?: Error | null) => void): Promise<void>;
|
|
21
|
-
function startWatchingDataUpdate(callback: () => void): void;
|
|
22
|
-
function stopWatchingDataUpdate(): void;
|
|
23
|
-
function clear(): void;
|
|
24
|
-
|
|
25
|
-
export {
|
|
26
|
-
lookup,
|
|
27
|
-
pretty,
|
|
28
|
-
reloadDataSync,
|
|
29
|
-
reloadData,
|
|
30
|
-
startWatchingDataUpdate,
|
|
31
|
-
stopWatchingDataUpdate,
|
|
32
|
-
clear
|
|
33
|
-
};
|
|
34
|
-
}
|
|
13
|
+
export function lookup(ip: string | number): GeoIp2Location | null;
|
|
14
|
+
export function pretty(n: string | number | any[]): string;
|
|
15
|
+
export function reloadDataSync(): void;
|
|
16
|
+
export function reloadData(callback?: (err?: Error | null) => void): Promise<void>;
|
|
17
|
+
export function startWatchingDataUpdate(callback: () => void): void;
|
|
18
|
+
export function stopWatchingDataUpdate(): void;
|
|
19
|
+
export function clear(): void;
|
|
20
|
+
export const version: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geoip-lite2",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
4
4
|
"description": "A light weight native JavaScript implementation of GeoIP API from MaxMind. Improved and faster version by Sefinek.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"city",
|
|
@@ -52,32 +52,41 @@
|
|
|
52
52
|
"license": "Apache-2.0",
|
|
53
53
|
"author": "Philip Tellis <philip@bluesmoon.info> (https://bluesmoon.info)",
|
|
54
54
|
"type": "commonjs",
|
|
55
|
-
"main": "
|
|
55
|
+
"main": "dist/main.js",
|
|
56
56
|
"types": "index.d.ts",
|
|
57
57
|
"directories": {
|
|
58
58
|
"lib": "lib",
|
|
59
59
|
"test": "test"
|
|
60
60
|
},
|
|
61
|
+
"files": [
|
|
62
|
+
"data",
|
|
63
|
+
"dist",
|
|
64
|
+
"tools",
|
|
65
|
+
"AUTHORS",
|
|
66
|
+
"index.d.ts",
|
|
67
|
+
"LICENSE",
|
|
68
|
+
"README.md"
|
|
69
|
+
],
|
|
61
70
|
"scripts": {
|
|
62
71
|
"minify": "node minify.js",
|
|
63
72
|
"test": "jest test",
|
|
64
73
|
"up": "ncu -u && npm install && npm update && npm audit fix",
|
|
65
|
-
"updatedb": "node
|
|
66
|
-
"updatedb-debug": "node
|
|
67
|
-
"updatedb-force": "node
|
|
74
|
+
"updatedb": "node tools/updatedb.js",
|
|
75
|
+
"updatedb-debug": "node tools/updatedb.js debug",
|
|
76
|
+
"updatedb-force": "node tools/updatedb.js force"
|
|
68
77
|
},
|
|
69
78
|
"dependencies": {
|
|
70
79
|
"adm-zip": "^0.5.16",
|
|
71
80
|
"async": "^3.2.6",
|
|
72
|
-
"iconv-lite": "0.
|
|
81
|
+
"iconv-lite": "0.7.0",
|
|
73
82
|
"ip-address": "^10.0.1",
|
|
74
83
|
"rimraf": "^6.0.1"
|
|
75
84
|
},
|
|
76
85
|
"devDependencies": {
|
|
77
|
-
"@eslint/js": "^9.
|
|
78
|
-
"globals": "^16.
|
|
79
|
-
"jest": "^30.
|
|
80
|
-
"terser": "^5.
|
|
86
|
+
"@eslint/js": "^9.35.0",
|
|
87
|
+
"globals": "^16.3.0",
|
|
88
|
+
"jest": "^30.1.3",
|
|
89
|
+
"terser": "^5.44.0"
|
|
81
90
|
},
|
|
82
91
|
"engines": {
|
|
83
92
|
"node": ">=10.3.0"
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
const { name, version } = require('../package.json');
|
|
6
6
|
const UserAgent = `Mozilla/5.0 (compatible; ${name}/${version}; +https://github.com/sefinek/geoip-lite2)`;
|
|
7
7
|
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const http = require('http');
|
|
10
|
-
const https = require('https');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const zlib = require('zlib');
|
|
13
|
-
const readline = require('readline');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const http = require('node:http');
|
|
10
|
+
const https = require('node:https');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const zlib = require('node:zlib');
|
|
13
|
+
const readline = require('node:readline');
|
|
14
14
|
|
|
15
15
|
const async = require('async');
|
|
16
16
|
const { decodeStream } = require('iconv-lite');
|
|
@@ -30,7 +30,7 @@ if (typeof geoDataDir === 'undefined' && typeof process.env.GEODATADIR !== 'unde
|
|
|
30
30
|
geoDataDir = `geoDataDir=${process.env.GEODATADIR}`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
let dataPath = path.resolve(__dirname, '..', '
|
|
33
|
+
let dataPath = path.resolve(__dirname, '..', 'data');
|
|
34
34
|
if (typeof geoDataDir !== 'undefined') {
|
|
35
35
|
dataPath = path.resolve(process.cwd(), geoDataDir.split('=')[1]);
|
|
36
36
|
if (!fs.existsSync(dataPath)) {
|
|
@@ -161,7 +161,6 @@ function check(database, cb) {
|
|
|
161
161
|
if ([301, 302, 303, 307, 308].includes(status)) {
|
|
162
162
|
return https.get(getHTTPOptions(response.headers.location), onResponse);
|
|
163
163
|
} else if (status !== 200) {
|
|
164
|
-
console.error(response.data);
|
|
165
164
|
console.error('ERROR: HTTP Request Failed [%d %s]', status, http.STATUS_CODES[status]);
|
|
166
165
|
client.end();
|
|
167
166
|
process.exit(1);
|
package/geoip-data/city.checksum
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
7014a41dc60b27ef593d260eca0d4b396d38c924c0eee9859662f4ecae56b092 GeoLite2-City-CSV_20250620.zip
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
6c3d34a9eaa8d2bdafe85701f906c36c5f0eecde4f6c91a00499eaa612bfa26f GeoLite2-Country-CSV_20250620.zip
|
package/lib/fsWatcher.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const { access, constants, watch } = require('fs');
|
|
2
|
-
const { join } = require('path');
|
|
3
|
-
const FSWatcher = {};
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Takes an FSWatcher object and closes it.
|
|
7
|
-
* @param {string} name - The name of the watcher to close.
|
|
8
|
-
*/
|
|
9
|
-
const stopWatching = name => FSWatcher[name].close();
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Takes a directory/file and watch for change. Upon change, call the callback.
|
|
13
|
-
*
|
|
14
|
-
* @param {String} name - name of this watcher
|
|
15
|
-
* @param {String} directory - path to the directory to watch
|
|
16
|
-
* @param {String} [filename] - (optional) specific filename to watch for, watches for all files in the directory if unspecified
|
|
17
|
-
* @param {Number} cdDelay - delay to wait before triggering the callback
|
|
18
|
-
* @param {Function} callback - function() - called when changes are detected
|
|
19
|
-
*/
|
|
20
|
-
const makeFsWatchFilter = (name, directory, filename, cdDelay, callback) => {
|
|
21
|
-
let cdId = null;
|
|
22
|
-
|
|
23
|
-
// Delete the cdId and callback the outer function
|
|
24
|
-
function timeoutCallback() {
|
|
25
|
-
cdId = null;
|
|
26
|
-
callback();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// This function is called when there is a change in the data directory
|
|
30
|
-
// It sets a timer to wait for the change to be completed
|
|
31
|
-
function onWatchEvent(event, changedFile) {
|
|
32
|
-
// Check to make sure changedFile is not null
|
|
33
|
-
if (!changedFile) return;
|
|
34
|
-
|
|
35
|
-
const filePath = join(directory, changedFile);
|
|
36
|
-
if (!filename || filename === changedFile) {
|
|
37
|
-
access(filePath, constants.F_OK, err => {
|
|
38
|
-
if (err) return console.error(err);
|
|
39
|
-
|
|
40
|
-
// At this point, a new file system activity has been detected,
|
|
41
|
-
// We have to wait for file transfer to be finished before moving on.
|
|
42
|
-
|
|
43
|
-
// If a cdId already exists, we delete it
|
|
44
|
-
if (cdId !== null) {
|
|
45
|
-
clearTimeout(cdId);
|
|
46
|
-
cdId = null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Once the cdDelay has passed, the timeoutCallback function will be called
|
|
50
|
-
cdId = setTimeout(timeoutCallback, cdDelay);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Manage the case where filename is missing (because it's optional)
|
|
56
|
-
if (typeof cdDelay === 'function') {
|
|
57
|
-
callback = cdDelay;
|
|
58
|
-
cdDelay = filename;
|
|
59
|
-
filename = null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (FSWatcher[name]) {
|
|
63
|
-
stopWatching(name);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
FSWatcher[name] = watch(directory, onWatchEvent);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
module.exports = { makeFsWatchFilter, stopWatching };
|
package/lib/main.js
DELETED
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
const { open, fstat, read, close, openSync, fstatSync, readSync, closeSync } = require('fs');
|
|
2
|
-
const { join, resolve } = require('path');
|
|
3
|
-
const { isIP } = require('net');
|
|
4
|
-
const async = require('async');
|
|
5
|
-
const { aton4, aton6, cmp6, ntoa4, ntoa6, cmp } = require('./utils.js');
|
|
6
|
-
const fsWatcher = require('./fsWatcher.js');
|
|
7
|
-
const { version } = require('../package.json');
|
|
8
|
-
|
|
9
|
-
const watcherName = 'dataWatcher';
|
|
10
|
-
|
|
11
|
-
const geoDataDir = resolve(
|
|
12
|
-
__dirname,
|
|
13
|
-
global.geoDataDir || process.env.GEODATADIR || '../geoip-data/'
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const dataFiles = {
|
|
17
|
-
city: join(geoDataDir, 'geoip-city.dat'),
|
|
18
|
-
city6: join(geoDataDir, 'geoip-city6.dat'),
|
|
19
|
-
cityNames: join(geoDataDir, 'geoip-city-names.dat'),
|
|
20
|
-
country: join(geoDataDir, 'geoip-country.dat'),
|
|
21
|
-
country6: join(geoDataDir, 'geoip-country6.dat'),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const privateRange4 = [
|
|
25
|
-
[aton4('10.0.0.0'), aton4('10.255.255.255')],
|
|
26
|
-
[aton4('172.16.0.0'), aton4('172.31.255.255')],
|
|
27
|
-
[aton4('192.168.0.0'), aton4('192.168.255.255')],
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const conf4 = {
|
|
31
|
-
firstIP: null,
|
|
32
|
-
lastIP: null,
|
|
33
|
-
lastLine: 0,
|
|
34
|
-
locationBuffer: null,
|
|
35
|
-
locationRecordSize: 88,
|
|
36
|
-
mainBuffer: null,
|
|
37
|
-
recordSize: 24,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const conf6 = {
|
|
41
|
-
firstIP: null,
|
|
42
|
-
lastIP: null,
|
|
43
|
-
lastLine: 0,
|
|
44
|
-
mainBuffer: null,
|
|
45
|
-
recordSize: 48,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Copy original configs
|
|
49
|
-
let cache4 = JSON.parse(JSON.stringify(conf4));
|
|
50
|
-
let cache6 = JSON.parse(JSON.stringify(conf6));
|
|
51
|
-
|
|
52
|
-
const RECORD_SIZE = 10;
|
|
53
|
-
const RECORD_SIZE6 = 34;
|
|
54
|
-
|
|
55
|
-
const lookup4 = ip => {
|
|
56
|
-
let fline = 0;
|
|
57
|
-
let cline = cache4.lastLine;
|
|
58
|
-
let floor = cache4.lastIP;
|
|
59
|
-
let ceil = cache4.firstIP;
|
|
60
|
-
let line, locId;
|
|
61
|
-
|
|
62
|
-
const buffer = cache4.mainBuffer;
|
|
63
|
-
const locBuffer = cache4.locationBuffer;
|
|
64
|
-
const privateRange = privateRange4;
|
|
65
|
-
const recordSize = cache4.recordSize;
|
|
66
|
-
const locRecordSize = cache4.locationRecordSize;
|
|
67
|
-
|
|
68
|
-
const geoData = {
|
|
69
|
-
range: [null, null],
|
|
70
|
-
country: '',
|
|
71
|
-
region: '',
|
|
72
|
-
eu: '',
|
|
73
|
-
timezone: '',
|
|
74
|
-
city: '',
|
|
75
|
-
ll: [null, null],
|
|
76
|
-
metro: null,
|
|
77
|
-
area: null,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Outside IPv4 range
|
|
81
|
-
if (ip > cache4.lastIP || ip < cache4.firstIP) return null;
|
|
82
|
-
|
|
83
|
-
// Private IP
|
|
84
|
-
for (let i = 0; i < privateRange.length; i++) {
|
|
85
|
-
if (ip >= privateRange[i][0] && ip <= privateRange[i][1]) return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
while (true) {
|
|
89
|
-
line = Math.round((cline - fline) / 2) + fline;
|
|
90
|
-
floor = buffer.readUInt32BE(line * recordSize);
|
|
91
|
-
ceil = buffer.readUInt32BE((line * recordSize) + 4);
|
|
92
|
-
|
|
93
|
-
if (floor <= ip && ceil >= ip) {
|
|
94
|
-
geoData.range = [floor, ceil];
|
|
95
|
-
|
|
96
|
-
if (recordSize === RECORD_SIZE) {
|
|
97
|
-
geoData.country = buffer.toString('utf8', (line * recordSize) + 8, (line * recordSize) + 10);
|
|
98
|
-
} else {
|
|
99
|
-
locId = buffer.readUInt32BE((line * recordSize) + 8);
|
|
100
|
-
|
|
101
|
-
// -1>>>0 is a marker for "No Location Info"
|
|
102
|
-
if (-1 >>> 0 > locId) {
|
|
103
|
-
geoData.country = locBuffer.toString('utf8', (locId * locRecordSize), (locId * locRecordSize) + 2).replace(/\u0000.*/, '');
|
|
104
|
-
geoData.region = locBuffer.toString('utf8', (locId * locRecordSize) + 2, (locId * locRecordSize) + 5).replace(/\u0000.*/, '');
|
|
105
|
-
geoData.metro = locBuffer.readInt32BE((locId * locRecordSize) + 5);
|
|
106
|
-
geoData.ll[0] = buffer.readInt32BE((line * recordSize) + 12) / 10000; // latitude
|
|
107
|
-
geoData.ll[1] = buffer.readInt32BE((line * recordSize) + 16) / 10000; // longitude
|
|
108
|
-
geoData.area = buffer.readUInt32BE((line * recordSize) + 20);
|
|
109
|
-
geoData.eu = locBuffer.toString('utf8', (locId * locRecordSize) + 9, (locId * locRecordSize) + 10).replace(/\u0000.*/, '');
|
|
110
|
-
geoData.timezone = locBuffer.toString('utf8', (locId * locRecordSize) + 10, (locId * locRecordSize) + 42).replace(/\u0000.*/, '');
|
|
111
|
-
geoData.city = locBuffer.toString('utf8', (locId * locRecordSize) + 42, (locId * locRecordSize) + locRecordSize).replace(/\u0000.*/, '');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return geoData;
|
|
116
|
-
} else if (fline === cline) {
|
|
117
|
-
return null;
|
|
118
|
-
} else if (fline === (cline - 1)) {
|
|
119
|
-
if (line === fline) {
|
|
120
|
-
fline = cline;
|
|
121
|
-
} else {
|
|
122
|
-
cline = fline;
|
|
123
|
-
}
|
|
124
|
-
} else if (floor > ip) {
|
|
125
|
-
cline = line;
|
|
126
|
-
} else if (ceil < ip) {
|
|
127
|
-
fline = line;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const lookup6 = ip => {
|
|
133
|
-
const buffer = cache6.mainBuffer;
|
|
134
|
-
const recordSize = cache6.recordSize;
|
|
135
|
-
const locBuffer = cache4.locationBuffer;
|
|
136
|
-
const locRecordSize = cache4.locationRecordSize;
|
|
137
|
-
|
|
138
|
-
const geoData = { range: [null, null], country: '', region: '', city: '', ll: [0, 0], metro: null, area: null, eu: '', timezone: '' };
|
|
139
|
-
|
|
140
|
-
const readIp = (line, offset) => {
|
|
141
|
-
const ipArray = [];
|
|
142
|
-
for (let i = 0; i < 2; i++) {
|
|
143
|
-
ipArray.push(buffer.readUInt32BE((line * recordSize) + (offset * 16) + (i * 4)));
|
|
144
|
-
}
|
|
145
|
-
return ipArray;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
cache6.lastIP = readIp(cache6.lastLine, 1);
|
|
149
|
-
cache6.firstIP = readIp(0, 0);
|
|
150
|
-
|
|
151
|
-
let fline = 0;
|
|
152
|
-
let cline = cache6.lastLine;
|
|
153
|
-
let floor = cache6.lastIP;
|
|
154
|
-
let ceil = cache6.firstIP;
|
|
155
|
-
let line, locId;
|
|
156
|
-
|
|
157
|
-
if (cmp6(ip, cache6.lastIP) > 0 || cmp6(ip, cache6.firstIP) < 0) return null;
|
|
158
|
-
|
|
159
|
-
while (true) {
|
|
160
|
-
line = Math.round((cline - fline) / 2) + fline;
|
|
161
|
-
floor = readIp(line, 0);
|
|
162
|
-
ceil = readIp(line, 1);
|
|
163
|
-
|
|
164
|
-
if (cmp6(floor, ip) <= 0 && cmp6(ceil, ip) >= 0) {
|
|
165
|
-
if (recordSize === RECORD_SIZE6) {
|
|
166
|
-
geoData.country = buffer.toString('utf8', (line * recordSize) + 32, (line * recordSize) + 34).replace(/\u0000.*/, '');
|
|
167
|
-
} else {
|
|
168
|
-
locId = buffer.readUInt32BE((line * recordSize) + 32);
|
|
169
|
-
|
|
170
|
-
// -1>>>0 is a marker for "No Location Info"
|
|
171
|
-
if (-1 >>> 0 > locId) {
|
|
172
|
-
geoData.country = locBuffer.toString('utf8', (locId * locRecordSize), (locId * locRecordSize) + 2).replace(/\u0000.*/, '');
|
|
173
|
-
geoData.region = locBuffer.toString('utf8', (locId * locRecordSize) + 2, (locId * locRecordSize) + 5).replace(/\u0000.*/, '');
|
|
174
|
-
geoData.metro = locBuffer.readInt32BE((locId * locRecordSize) + 5);
|
|
175
|
-
geoData.ll[0] = buffer.readInt32BE((line * recordSize) + 36) / 10000; // latitude
|
|
176
|
-
geoData.ll[1] = buffer.readInt32BE((line * recordSize) + 40) / 10000; // longitude
|
|
177
|
-
geoData.area = buffer.readUInt32BE((line * recordSize) + 44); // area
|
|
178
|
-
geoData.eu = locBuffer.toString('utf8', (locId * locRecordSize) + 9, (locId * locRecordSize) + 10).replace(/\u0000.*/, '');
|
|
179
|
-
geoData.timezone = locBuffer.toString('utf8', (locId * locRecordSize) + 10, (locId * locRecordSize) + 42).replace(/\u0000.*/, '');
|
|
180
|
-
geoData.city = locBuffer.toString('utf8', (locId * locRecordSize) + 42, (locId * locRecordSize) + locRecordSize).replace(/\u0000.*/, '');
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// We do not currently have detailed region/city info for IPv6, but finally have coords
|
|
184
|
-
return geoData;
|
|
185
|
-
} else if (fline === cline) {
|
|
186
|
-
return null;
|
|
187
|
-
} else if (fline === (cline - 1)) {
|
|
188
|
-
if (line === fline) {
|
|
189
|
-
fline = cline;
|
|
190
|
-
} else {
|
|
191
|
-
cline = fline;
|
|
192
|
-
}
|
|
193
|
-
} else if (cmp6(floor, ip) > 0) {
|
|
194
|
-
cline = line;
|
|
195
|
-
} else if (cmp6(ceil, ip) < 0) {
|
|
196
|
-
fline = line;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const v6prefixes = ['0:0:0:0:0:FFFF:', '::FFFF:'];
|
|
202
|
-
const get4mapped = ip => {
|
|
203
|
-
const ipv6 = ip.toUpperCase();
|
|
204
|
-
for (let i = 0; i < v6prefixes.length; i++) {
|
|
205
|
-
const v6prefix = v6prefixes[i];
|
|
206
|
-
if (ipv6.indexOf(v6prefix) === 0) return ipv6.substring(v6prefix.length);
|
|
207
|
-
}
|
|
208
|
-
return null;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
function preload(callback) {
|
|
212
|
-
let datFile;
|
|
213
|
-
let datSize;
|
|
214
|
-
const asyncCache = JSON.parse(JSON.stringify(conf4));
|
|
215
|
-
|
|
216
|
-
// When the preload function receives a callback, do the task asynchronously
|
|
217
|
-
if (typeof arguments[0] === 'function') {
|
|
218
|
-
async.series([
|
|
219
|
-
cb => {
|
|
220
|
-
async.series([
|
|
221
|
-
cb2 => {
|
|
222
|
-
open(dataFiles.cityNames, 'r', (err, file) => {
|
|
223
|
-
datFile = file;
|
|
224
|
-
cb2(err);
|
|
225
|
-
});
|
|
226
|
-
},
|
|
227
|
-
cb2 => {
|
|
228
|
-
fstat(datFile, (err, stats) => {
|
|
229
|
-
datSize = stats.size;
|
|
230
|
-
asyncCache.locationBuffer = Buffer.alloc(datSize);
|
|
231
|
-
cb2(err);
|
|
232
|
-
});
|
|
233
|
-
},
|
|
234
|
-
cb2 => {
|
|
235
|
-
read(datFile, asyncCache.locationBuffer, 0, datSize, 0, cb2);
|
|
236
|
-
},
|
|
237
|
-
cb2 => {
|
|
238
|
-
close(datFile, cb2);
|
|
239
|
-
},
|
|
240
|
-
cb2 => {
|
|
241
|
-
open(dataFiles.city, 'r', (err, file) => {
|
|
242
|
-
datFile = file;
|
|
243
|
-
cb2(err);
|
|
244
|
-
});
|
|
245
|
-
},
|
|
246
|
-
cb2 => {
|
|
247
|
-
fstat(datFile, (err, stats) => {
|
|
248
|
-
datSize = stats.size;
|
|
249
|
-
cb2(err);
|
|
250
|
-
});
|
|
251
|
-
},
|
|
252
|
-
], err => {
|
|
253
|
-
if (err) {
|
|
254
|
-
if (err.code !== 'ENOENT' && err.code !== 'EBADF') {
|
|
255
|
-
throw err;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
open(dataFiles.country, 'r', (err, file) => {
|
|
259
|
-
if (err) {
|
|
260
|
-
cb(err);
|
|
261
|
-
} else {
|
|
262
|
-
datFile = file;
|
|
263
|
-
fstat(datFile, (err, stats) => {
|
|
264
|
-
datSize = stats.size;
|
|
265
|
-
asyncCache.recordSize = RECORD_SIZE;
|
|
266
|
-
|
|
267
|
-
cb();
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
} else {
|
|
273
|
-
cb();
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}, () => {
|
|
277
|
-
asyncCache.mainBuffer = Buffer.alloc(datSize);
|
|
278
|
-
|
|
279
|
-
async.series([
|
|
280
|
-
cb2 => {
|
|
281
|
-
read(datFile, asyncCache.mainBuffer, 0, datSize, 0, cb2);
|
|
282
|
-
},
|
|
283
|
-
cb2 => {
|
|
284
|
-
close(datFile, cb2);
|
|
285
|
-
},
|
|
286
|
-
], err => {
|
|
287
|
-
if (!err) {
|
|
288
|
-
asyncCache.lastLine = (datSize / asyncCache.recordSize) - 1;
|
|
289
|
-
asyncCache.lastIP = asyncCache.mainBuffer.readUInt32BE((asyncCache.lastLine * asyncCache.recordSize) + 4);
|
|
290
|
-
asyncCache.firstIP = asyncCache.mainBuffer.readUInt32BE(0);
|
|
291
|
-
cache4 = asyncCache;
|
|
292
|
-
}
|
|
293
|
-
callback(err);
|
|
294
|
-
});
|
|
295
|
-
},
|
|
296
|
-
]);
|
|
297
|
-
} else {
|
|
298
|
-
try {
|
|
299
|
-
datFile = openSync(dataFiles.cityNames, 'r');
|
|
300
|
-
datSize = fstatSync(datFile).size;
|
|
301
|
-
if (datSize === 0) throw { code: 'EMPTY_FILE' };
|
|
302
|
-
|
|
303
|
-
cache4.locationBuffer = Buffer.alloc(datSize);
|
|
304
|
-
readSync(datFile, cache4.locationBuffer, 0, datSize, 0);
|
|
305
|
-
closeSync(datFile);
|
|
306
|
-
|
|
307
|
-
datFile = openSync(dataFiles.city, 'r');
|
|
308
|
-
datSize = fstatSync(datFile).size;
|
|
309
|
-
} catch (err) {
|
|
310
|
-
if (err.code !== 'ENOENT' && err.code !== 'EBADF' && err.code !== 'EMPTY_FILE') {
|
|
311
|
-
throw err;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
datFile = openSync(dataFiles.country, 'r');
|
|
315
|
-
datSize = fstatSync(datFile).size;
|
|
316
|
-
cache4.recordSize = RECORD_SIZE;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
cache4.mainBuffer = Buffer.alloc(datSize);
|
|
320
|
-
readSync(datFile, cache4.mainBuffer, 0, datSize, 0);
|
|
321
|
-
closeSync(datFile);
|
|
322
|
-
|
|
323
|
-
cache4.lastLine = (datSize / cache4.recordSize) - 1;
|
|
324
|
-
cache4.lastIP = cache4.mainBuffer.readUInt32BE((cache4.lastLine * cache4.recordSize) + 4);
|
|
325
|
-
cache4.firstIP = cache4.mainBuffer.readUInt32BE(0);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function preload6(callback) {
|
|
330
|
-
let datFile;
|
|
331
|
-
let datSize;
|
|
332
|
-
const asyncCache6 = JSON.parse(JSON.stringify(conf6));
|
|
333
|
-
|
|
334
|
-
// When the preload function receives a callback, do the task asynchronously
|
|
335
|
-
if (typeof arguments[0] === 'function') {
|
|
336
|
-
async.series([
|
|
337
|
-
cb => {
|
|
338
|
-
async.series([
|
|
339
|
-
cb2 => {
|
|
340
|
-
open(dataFiles.city6, 'r', (err, file) => {
|
|
341
|
-
datFile = file;
|
|
342
|
-
cb2(err);
|
|
343
|
-
});
|
|
344
|
-
},
|
|
345
|
-
cb2 => {
|
|
346
|
-
fstat(datFile, (err, stats) => {
|
|
347
|
-
datSize = stats.size;
|
|
348
|
-
cb2(err);
|
|
349
|
-
});
|
|
350
|
-
},
|
|
351
|
-
], err => {
|
|
352
|
-
if (err) {
|
|
353
|
-
if (err.code !== 'ENOENT' && err.code !== 'EBADF') {
|
|
354
|
-
throw err;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
open(dataFiles.country6, 'r', (err, file) => {
|
|
358
|
-
if (err) {
|
|
359
|
-
cb(err);
|
|
360
|
-
} else {
|
|
361
|
-
datFile = file;
|
|
362
|
-
fstat(datFile, (err, stats) => {
|
|
363
|
-
datSize = stats.size;
|
|
364
|
-
asyncCache6.recordSize = RECORD_SIZE6;
|
|
365
|
-
|
|
366
|
-
cb();
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
} else {
|
|
371
|
-
cb();
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
}, () => {
|
|
375
|
-
asyncCache6.mainBuffer = Buffer.alloc(datSize);
|
|
376
|
-
|
|
377
|
-
async.series([
|
|
378
|
-
cb2 => {
|
|
379
|
-
read(datFile, asyncCache6.mainBuffer, 0, datSize, 0, cb2);
|
|
380
|
-
},
|
|
381
|
-
cb2 => {
|
|
382
|
-
close(datFile, cb2);
|
|
383
|
-
},
|
|
384
|
-
], err => {
|
|
385
|
-
if (!err) {
|
|
386
|
-
asyncCache6.lastLine = (datSize / asyncCache6.recordSize) - 1;
|
|
387
|
-
cache6 = asyncCache6;
|
|
388
|
-
}
|
|
389
|
-
callback(err);
|
|
390
|
-
});
|
|
391
|
-
},
|
|
392
|
-
]);
|
|
393
|
-
} else {
|
|
394
|
-
try {
|
|
395
|
-
datFile = openSync(dataFiles.city6, 'r');
|
|
396
|
-
datSize = fstatSync(datFile).size;
|
|
397
|
-
|
|
398
|
-
if (datSize === 0) throw { code: 'EMPTY_FILE' };
|
|
399
|
-
} catch (err) {
|
|
400
|
-
if (err.code !== 'ENOENT' && err.code !== 'EBADF' && err.code !== 'EMPTY_FILE') {
|
|
401
|
-
throw err;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
datFile = openSync(dataFiles.country6, 'r');
|
|
405
|
-
datSize = fstatSync(datFile).size;
|
|
406
|
-
cache6.recordSize = RECORD_SIZE6;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
cache6.mainBuffer = Buffer.alloc(datSize);
|
|
410
|
-
readSync(datFile, cache6.mainBuffer, 0, datSize, 0);
|
|
411
|
-
closeSync(datFile);
|
|
412
|
-
|
|
413
|
-
cache6.lastLine = (datSize / cache6.recordSize) - 1;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
module.exports = {
|
|
418
|
-
cmp,
|
|
419
|
-
|
|
420
|
-
lookup: ip => {
|
|
421
|
-
if (!ip) {
|
|
422
|
-
return null;
|
|
423
|
-
} else if (typeof ip === 'number') {
|
|
424
|
-
return lookup4(ip);
|
|
425
|
-
} else if (isIP(ip) === 4) {
|
|
426
|
-
return lookup4(aton4(ip));
|
|
427
|
-
} else if (isIP(ip) === 6) {
|
|
428
|
-
const ipv4 = get4mapped(ip);
|
|
429
|
-
if (ipv4) {
|
|
430
|
-
return lookup4(aton4(ipv4));
|
|
431
|
-
} else {
|
|
432
|
-
return lookup6(aton6(ip));
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return null;
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
pretty: n => {
|
|
440
|
-
if (typeof n === 'string') {
|
|
441
|
-
return n;
|
|
442
|
-
} else if (typeof n === 'number') {
|
|
443
|
-
return ntoa4(n);
|
|
444
|
-
} else if (Array.isArray(n)) {
|
|
445
|
-
return ntoa6(n);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return n;
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
// Start watching for data updates. The watcher waits one minute for file transfer to
|
|
452
|
-
// complete before triggering the callback.
|
|
453
|
-
startWatchingDataUpdate: callback => {
|
|
454
|
-
fsWatcher.makeFsWatchFilter(watcherName, geoDataDir, 60 * 1000, async () => {
|
|
455
|
-
// Reload data
|
|
456
|
-
await async.series([
|
|
457
|
-
cb => {
|
|
458
|
-
preload(cb);
|
|
459
|
-
}, cb => {
|
|
460
|
-
preload6(cb);
|
|
461
|
-
},
|
|
462
|
-
], callback);
|
|
463
|
-
});
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
// Stop watching for data updates.
|
|
467
|
-
stopWatchingDataUpdate: () => fsWatcher.stopWatching(watcherName),
|
|
468
|
-
|
|
469
|
-
// Clear data
|
|
470
|
-
clear: () => {
|
|
471
|
-
cache4 = JSON.parse(JSON.stringify(conf4));
|
|
472
|
-
cache6 = JSON.parse(JSON.stringify(conf6));
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
// Reload data synchronously
|
|
476
|
-
reloadDataSync: () => {
|
|
477
|
-
preload();
|
|
478
|
-
preload6();
|
|
479
|
-
},
|
|
480
|
-
|
|
481
|
-
// Reload data asynchronously
|
|
482
|
-
reloadData: async callback => {
|
|
483
|
-
// Reload data
|
|
484
|
-
await async.series([
|
|
485
|
-
cb => {
|
|
486
|
-
preload(cb);
|
|
487
|
-
},
|
|
488
|
-
cb => {
|
|
489
|
-
preload6(cb);
|
|
490
|
-
},
|
|
491
|
-
], callback);
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
version,
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
preload();
|
|
498
|
-
preload6();
|
|
499
|
-
|
|
500
|
-
// lookup4 = gen_lookup('geoip-country.dat', 4);
|
|
501
|
-
// lookup6 = gen_lookup('geoip-country6.dat', 16);
|
package/lib/utils.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
const utils = module.exports = {};
|
|
2
|
-
|
|
3
|
-
utils.aton4 = a => {
|
|
4
|
-
a = a.split(/\./);
|
|
5
|
-
return ((parseInt(a[0], 10) << 24) >>> 0) + ((parseInt(a[1], 10) << 16) >>> 0) + ((parseInt(a[2], 10) << 8) >>> 0) + (parseInt(a[3], 10) >>> 0);
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
utils.aton6 = a => {
|
|
9
|
-
a = a.replace(/"/g, '').split(/:/);
|
|
10
|
-
|
|
11
|
-
const l = a.length - 1;
|
|
12
|
-
let i;
|
|
13
|
-
|
|
14
|
-
if (a[l] === '') {
|
|
15
|
-
a[l] = 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (l < 7) {
|
|
19
|
-
a.length = 8;
|
|
20
|
-
|
|
21
|
-
for (i = l; i >= 0 && a[i] !== ''; i--) {
|
|
22
|
-
a[7 - l + i] = a[i];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
for (i = 0; i < 8; i++) {
|
|
27
|
-
if (!a[i]) {
|
|
28
|
-
a[i] = 0;
|
|
29
|
-
} else {
|
|
30
|
-
a[i] = parseInt(a[i], 16);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const r = [];
|
|
35
|
-
for (i = 0; i < 4; i++) {
|
|
36
|
-
r.push(((a[2 * i] << 16) + a[2 * i + 1]) >>> 0);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return r;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
utils.cmp = (a, b) => {
|
|
44
|
-
if (typeof a === 'number' && typeof b === 'number') return (a < b ? -1 : (a > b ? 1 : 0));
|
|
45
|
-
if (a instanceof Array && b instanceof Array) return this.cmp6(a, b);
|
|
46
|
-
|
|
47
|
-
return null;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
utils.cmp6 = (a, b) => {
|
|
51
|
-
for (let ii = 0; ii < 2; ii++) {
|
|
52
|
-
if (a[ii] < b[ii]) return -1;
|
|
53
|
-
if (a[ii] > b[ii]) return 1;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return 0;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
utils.isPrivateIP = addr => {
|
|
60
|
-
addr = addr.toString();
|
|
61
|
-
|
|
62
|
-
return addr.match(/^10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
|
|
63
|
-
addr.match(/^192\.168\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
|
|
64
|
-
addr.match(/^172\.16\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
|
|
65
|
-
addr.match(/^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
|
|
66
|
-
addr.match(/^169\.254\.([0-9]{1,3})\.([0-9]{1,3})/) != null ||
|
|
67
|
-
addr.match(/^fc00:/) != null || addr.match(/^fe80:/) != null;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
utils.ntoa4 = n => {
|
|
71
|
-
n = n.toString();
|
|
72
|
-
n = '' + (n >>> 24 & 0xff) + '.' + (n >>> 16 & 0xff) + '.' + (n >>> 8 & 0xff) + '.' + (n & 0xff);
|
|
73
|
-
return n;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
utils.ntoa6 = n => {
|
|
77
|
-
let a = '[';
|
|
78
|
-
|
|
79
|
-
for (let i = 0; i < n.length; i++) {
|
|
80
|
-
a += (n[i] >>> 16).toString(16) + ':';
|
|
81
|
-
a += (n[i] & 0xffff).toString(16) + ':';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
a = a.replace(/:$/, ']').replace(/:0+/g, ':').replace(/::+/, '::');
|
|
85
|
-
return a;
|
|
86
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const{access:t,constants:e,watch:n}=require('fs'),{join:o}=require('path'),l={},u=t=>l[t].close();module.exports={makeFsWatchFilter:(c,r,s,i,a)=>{let f=null;function h(){f=null,a()}'function'==typeof i&&(a=i,i=s,s=null),l[c]&&u(c),l[c]=n(r,function(n,l){if(!l)return;const u=o(r,l);s&&s!==l||t(u,e.F_OK,t=>{if(t)return console.error(t);null!==f&&(clearTimeout(f),f=null),f=setTimeout(h,i)})})},stopWatching:u};
|
package/lib-minified/main.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const{open:e,fstat:r,read:t,close:n,openSync:i,fstatSync:a,readSync:o,closeSync:l}=require('fs'),{join:u,resolve:c}=require('path'),{isIP:f}=require('net'),s=require('async'),{aton4:d,aton6:y,cmp6:B,ntoa4:p,ntoa6:S,cmp:g}=require('./utils.js'),I=require('./fsWatcher.js'),{version:E}=require('../package.json'),m='dataWatcher',z=c(__dirname,global.geoDataDir||process.env.GEODATADIR||'../geoip-data/'),N={city:u(z,'geoip-city.dat'),city6:u(z,'geoip-city6.dat'),cityNames:u(z,'geoip-city-names.dat'),country:u(z,'geoip-country.dat'),country6:u(z,'geoip-country6.dat')},P=[[d('10.0.0.0'),d('10.255.255.255')],[d('172.16.0.0'),d('172.31.255.255')],[d('192.168.0.0'),d('192.168.255.255')]],h={firstIP:null,lastIP:null,lastLine:0,locationBuffer:null,locationRecordSize:88,mainBuffer:null,recordSize:24},F={firstIP:null,lastIP:null,lastLine:0,mainBuffer:null,recordSize:48};let O=JSON.parse(JSON.stringify(h)),L=JSON.parse(JSON.stringify(F));const U=e=>{let r,t,n=0,i=O.lastLine,a=O.lastIP,o=O.firstIP;const l=O.mainBuffer,u=O.locationBuffer,c=P,f=O.recordSize,s=O.locationRecordSize,d={range:[null,null],country:'',region:'',eu:'',timezone:'',city:'',ll:[null,null],metro:null,area:null};if(e>O.lastIP||e<O.firstIP)return null;for(let r=0;r<c.length;r++)if(e>=c[r][0]&&e<=c[r][1])return null;for(;;){if(r=Math.round((i-n)/2)+n,a=l.readUInt32BE(r*f),o=l.readUInt32BE(r*f+4),a<=e&&o>=e)return d.range=[a,o],10===f?d.country=l.toString('utf8',r*f+8,r*f+10):(t=l.readUInt32BE(r*f+8),-1>>>0>t&&(d.country=u.toString('utf8',t*s,t*s+2).replace(/\u0000.*/,''),d.region=u.toString('utf8',t*s+2,t*s+5).replace(/\u0000.*/,''),d.metro=u.readInt32BE(t*s+5),d.ll[0]=l.readInt32BE(r*f+12)/1e4,d.ll[1]=l.readInt32BE(r*f+16)/1e4,d.area=l.readUInt32BE(r*f+20),d.eu=u.toString('utf8',t*s+9,t*s+10).replace(/\u0000.*/,''),d.timezone=u.toString('utf8',t*s+10,t*s+42).replace(/\u0000.*/,''),d.city=u.toString('utf8',t*s+42,t*s+s).replace(/\u0000.*/,''))),d;if(n===i)return null;n===i-1?r===n?n=i:i=n:a>e?i=r:o<e&&(n=r)}},D=['0:0:0:0:0:FFFF:','::FFFF:'];function J(u){let c,f;const d=JSON.parse(JSON.stringify(h));if('function'==typeof arguments[0])s.series([i=>{s.series([r=>{e(N.cityNames,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,d.locationBuffer=Buffer.alloc(f),e(r)})},e=>{t(c,d.locationBuffer,0,f,0,e)},e=>{n(c,e)},r=>{e(N.city,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,e(r)})}],t=>{if(t){if('ENOENT'!==t.code&&'EBADF'!==t.code)throw t;e(N.country,'r',(e,t)=>{e?i(e):(c=t,r(c,(e,r)=>{f=r.size,d.recordSize=10,i()}))})}else i()})},()=>{d.mainBuffer=Buffer.alloc(f),s.series([e=>{t(c,d.mainBuffer,0,f,0,e)},e=>{n(c,e)}],e=>{e||(d.lastLine=f/d.recordSize-1,d.lastIP=d.mainBuffer.readUInt32BE(d.lastLine*d.recordSize+4),d.firstIP=d.mainBuffer.readUInt32BE(0),O=d),u(e)})}]);else{try{if(c=i(N.cityNames,'r'),f=a(c).size,0===f)throw{code:'EMPTY_FILE'};O.locationBuffer=Buffer.alloc(f),o(c,O.locationBuffer,0,f,0),l(c),c=i(N.city,'r'),f=a(c).size}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;c=i(N.country,'r'),f=a(c).size,O.recordSize=10}O.mainBuffer=Buffer.alloc(f),o(c,O.mainBuffer,0,f,0),l(c),O.lastLine=f/O.recordSize-1,O.lastIP=O.mainBuffer.readUInt32BE(O.lastLine*O.recordSize+4),O.firstIP=O.mainBuffer.readUInt32BE(0)}}function T(u){let c,f;const d=JSON.parse(JSON.stringify(F));if('function'==typeof arguments[0])s.series([t=>{s.series([r=>{e(N.city6,'r',(e,t)=>{c=t,r(e)})},e=>{r(c,(r,t)=>{f=t.size,e(r)})}],n=>{if(n){if('ENOENT'!==n.code&&'EBADF'!==n.code)throw n;e(N.country6,'r',(e,n)=>{e?t(e):(c=n,r(c,(e,r)=>{f=r.size,d.recordSize=34,t()}))})}else t()})},()=>{d.mainBuffer=Buffer.alloc(f),s.series([e=>{t(c,d.mainBuffer,0,f,0,e)},e=>{n(c,e)}],e=>{e||(d.lastLine=f/d.recordSize-1,L=d),u(e)})}]);else{try{if(c=i(N.city6,'r'),f=a(c).size,0===f)throw{code:'EMPTY_FILE'}}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;c=i(N.country6,'r'),f=a(c).size,L.recordSize=34}L.mainBuffer=Buffer.alloc(f),o(c,L.mainBuffer,0,f,0),l(c),L.lastLine=f/L.recordSize-1}}module.exports={cmp:g,lookup:e=>{if(!e)return null;if('number'==typeof e)return U(e);if(4===f(e))return U(d(e));if(6===f(e)){const r=(e=>{const r=e.toUpperCase();for(let e=0;e<D.length;e++){const t=D[e];if(0===r.indexOf(t))return r.substring(t.length)}return null})(e);return r?U(d(r)):(e=>{const r=L.mainBuffer,t=L.recordSize,n=O.locationBuffer,i=O.locationRecordSize,a={range:[null,null],country:'',region:'',city:'',ll:[0,0],metro:null,area:null,eu:'',timezone:''},o=(e,n)=>{const i=[];for(let a=0;a<2;a++)i.push(r.readUInt32BE(e*t+16*n+4*a));return i};L.lastIP=o(L.lastLine,1),L.firstIP=o(0,0);let l,u,c=0,f=L.lastLine,s=L.lastIP,d=L.firstIP;if(B(e,L.lastIP)>0||B(e,L.firstIP)<0)return null;for(;;){if(l=Math.round((f-c)/2)+c,s=o(l,0),d=o(l,1),B(s,e)<=0&&B(d,e)>=0)return 34===t?a.country=r.toString('utf8',l*t+32,l*t+34).replace(/\u0000.*/,''):(u=r.readUInt32BE(l*t+32),-1>>>0>u&&(a.country=n.toString('utf8',u*i,u*i+2).replace(/\u0000.*/,''),a.region=n.toString('utf8',u*i+2,u*i+5).replace(/\u0000.*/,''),a.metro=n.readInt32BE(u*i+5),a.ll[0]=r.readInt32BE(l*t+36)/1e4,a.ll[1]=r.readInt32BE(l*t+40)/1e4,a.area=r.readUInt32BE(l*t+44),a.eu=n.toString('utf8',u*i+9,u*i+10).replace(/\u0000.*/,''),a.timezone=n.toString('utf8',u*i+10,u*i+42).replace(/\u0000.*/,''),a.city=n.toString('utf8',u*i+42,u*i+i).replace(/\u0000.*/,''))),a;if(c===f)return null;c===f-1?l===c?c=f:f=c:B(s,e)>0?f=l:B(d,e)<0&&(c=l)}})(y(e))}return null},pretty:e=>'string'==typeof e?e:'number'==typeof e?p(e):Array.isArray(e)?S(e):e,startWatchingDataUpdate:e=>{I.makeFsWatchFilter(m,z,6e4,async()=>{await s.series([e=>{J(e)},e=>{T(e)}],e)})},stopWatchingDataUpdate:()=>I.stopWatching(m),clear:()=>{O=JSON.parse(JSON.stringify(h)),L=JSON.parse(JSON.stringify(F))},reloadDataSync:()=>{J(),T()},reloadData:async e=>{await s.series([e=>{J(e)},e=>{T(e)}],e)},version:E},J(),T();
|
package/minify.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const terser = require('terser');
|
|
2
|
-
const fs = require('node:fs/promises');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
|
|
5
|
-
const minifyJSFiles = async (sourceDirectory, outputDirectory) => {
|
|
6
|
-
try {
|
|
7
|
-
await fs.access(sourceDirectory);
|
|
8
|
-
} catch {
|
|
9
|
-
throw new Error(`Source directory does not exist: ${sourceDirectory}`);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
await fs.rm(outputDirectory, { recursive: true, force: true });
|
|
13
|
-
await fs.mkdir(outputDirectory, { recursive: true });
|
|
14
|
-
|
|
15
|
-
const files = (await fs.readdir(sourceDirectory)).filter(file => file.endsWith('.js'));
|
|
16
|
-
for (const file of files) {
|
|
17
|
-
try {
|
|
18
|
-
const code = await fs.readFile(path.join(sourceDirectory, file), 'utf8');
|
|
19
|
-
const result = await terser.minify(code, {
|
|
20
|
-
mangle: true,
|
|
21
|
-
ecma: 2024,
|
|
22
|
-
module: true,
|
|
23
|
-
compress: {
|
|
24
|
-
passes: 3,
|
|
25
|
-
pure_funcs: ['console.info', 'console.debug'],
|
|
26
|
-
hoist_funs: false,
|
|
27
|
-
hoist_vars: true,
|
|
28
|
-
reduce_funcs: true,
|
|
29
|
-
reduce_vars: true,
|
|
30
|
-
unsafe: false,
|
|
31
|
-
unused: true,
|
|
32
|
-
dead_code: true,
|
|
33
|
-
},
|
|
34
|
-
format: {
|
|
35
|
-
quote_style: 3,
|
|
36
|
-
preserve_annotations: true,
|
|
37
|
-
comments: false,
|
|
38
|
-
},
|
|
39
|
-
toplevel: true,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await fs.writeFile(path.join(outputDirectory, file), result.code, 'utf8');
|
|
43
|
-
console.log('Minimized:', file);
|
|
44
|
-
} catch (err) {
|
|
45
|
-
console.error(`Error processing ${file}:`, err.message);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.log('COMPLETED:', sourceDirectory);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
(async () => {
|
|
53
|
-
await minifyJSFiles('./lib', './lib-minified');
|
|
54
|
-
await minifyJSFiles('./utils', './utils-minified');
|
|
55
|
-
})();
|
package/test/example.js
DELETED
package/test/geo-lookup.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const t1 = +new Date();
|
|
3
|
-
const geoIp2 = require('../lib/main.js');
|
|
4
|
-
const t2 = +new Date();
|
|
5
|
-
|
|
6
|
-
if (process.argv.length > 2) {
|
|
7
|
-
console.dir(geoIp2.lookup(process.argv[2]));
|
|
8
|
-
const t3 = +new Date();
|
|
9
|
-
console.log('Startup: %dms, exec: %dms', t2 - t1, t3 - t2);
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const f = [];
|
|
14
|
-
let ip;
|
|
15
|
-
const n = 30000;
|
|
16
|
-
const nf = [];
|
|
17
|
-
let r;
|
|
18
|
-
const ts = +new Date();
|
|
19
|
-
|
|
20
|
-
for (let i = 0; i < n; i++) {
|
|
21
|
-
if ((i % 2) === 0) {
|
|
22
|
-
ip = Math.round((Math.random() * 0xff000000) + 0xffffff);
|
|
23
|
-
} else {
|
|
24
|
-
ip = '2001:' +
|
|
25
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
26
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
27
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
28
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
29
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
30
|
-
Math.round(Math.random() * 0xffff).toString(16) + ':' +
|
|
31
|
-
Math.round(Math.random() * 0xffff).toString(16) + '';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
r = geoIp2.lookup(ip);
|
|
35
|
-
if (r === null) {
|
|
36
|
-
nf.push(ip);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
f.push([ip, r]);
|
|
41
|
-
|
|
42
|
-
assert.ok(geoIp2.cmp(ip, r.range[0]) >= 0, 'Problem with ' + geoIp2.pretty(ip) + ' < ' + geoIp2.pretty(r.range[0]));
|
|
43
|
-
assert.ok(geoIp2.cmp(ip, r.range[1]) <= 0, 'Problem with ' + geoIp2.pretty(ip) + ' > ' + geoIp2.pretty(r.range[1]));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const te = +new Date();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// f.forEach(data => {
|
|
50
|
-
// console.log('%s bw %s & %s is %s', geoIp2.pretty(data[0]), geoIp2.pretty(data[1].range[0]), geoIp2.pretty(data[1].range[1]), data[1].country);
|
|
51
|
-
// });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log('Found %d (%d/%d) ips in %dms (%s ip/s) (%sμs/ip)', n, f.length, nf.length, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) * 1000 / n).toFixed(0));
|
|
55
|
-
console.log('Took %d ms to startup', t2 - t1);
|
package/test/index.test.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
const geoIp2 = require('../lib/main.js');
|
|
2
|
-
|
|
3
|
-
describe('GeoIP2', () => {
|
|
4
|
-
describe('#testLookup', () => {
|
|
5
|
-
it('should return data about IPv4', () => {
|
|
6
|
-
const ip = '1.1.1.1';
|
|
7
|
-
const actual = geoIp2.lookup(ip);
|
|
8
|
-
expect(actual).toBeTruthy();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should return data about IPv6', () => {
|
|
12
|
-
const actual = geoIp2.lookup('2606:4700:4700::64');
|
|
13
|
-
expect(actual).toBeTruthy();
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('#testDataIP4', () => {
|
|
18
|
-
it('should match data for IPv4 - PL', () => {
|
|
19
|
-
const actual = geoIp2.lookup('104.113.255.255');
|
|
20
|
-
expect(actual.country).toBe('PL');
|
|
21
|
-
expect(actual.region).toBe('14');
|
|
22
|
-
expect(actual.eu).toBe('1');
|
|
23
|
-
expect(actual.timezone).toBe('Europe/Warsaw');
|
|
24
|
-
expect(actual.city).toBe('Warsaw');
|
|
25
|
-
expect(actual.ll).toBeTruthy();
|
|
26
|
-
expect(actual.metro).toBe(0);
|
|
27
|
-
expect(actual.area).toBe(20);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should match data for IPv4 - US', () => {
|
|
31
|
-
const actual = geoIp2.lookup('72.229.28.185');
|
|
32
|
-
expect(actual.country).toBe('US');
|
|
33
|
-
expect(actual.region).toBe('NY');
|
|
34
|
-
expect(actual.eu).toBe('0');
|
|
35
|
-
expect(actual.timezone).toBe('America/New_York');
|
|
36
|
-
expect(actual.city).toBe('New York');
|
|
37
|
-
expect(actual.ll).toBeTruthy();
|
|
38
|
-
expect(actual.metro).toBe(501);
|
|
39
|
-
expect(actual.area).toBe(5);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should match data for IPv4 - JP', () => {
|
|
43
|
-
const actual = geoIp2.lookup('210.138.184.59');
|
|
44
|
-
expect(actual.country).toBe('JP');
|
|
45
|
-
expect(actual.eu).toBe('0');
|
|
46
|
-
expect(actual.timezone).toBe('Asia/Tokyo');
|
|
47
|
-
expect(actual.city).toBe('');
|
|
48
|
-
expect(actual.ll).toBeTruthy();
|
|
49
|
-
expect(actual.metro).toBe(0);
|
|
50
|
-
expect(actual.area).toBe(500);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should match data for IPv4 - RU', () => {
|
|
54
|
-
const actual = geoIp2.lookup('109.108.63.255');
|
|
55
|
-
expect(actual.country).toBe('RU');
|
|
56
|
-
expect(actual.region).toBe('IVA');
|
|
57
|
-
expect(actual.eu).toBe('0');
|
|
58
|
-
expect(actual.timezone).toBe('Europe/Moscow');
|
|
59
|
-
expect(actual.city).toBe('Kineshma');
|
|
60
|
-
expect(actual.ll).toBeTruthy();
|
|
61
|
-
expect(actual.metro).toBe(0);
|
|
62
|
-
expect(actual.area).toBe(100);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('#testDataIP6', () => {
|
|
67
|
-
it('should match data for IPv6 - PL', () => {
|
|
68
|
-
const actual = geoIp2.lookup('2a01:118f:30a:3900:c954:e6ef:8067:d4e8');
|
|
69
|
-
expect(actual.country).toBe('PL');
|
|
70
|
-
expect(actual.region).toBe('06');
|
|
71
|
-
expect(actual.eu).toBe('1');
|
|
72
|
-
expect(actual.timezone).toBe('Europe/Warsaw');
|
|
73
|
-
expect(actual.city).toBe('Lublin');
|
|
74
|
-
expect(actual.ll).toBeTruthy();
|
|
75
|
-
expect(actual.metro).toBe(0);
|
|
76
|
-
expect(actual.area).toBe(100);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should match data for IPv6 - NL ', () => {
|
|
80
|
-
const actual = geoIp2.lookup('2001:1c04:400::1');
|
|
81
|
-
expect(actual.country).toBe('NL');
|
|
82
|
-
expect(actual.region).toBe('NH');
|
|
83
|
-
expect(actual.eu).toBe('1');
|
|
84
|
-
expect(actual.timezone).toBe('Europe/Amsterdam');
|
|
85
|
-
expect(actual.city).toBe('Zandvoort');
|
|
86
|
-
expect(actual.ll).toBeTruthy();
|
|
87
|
-
expect(actual.metro).toBe(0);
|
|
88
|
-
expect(actual.area).toBe(5);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should match data for IPv6 - JP', () => {
|
|
92
|
-
const actual = geoIp2.lookup('2400:8500:1302:814:a163:44:173:238f');
|
|
93
|
-
expect(actual.country).toBe('JP');
|
|
94
|
-
expect(actual.region).toBe('');
|
|
95
|
-
expect(actual.eu).toBe('0');
|
|
96
|
-
expect(actual.timezone).toBe('Asia/Tokyo');
|
|
97
|
-
expect(actual.city).toBe('');
|
|
98
|
-
expect(actual.ll).toBeTruthy();
|
|
99
|
-
expect(actual.metro).toBe(0);
|
|
100
|
-
expect(actual.area).toBe(500);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('#testUTF8', () => {
|
|
105
|
-
it('should return UTF8 city name', () => {
|
|
106
|
-
const actual = geoIp2.lookup('2.139.175.1');
|
|
107
|
-
expect(actual.country).toBe('ES');
|
|
108
|
-
expect(actual.city).toBe('Barcelona');
|
|
109
|
-
expect(actual.timezone).toBe('Europe/Madrid');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should match data for IPv4 - PL', () => {
|
|
113
|
-
const actual = geoIp2.lookup('86.63.89.41');
|
|
114
|
-
expect(actual.country).toBe('PL');
|
|
115
|
-
expect(actual.timezone).toBe('Europe/Warsaw');
|
|
116
|
-
expect(actual.city).toBe('Piła');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('#testMetro', () => {
|
|
121
|
-
it('should match metro data', () => {
|
|
122
|
-
const actual = geoIp2.lookup('23.240.63.68');
|
|
123
|
-
expect(actual.metro).toBe(803);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('#testIPv4MappedIPv6', () => {
|
|
128
|
-
it('should match IPv4 mapped IPv6 data', () => {
|
|
129
|
-
const actual = geoIp2.lookup('195.16.170.74');
|
|
130
|
-
expect(actual.metro).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('#testSyncReload', () => {
|
|
135
|
-
it('should reload data synchronously', () => {
|
|
136
|
-
const before4 = geoIp2.lookup('75.82.117.180');
|
|
137
|
-
expect(before4).not.toBeNull();
|
|
138
|
-
const before6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
139
|
-
expect(before6).not.toBeNull();
|
|
140
|
-
|
|
141
|
-
geoIp2.clear();
|
|
142
|
-
|
|
143
|
-
const none4 = geoIp2.lookup('75.82.117.180');
|
|
144
|
-
expect(none4).toBeNull();
|
|
145
|
-
const none6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
146
|
-
expect(none6).toBeNull();
|
|
147
|
-
|
|
148
|
-
geoIp2.reloadDataSync();
|
|
149
|
-
|
|
150
|
-
const after4 = geoIp2.lookup('75.82.117.180');
|
|
151
|
-
expect(before4).toEqual(after4);
|
|
152
|
-
const after6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
153
|
-
expect(before6).toEqual(after6);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('#testAsyncReload', () => {
|
|
158
|
-
it('should reload data asynchronously', (done) => {
|
|
159
|
-
const before4 = geoIp2.lookup('75.82.117.180');
|
|
160
|
-
expect(before4).not.toBeNull();
|
|
161
|
-
const before6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
162
|
-
expect(before6).not.toBeNull();
|
|
163
|
-
|
|
164
|
-
geoIp2.clear();
|
|
165
|
-
|
|
166
|
-
const none4 = geoIp2.lookup('75.82.117.180');
|
|
167
|
-
expect(none4).toBeNull();
|
|
168
|
-
const none6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
169
|
-
expect(none6).toBeNull();
|
|
170
|
-
|
|
171
|
-
geoIp2.reloadData(() => {
|
|
172
|
-
const after4 = geoIp2.lookup('75.82.117.180');
|
|
173
|
-
expect(before4).toEqual(after4);
|
|
174
|
-
const after6 = geoIp2.lookup('::ffff:173.185.182.82');
|
|
175
|
-
expect(before6).toEqual(after6);
|
|
176
|
-
|
|
177
|
-
done();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('#testInvalidIP', () => {
|
|
183
|
-
it('should return null for an invalid IP address', () => {
|
|
184
|
-
const ip = 'invalid_ip_address';
|
|
185
|
-
const actual = geoIp2.lookup(ip);
|
|
186
|
-
expect(actual).toBeNull();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('#testEmptyIP', () => {
|
|
191
|
-
it('should return null for an empty IP address', () => {
|
|
192
|
-
const actual = geoIp2.lookup('');
|
|
193
|
-
expect(actual).toBeNull();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('#testNullIP', () => {
|
|
198
|
-
it('should return null for a null IP address', () => {
|
|
199
|
-
const actual = geoIp2.lookup(null);
|
|
200
|
-
expect(actual).toBeNull();
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('#testUnknownIP', () => {
|
|
205
|
-
it('should return null for an unknown IP address', () => {
|
|
206
|
-
const ip = '192.168.0.1'; // Private IP address
|
|
207
|
-
const actual = geoIp2.lookup(ip);
|
|
208
|
-
expect(actual).toBeNull();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('#testNoDataForIP', () => {
|
|
213
|
-
it('should return null for an IP address with no data', () => {
|
|
214
|
-
const ip = '203.0.113.0'; // Example IP with no data
|
|
215
|
-
const actual = geoIp2.lookup(ip);
|
|
216
|
-
expect(actual).toBeNull();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
describe('#testSpecialCharactersIP', () => {
|
|
221
|
-
it('should return null for an IP address with special characters', () => {
|
|
222
|
-
const ip = '20.24.@.&'; // IP with special characters
|
|
223
|
-
const actual = geoIp2.lookup(ip);
|
|
224
|
-
expect(actual).toBeNull();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
package/test/memory_usage.js
DELETED
package/test/other.js
DELETED
package/test/setInterval.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const geoIp2 = require('../lib/main.js');
|
|
2
|
-
const ip = '86.63.89.41';
|
|
3
|
-
|
|
4
|
-
// Function
|
|
5
|
-
const action = async () => {
|
|
6
|
-
const data = geoIp2.lookup(ip);
|
|
7
|
-
console.log(data);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// Interval
|
|
11
|
-
setInterval(async () => {
|
|
12
|
-
await action();
|
|
13
|
-
}, 100);
|
|
14
|
-
|
|
15
|
-
// Run
|
|
16
|
-
(async () => await action())();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const{name:e,version:t}=require('../package.json'),n=`Mozilla/5.0 (compatible; ${e}/${t}; +https://github.com/sefinek/geoip-lite2)`,o=require('fs'),r=require('http'),s=require('https'),i=require('path'),c=require('zlib'),a=require('readline'),l=require('async'),{decodeStream:u}=require('iconv-lite'),d=require('rimraf').sync,p=require('adm-zip'),f=require('../lib/utils.js'),{Address6:h,Address4:g}=require('ip-address'),w=process.argv.slice(2);let m=w.find(e=>null!==e.match(/^license_key=[a-zA-Z0-9]+/));void 0===m&&void 0!==process.env.LICENSE_KEY&&(m=`license_key=${process.env.LICENSE_KEY}`);let y=w.find(e=>null!==e.match(/^geoDataDir=[\w./]+/));void 0===y&&void 0!==process.env.GEODATADIR&&(y=`geoDataDir=${process.env.GEODATADIR}`);let I=i.resolve(__dirname,'..','geoip-data');void 0!==y&&(I=i.resolve(process.cwd(),y.split('=')[1]),o.existsSync(I)||(console.log('ERROR: Directory doesn\'t exist: '+I),process.exit(1)));const v=process.env.GEOTMPDIR||i.resolve(__dirname,'..','tmp'),S={},E={NaN:-1},x=[{type:'country',url:`https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country-CSV&suffix=zip&${m}`,checksum:`https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country-CSV&suffix=zip.sha256&${m}`,fileName:'GeoLite2-Country-CSV.zip',src:['GeoLite2-Country-Locations-en.csv','GeoLite2-Country-Blocks-IPv4.csv','GeoLite2-Country-Blocks-IPv6.csv'],dest:['','geoip-country.dat','geoip-country6.dat']},{type:'city',url:`https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&suffix=zip&${m}`,checksum:`https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&suffix=zip.sha256&${m}`,fileName:'GeoLite2-City-CSV.zip',src:['GeoLite2-City-Locations-en.csv','GeoLite2-City-Blocks-IPv4.csv','GeoLite2-City-Blocks-IPv6.csv'],dest:['geoip-city-names.dat','geoip-city.dat','geoip-city6.dat']}];function k(e){const t=i.dirname(e);o.existsSync(t)||o.mkdirSync(t)}const D=/^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/,C=/(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;function R(e){if(!D.test(e)&&(e=function(e){let t=0,n=-1;for(e=e.replace(/""/,'\\"').replace(/'/g,'\\\'');t<e.length&&n<e.length;)t=n,n=e.indexOf(',',t+1),n<0&&(n=e.length),e.indexOf('\'',t||0)>-1&&e.indexOf('\'',t)<n&&'"'!=e[t+1]&&'"'!=e[n-1]&&(n=(e=e.substr(0,t+1)+'"'+e.substr(t+1,n-t-1)+'"'+e.substr(n,e.length-n)).indexOf(',',n+1),n<0&&(n=e.length));return e}(e),!D.test(e)))return null;const t=[];return e.replace(C,(e,n,o,r)=>(void 0!==n?t.push(n.replace(/\\'/g,'\'')):void 0!==o?t.push(o.replace(/\\"/g,'"').replace(/\\'/g,'\'')):void 0!==r&&t.push(r),'')),/,\s*$/.test(e)&&t.push(''),t}function B(e){const t=new URL(e),o={protocol:t.protocol,host:t.host,path:t.pathname+t.search,headers:{'User-Agent':n}};if(process.env.http_proxy||process.env.https_proxy)try{const e=require('https-proxy-agent');o.agent=new e(process.env.http_proxy||process.env.https_proxy)}catch(e){console.error(`Install https-proxy-agent to use an HTTP/HTTPS proxy. ${e.message}`),process.exit(-1)}return o}function _(e,t){if(-1!==w.indexOf('force'))return t(null,e);const n=e.checksum;if(void 0===n)return t(null,e);o.readFile(i.join(I,`${e.type}.checksum`),{encoding:'utf8'},(o,i)=>{!o&&i&&i.length&&(e.checkValue=i),console.log('Checking',e.fileName);var c=s.get(B(n),function n(o){const i=o.statusCode;if([301,302,303,307,308].includes(i))return s.get(B(o.headers.location),n);200!==i&&(console.error(o.data),console.error('ERROR: HTTP Request Failed [%d %s]',i,r.STATUS_CODES[i]),c.end(),process.exit(1));let a='';o.on('data',e=>{a+=e}),o.on('end',()=>{a&&a.length?a===e.checkValue?(console.log(`Database "${e.type}" is up to date`),e.skip=!0):(console.log(`Database "${e.type}" has new data`),e.checkValue=a):(console.error(`ERROR: Could not retrieve checksum for ${e.type}. Aborting.`),console.error('Run with "force" to update without checksum'),c.end(),process.exit(1)),t(null,e)})})})}function A(e,t){if(e.skip)return t(null,null,null,e);const n=e.url;let a=e.fileName;const l='.gz'===i.extname(a);l&&(a=a.replace('.gz',''));const u=i.join(v,a);if(o.existsSync(u))return t(null,u,a,e);console.log('Fetching',a),k(u);var d=s.get(B(n),function n(i){const p=i.statusCode;if([301,302,303,307,308].includes(p))return s.get(B(i.headers.location),n);let f;200!==p&&(console.error('ERROR: HTTP Request Failed [%d %s]',p,r.STATUS_CODES[p]),d.end(),process.exit(1));const h=o.createWriteStream(u);f=l?i.pipe(c.createGunzip()).pipe(h):i.pipe(h),f.on('close',()=>{console.log(' DONE'),t(null,u,a,e)})});process.stdout.write(`Retrieving ${a}...`)}function O(e,t,n,r){if(n.skip)return r(null,n);'.zip'!==i.extname(t)||(process.stdout.write('Extracting '+t+'...'),new p(e).getEntries().forEach(e=>{if(e.isDirectory)return;const t=e.entryName.split('/'),n=t[t.length-1],r=i.join(v,n);o.writeFileSync(r,e.getData())}),console.log(' DONE')),r(null,n)}async function b(e,t){var n,r;let s=0;function c(e){const t=R(e);if(!t||t.length<6)return console.warn('weird line: %s::',e);let o,i,c;s++;const a=S[t[1]];let l,u,d;if(a){if(t[0].match(/:/)){for(u=34,c=new h(t[0]),o=f.aton6(c.startAddress().correctForm()),i=f.aton6(c.endAddress().correctForm()),l=Buffer.alloc(u),d=0;d<o.length;d++)l.writeUInt32BE(o[d],4*d);for(d=0;d<i.length;d++)l.writeUInt32BE(i[d],16+4*d)}else u=10,c=new g(t[0]),o=parseInt(c.startAddress().bigInt(),10),i=parseInt(c.endAddress().bigInt(),10),l=Buffer.alloc(u),l.fill(0),l.writeUInt32BE(o,0),l.writeUInt32BE(i,4);return l.write(a,u-2),Date.now()-n>5e3&&(n=Date.now(),process.stdout.write(`\nStill working (${s})...`)),r._writableState.needDrain?new Promise(e=>{r.write(l,e)}):r.write(l)}}const l=i.join(I,t),u=i.join(v,e);d(l),k(l),process.stdout.write('\nProcessing data (may take a moment)...'),n=Date.now(),r=o.createWriteStream(l);const p=a.createInterface({input:o.createReadStream(u),crlfDelay:1/0});let w=0;for await(const e of p)w++,1!==w&&await c(e);r.close(),console.log(' DONE')}async function $(e,t){var n,r;let s=0;async function c(e){if(e.match(/^Copyright/)||!e.match(/\d/))return;const t=R(e);if(!t)return console.warn('Weird line: %s::',e);let o,i,c,a,l,u,d,p,w,m;if(s++,t[0].match(/:/)){let e=0;for(u=48,c=new h(t[0]),o=f.aton6(c.startAddress().correctForm()),i=f.aton6(c.endAddress().correctForm()),a=parseInt(t[1],10),a=E[a],l=Buffer.alloc(u),l.fill(0),m=0;m<o.length;m++)l.writeUInt32BE(o[m],e),e+=4;for(m=0;m<i.length;m++)l.writeUInt32BE(i[m],e),e+=4;l.writeUInt32BE(a>>>0,32),d=Math.round(1e4*parseFloat(t[7])),p=Math.round(1e4*parseFloat(t[8])),w=parseInt(t[9],10),l.writeInt32BE(d,36),l.writeInt32BE(p,40),l.writeInt32BE(w,44)}else u=24,c=new g(t[0]),o=parseInt(c.startAddress().bigInt(),10),i=parseInt(c.endAddress().bigInt(),10),a=parseInt(t[1],10),a=E[a],l=Buffer.alloc(u),l.fill(0),l.writeUInt32BE(o>>>0,0),l.writeUInt32BE(i>>>0,4),l.writeUInt32BE(a>>>0,8),d=Math.round(1e4*parseFloat(t[7])),p=Math.round(1e4*parseFloat(t[8])),w=parseInt(t[9],10),l.writeInt32BE(d,12),l.writeInt32BE(p,16),l.writeInt32BE(w,20);return Date.now()-n>5e3&&(n=Date.now(),process.stdout.write('\nStill working ('+s+')...')),r._writableState.needDrain?new Promise(e=>{r.write(l,e)}):r.write(l)}const l=i.join(I,t),u=i.join(v,e);d(l),process.stdout.write('\nProcessing data (may take a moment)...'),n=Date.now(),r=o.createWriteStream(l);const p=a.createInterface({input:o.createReadStream(u),crlfDelay:1/0});let w=0;for await(const e of p)w++,1!==w&&await c(e);r.close()}function q(e,t){if(e.skip)return t(null,e);const n=e.type,r=e.src,s=e.dest;'country'===n?Array.isArray(r)?function(e,t){const n=i.join(v,e);process.stdout.write('Processing lookup data (may take a moment)...');const r=a.createInterface({input:o.createReadStream(n).pipe(u('latin1')),output:process.stdout,terminal:!1});let s=0;r.on('line',e=>{s>0&&function(e){const t=R(e);!t||t.length<6?console.log('Weird line: %s::',e):S[t[0]]=t[4]}(e),s++}),r.on('close',()=>{console.log(' DONE'),t()})}(r[0],()=>{b(r[1],s[1]).then(()=>b(r[2],s[2])).then(()=>{t(null,e)})}):b(r,s):'city'===n&&function(e,t,n){let r=null,s=0;const c=i.join(I,t),l=i.join(v,e);d(c);var p=o.openSync(c,'w');const f=a.createInterface({input:o.createReadStream(l).pipe(u('utf-8')),output:process.stdout,terminal:!1});let h=0;f.on('line',e=>{h>0&&function(e){if(e.match(/^Copyright/)||!e.match(/\d/))return;const t=Buffer.alloc(88),n=R(e);if(!n)return void console.warn('Weird line: %s::',e);r=parseInt(n[0]),E[r]=s;const i=n[4],c=n[6],a=n[10],l=parseInt(n[11]),u=n[12],d=n[13];t.fill(0),t.write(i,0),t.write(c,2),l&&t.writeInt32BE(l,5),t.write(d,9),t.write(u,10),t.write(a,42),o.writeSync(p,t,0,t.length,null),s++}(e),h++}),f.on('close',n)}(r[0],s[0],()=>{$(r[1],s[1]).then(()=>(console.log('\nCity data processed'),$(r[2],s[2]))).then(()=>{console.log(' DONE'),t(null,e)})})}function L(e,t){if(e.skip||!e.checkValue)return t();o.writeFile(i.join(I,e.type+'.checksum'),e.checkValue,'utf8',n=>{n&&console.log('Failed to Update checksums! Database:',e.type),t()})}m||(console.error('ERROR: Missing license_key'),process.exit(1)),d(v),k(v),l.eachSeries(x,(e,t)=>{l.seq(_,A,O,q,L)(e,t)},e=>{e?(console.error('Failed to update databases from MaxMind!',e),process.exit(1)):(console.log('Successfully updated databases from MaxMind'),-1!==w.indexOf('debug')||d(v),process.exit(0))});
|
|
File without changes
|