geoip-lite2 2.2.8-beta.4 → 2.2.8-beta.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/data/city.checksum +1 -1
- package/data/country.checksum +1 -1
- package/data/geoip-city-names.dat +0 -0
- package/data/geoip-city.dat +0 -0
- package/data/geoip-city6.dat +0 -0
- package/data/geoip-country.dat +0 -0
- package/data/geoip-country6.dat +0 -0
- package/fsWatcher.js +85 -0
- package/index.js +571 -0
- package/package.json +11 -9
- package/tools/updatedb.js +114 -46
- package/utils.js +103 -0
- package/dist/fsWatcher.js +0 -1
- package/dist/main.js +0 -1
- package/dist/utils.js +0 -1
package/data/city.checksum
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1e2d74273ff207a2a7d129a195b300dbc45b22a4358cd3e45de17ae47ccab4f8 GeoLite2-City-CSV_20251128.zip
|
package/data/country.checksum
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
67ac2aeddc82fc748089eaf5b01edf2ce0895c0c383a4a3f6812270a93bb3333 GeoLite2-Country-CSV_20251128.zip
|
|
Binary file
|
package/data/geoip-city.dat
CHANGED
|
Binary file
|
package/data/geoip-city6.dat
CHANGED
|
Binary file
|
package/data/geoip-country.dat
CHANGED
|
Binary file
|
package/data/geoip-country6.dat
CHANGED
|
Binary file
|
package/fsWatcher.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// File System Watcher for Auto-Reloading GeoIP Data
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
const { access, constants, watch } = require('node:fs');
|
|
6
|
+
const { join } = require('node:path');
|
|
7
|
+
const FSWatcher = {};
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Watcher Management
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Takes an FSWatcher object and closes it.
|
|
15
|
+
* @param {string} name - The name of the watcher to close.
|
|
16
|
+
*/
|
|
17
|
+
const stopWatching = name => FSWatcher[name].close();
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// File System Watch with Debounce
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Takes a directory/file and watch for change. Upon change, call the callback.
|
|
25
|
+
*
|
|
26
|
+
* @param {String} name - name of this watcher
|
|
27
|
+
* @param {String} directory - path to the directory to watch
|
|
28
|
+
* @param {String} [filename] - (optional) specific filename to watch for, watches for all files in the directory if unspecified
|
|
29
|
+
* @param {Number} cdDelay - delay to wait before triggering the callback
|
|
30
|
+
* @param {Function} callback - function() - called when changes are detected
|
|
31
|
+
*/
|
|
32
|
+
const makeFsWatchFilter = (name, directory, filename, cdDelay, callback) => {
|
|
33
|
+
let cdId = null;
|
|
34
|
+
|
|
35
|
+
// Delete the cdId and callback the outer function
|
|
36
|
+
function timeoutCallback() {
|
|
37
|
+
cdId = null;
|
|
38
|
+
callback();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// This function is called when there is a change in the data directory
|
|
42
|
+
// It sets a timer to wait for the change to be completed
|
|
43
|
+
function onWatchEvent(event, changedFile) {
|
|
44
|
+
// Check to make sure changedFile is not null
|
|
45
|
+
if (!changedFile) return;
|
|
46
|
+
|
|
47
|
+
const filePath = join(directory, changedFile);
|
|
48
|
+
if (!filename || filename === changedFile) {
|
|
49
|
+
access(filePath, constants.F_OK, err => {
|
|
50
|
+
if (err) return console.error(err);
|
|
51
|
+
|
|
52
|
+
// At this point, a new file system activity has been detected,
|
|
53
|
+
// We have to wait for file transfer to be finished before moving on.
|
|
54
|
+
|
|
55
|
+
// If a cdId already exists, we delete it
|
|
56
|
+
if (cdId !== null) {
|
|
57
|
+
clearTimeout(cdId);
|
|
58
|
+
cdId = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Once the cdDelay has passed, the timeoutCallback function will be called
|
|
62
|
+
cdId = setTimeout(timeoutCallback, cdDelay);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Manage the case where filename is missing (because it's optional)
|
|
68
|
+
if (typeof cdDelay === 'function') {
|
|
69
|
+
callback = cdDelay;
|
|
70
|
+
cdDelay = filename;
|
|
71
|
+
filename = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (FSWatcher[name]) {
|
|
75
|
+
stopWatching(name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
FSWatcher[name] = watch(directory, onWatchEvent);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Exports
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
module.exports = { makeFsWatchFilter, stopWatching };
|
package/index.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Dependencies
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
const { open, fstat, read, close, openSync, fstatSync, readSync, closeSync } = require('node:fs');
|
|
6
|
+
const { join, resolve } = require('node:path');
|
|
7
|
+
const { isIP } = require('node:net');
|
|
8
|
+
const async = require('async');
|
|
9
|
+
const { aton4, aton6, cmp6, ntoa4, ntoa6, cmp } = require('./utils.js');
|
|
10
|
+
const fsWatcher = require('./fsWatcher.js');
|
|
11
|
+
const { version } = require('./package.json');
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Configuration
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
const watcherName = 'dataWatcher';
|
|
18
|
+
|
|
19
|
+
const geoDataDir = resolve(
|
|
20
|
+
__dirname,
|
|
21
|
+
global.geoDataDir || process.env.GEODATADIR || './data/'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const dataFiles = {
|
|
25
|
+
city: join(geoDataDir, 'geoip-city.dat'),
|
|
26
|
+
city6: join(geoDataDir, 'geoip-city6.dat'),
|
|
27
|
+
cityNames: join(geoDataDir, 'geoip-city-names.dat'),
|
|
28
|
+
country: join(geoDataDir, 'geoip-country.dat'),
|
|
29
|
+
country6: join(geoDataDir, 'geoip-country6.dat'),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const privateRange4 = [
|
|
33
|
+
[aton4('10.0.0.0'), aton4('10.255.255.255')],
|
|
34
|
+
[aton4('172.16.0.0'), aton4('172.31.255.255')],
|
|
35
|
+
[aton4('192.168.0.0'), aton4('192.168.255.255')],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Cache Configuration
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
const conf4 = {
|
|
43
|
+
firstIP: null,
|
|
44
|
+
lastIP: null,
|
|
45
|
+
lastLine: 0,
|
|
46
|
+
locationBuffer: null,
|
|
47
|
+
locationRecordSize: 88,
|
|
48
|
+
mainBuffer: null,
|
|
49
|
+
recordSize: 24,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const conf6 = {
|
|
53
|
+
firstIP: null,
|
|
54
|
+
lastIP: null,
|
|
55
|
+
lastLine: 0,
|
|
56
|
+
mainBuffer: null,
|
|
57
|
+
recordSize: 48,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
let cache4 = { ...conf4 };
|
|
61
|
+
let cache6 = { ...conf6 };
|
|
62
|
+
|
|
63
|
+
const RECORD_SIZE = 10;
|
|
64
|
+
const RECORD_SIZE6 = 34;
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Helper Functions
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
const removeNullTerminator = str => {
|
|
71
|
+
const nullIndex = str.indexOf('\0');
|
|
72
|
+
return nullIndex === -1 ? str : str.substring(0, nullIndex);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const readIp6 = (buffer, line, recordSize, offset) => {
|
|
76
|
+
const ipArray = [];
|
|
77
|
+
for (let i = 0; i < 2; i++) {
|
|
78
|
+
ipArray.push(buffer.readUInt32BE((line * recordSize) + (offset * 16) + (i * 4)));
|
|
79
|
+
}
|
|
80
|
+
return ipArray;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// IPv4 Lookup Function
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
const lookup4 = ip => {
|
|
88
|
+
let fline = 0;
|
|
89
|
+
let cline = cache4.lastLine;
|
|
90
|
+
let floor = cache4.lastIP;
|
|
91
|
+
let ceil = cache4.firstIP;
|
|
92
|
+
let line, locId;
|
|
93
|
+
|
|
94
|
+
const buffer = cache4.mainBuffer;
|
|
95
|
+
const locBuffer = cache4.locationBuffer;
|
|
96
|
+
const privateRange = privateRange4;
|
|
97
|
+
const recordSize = cache4.recordSize;
|
|
98
|
+
const locRecordSize = cache4.locationRecordSize;
|
|
99
|
+
|
|
100
|
+
const geoData = {
|
|
101
|
+
range: [null, null],
|
|
102
|
+
country: '',
|
|
103
|
+
region: '',
|
|
104
|
+
eu: '',
|
|
105
|
+
timezone: '',
|
|
106
|
+
city: '',
|
|
107
|
+
ll: [null, null],
|
|
108
|
+
metro: null,
|
|
109
|
+
area: null,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Outside IPv4 range
|
|
113
|
+
if (ip > cache4.lastIP || ip < cache4.firstIP) return null;
|
|
114
|
+
|
|
115
|
+
// Private IP
|
|
116
|
+
for (let i = 0; i < privateRange.length; i++) {
|
|
117
|
+
if (ip >= privateRange[i][0] && ip <= privateRange[i][1]) return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
while (true) {
|
|
121
|
+
line = Math.round((cline - fline) / 2) + fline;
|
|
122
|
+
const offset = line * recordSize;
|
|
123
|
+
floor = buffer.readUInt32BE(offset);
|
|
124
|
+
ceil = buffer.readUInt32BE(offset + 4);
|
|
125
|
+
|
|
126
|
+
if (floor <= ip && ceil >= ip) {
|
|
127
|
+
geoData.range = [floor, ceil];
|
|
128
|
+
|
|
129
|
+
if (recordSize === RECORD_SIZE) {
|
|
130
|
+
geoData.country = buffer.toString('utf8', offset + 8, offset + 10);
|
|
131
|
+
} else {
|
|
132
|
+
locId = buffer.readUInt32BE(offset + 8);
|
|
133
|
+
|
|
134
|
+
// -1>>>0 is a marker for "No Location Info"
|
|
135
|
+
if (-1 >>> 0 > locId) {
|
|
136
|
+
const locOffset = locId * locRecordSize;
|
|
137
|
+
geoData.country = removeNullTerminator(locBuffer.toString('utf8', locOffset, locOffset + 2));
|
|
138
|
+
geoData.region = removeNullTerminator(locBuffer.toString('utf8', locOffset + 2, locOffset + 5));
|
|
139
|
+
geoData.metro = locBuffer.readInt32BE(locOffset + 5);
|
|
140
|
+
geoData.ll[0] = buffer.readInt32BE(offset + 12) / 10000; // latitude
|
|
141
|
+
geoData.ll[1] = buffer.readInt32BE(offset + 16) / 10000; // longitude
|
|
142
|
+
geoData.area = buffer.readUInt32BE(offset + 20);
|
|
143
|
+
geoData.eu = removeNullTerminator(locBuffer.toString('utf8', locOffset + 9, locOffset + 10));
|
|
144
|
+
geoData.timezone = removeNullTerminator(locBuffer.toString('utf8', locOffset + 10, locOffset + 42));
|
|
145
|
+
geoData.city = removeNullTerminator(locBuffer.toString('utf8', locOffset + 42, locOffset + locRecordSize));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return geoData;
|
|
150
|
+
} else if (fline === cline) {
|
|
151
|
+
return null;
|
|
152
|
+
} else if (fline === (cline - 1)) {
|
|
153
|
+
if (line === fline) {
|
|
154
|
+
fline = cline;
|
|
155
|
+
} else {
|
|
156
|
+
cline = fline;
|
|
157
|
+
}
|
|
158
|
+
} else if (floor > ip) {
|
|
159
|
+
cline = line;
|
|
160
|
+
} else if (ceil < ip) {
|
|
161
|
+
fline = line;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// IPv6 Lookup Function
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
const lookup6 = ip => {
|
|
171
|
+
const buffer = cache6.mainBuffer;
|
|
172
|
+
const recordSize = cache6.recordSize;
|
|
173
|
+
const locBuffer = cache4.locationBuffer;
|
|
174
|
+
const locRecordSize = cache4.locationRecordSize;
|
|
175
|
+
|
|
176
|
+
const geoData = {
|
|
177
|
+
range: [null, null],
|
|
178
|
+
country: '',
|
|
179
|
+
region: '',
|
|
180
|
+
eu: '',
|
|
181
|
+
timezone: '',
|
|
182
|
+
city: '',
|
|
183
|
+
ll: [null, null],
|
|
184
|
+
metro: null,
|
|
185
|
+
area: null,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
let fline = 0;
|
|
189
|
+
let cline = cache6.lastLine;
|
|
190
|
+
let floor = cache6.lastIP;
|
|
191
|
+
let ceil = cache6.firstIP;
|
|
192
|
+
let line, locId;
|
|
193
|
+
|
|
194
|
+
if (cmp6(ip, cache6.lastIP) > 0 || cmp6(ip, cache6.firstIP) < 0) return null;
|
|
195
|
+
|
|
196
|
+
while (true) {
|
|
197
|
+
line = Math.round((cline - fline) / 2) + fline;
|
|
198
|
+
floor = readIp6(buffer, line, recordSize, 0);
|
|
199
|
+
ceil = readIp6(buffer, line, recordSize, 1);
|
|
200
|
+
|
|
201
|
+
if (cmp6(floor, ip) <= 0 && cmp6(ceil, ip) >= 0) {
|
|
202
|
+
const offset = line * recordSize;
|
|
203
|
+
if (recordSize === RECORD_SIZE6) {
|
|
204
|
+
geoData.country = removeNullTerminator(buffer.toString('utf8', offset + 32, offset + 34));
|
|
205
|
+
} else {
|
|
206
|
+
locId = buffer.readUInt32BE(offset + 32);
|
|
207
|
+
|
|
208
|
+
// -1>>>0 is a marker for "No Location Info"
|
|
209
|
+
if (-1 >>> 0 > locId) {
|
|
210
|
+
const locOffset = locId * locRecordSize;
|
|
211
|
+
geoData.country = removeNullTerminator(locBuffer.toString('utf8', locOffset, locOffset + 2));
|
|
212
|
+
geoData.region = removeNullTerminator(locBuffer.toString('utf8', locOffset + 2, locOffset + 5));
|
|
213
|
+
geoData.metro = locBuffer.readInt32BE(locOffset + 5);
|
|
214
|
+
geoData.ll[0] = buffer.readInt32BE(offset + 36) / 10000; // latitude
|
|
215
|
+
geoData.ll[1] = buffer.readInt32BE(offset + 40) / 10000; // longitude
|
|
216
|
+
geoData.area = buffer.readUInt32BE(offset + 44); // area
|
|
217
|
+
geoData.eu = removeNullTerminator(locBuffer.toString('utf8', locOffset + 9, locOffset + 10));
|
|
218
|
+
geoData.timezone = removeNullTerminator(locBuffer.toString('utf8', locOffset + 10, locOffset + 42));
|
|
219
|
+
geoData.city = removeNullTerminator(locBuffer.toString('utf8', locOffset + 42, locOffset + locRecordSize));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// We do not currently have detailed region/city info for IPv6, but finally have coords
|
|
223
|
+
return geoData;
|
|
224
|
+
} else if (fline === cline) {
|
|
225
|
+
return null;
|
|
226
|
+
} else if (fline === (cline - 1)) {
|
|
227
|
+
if (line === fline) {
|
|
228
|
+
fline = cline;
|
|
229
|
+
} else {
|
|
230
|
+
cline = fline;
|
|
231
|
+
}
|
|
232
|
+
} else if (cmp6(floor, ip) > 0) {
|
|
233
|
+
cline = line;
|
|
234
|
+
} else if (cmp6(ceil, ip) < 0) {
|
|
235
|
+
fline = line;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// IPv4-Mapped IPv6 Handler
|
|
242
|
+
// ============================================================================
|
|
243
|
+
|
|
244
|
+
const V6_PREFIX_1 = '0:0:0:0:0:FFFF:';
|
|
245
|
+
const V6_PREFIX_2 = '::FFFF:';
|
|
246
|
+
const get4mapped = ip => {
|
|
247
|
+
const ipv6 = ip.toUpperCase();
|
|
248
|
+
if (ipv6.startsWith(V6_PREFIX_1)) return ipv6.substring(V6_PREFIX_1.length);
|
|
249
|
+
if (ipv6.startsWith(V6_PREFIX_2)) return ipv6.substring(V6_PREFIX_2.length);
|
|
250
|
+
return null;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Data Loading Functions - IPv4
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
function preload(callback) {
|
|
258
|
+
let datFile;
|
|
259
|
+
let datSize;
|
|
260
|
+
const asyncCache = { ...conf4 };
|
|
261
|
+
|
|
262
|
+
// When the preload function receives a callback, do the task asynchronously
|
|
263
|
+
if (typeof arguments[0] === 'function') {
|
|
264
|
+
async.series([
|
|
265
|
+
cb => {
|
|
266
|
+
async.series([
|
|
267
|
+
cb2 => {
|
|
268
|
+
open(dataFiles.cityNames, 'r', (err, file) => {
|
|
269
|
+
datFile = file;
|
|
270
|
+
cb2(err);
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
cb2 => {
|
|
274
|
+
fstat(datFile, (err, stats) => {
|
|
275
|
+
datSize = stats.size;
|
|
276
|
+
asyncCache.locationBuffer = Buffer.alloc(datSize);
|
|
277
|
+
cb2(err);
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
cb2 => {
|
|
281
|
+
read(datFile, asyncCache.locationBuffer, 0, datSize, 0, cb2);
|
|
282
|
+
},
|
|
283
|
+
cb2 => {
|
|
284
|
+
close(datFile, cb2);
|
|
285
|
+
},
|
|
286
|
+
cb2 => {
|
|
287
|
+
open(dataFiles.city, 'r', (err, file) => {
|
|
288
|
+
datFile = file;
|
|
289
|
+
cb2(err);
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
cb2 => {
|
|
293
|
+
fstat(datFile, (err, stats) => {
|
|
294
|
+
datSize = stats.size;
|
|
295
|
+
cb2(err);
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
], err => {
|
|
299
|
+
if (err) {
|
|
300
|
+
if (err.code !== 'ENOENT' && err.code !== 'EBADF') {
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
open(dataFiles.country, 'r', (err, file) => {
|
|
305
|
+
if (err) {
|
|
306
|
+
cb(err);
|
|
307
|
+
} else {
|
|
308
|
+
datFile = file;
|
|
309
|
+
fstat(datFile, (err, stats) => {
|
|
310
|
+
datSize = stats.size;
|
|
311
|
+
asyncCache.recordSize = RECORD_SIZE;
|
|
312
|
+
|
|
313
|
+
cb();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
} else {
|
|
319
|
+
cb();
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}, () => {
|
|
323
|
+
asyncCache.mainBuffer = Buffer.alloc(datSize);
|
|
324
|
+
|
|
325
|
+
async.series([
|
|
326
|
+
cb2 => {
|
|
327
|
+
read(datFile, asyncCache.mainBuffer, 0, datSize, 0, cb2);
|
|
328
|
+
},
|
|
329
|
+
cb2 => {
|
|
330
|
+
close(datFile, cb2);
|
|
331
|
+
},
|
|
332
|
+
], err => {
|
|
333
|
+
if (!err) {
|
|
334
|
+
asyncCache.lastLine = (datSize / asyncCache.recordSize) - 1;
|
|
335
|
+
asyncCache.lastIP = asyncCache.mainBuffer.readUInt32BE((asyncCache.lastLine * asyncCache.recordSize) + 4);
|
|
336
|
+
asyncCache.firstIP = asyncCache.mainBuffer.readUInt32BE(0);
|
|
337
|
+
cache4 = asyncCache;
|
|
338
|
+
}
|
|
339
|
+
callback(err);
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
]);
|
|
343
|
+
} else {
|
|
344
|
+
try {
|
|
345
|
+
datFile = openSync(dataFiles.cityNames, 'r');
|
|
346
|
+
datSize = fstatSync(datFile).size;
|
|
347
|
+
if (datSize === 0) {
|
|
348
|
+
const err = new Error('Empty file');
|
|
349
|
+
err.code = 'EMPTY_FILE';
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
cache4.locationBuffer = Buffer.alloc(datSize);
|
|
354
|
+
readSync(datFile, cache4.locationBuffer, 0, datSize, 0);
|
|
355
|
+
closeSync(datFile);
|
|
356
|
+
|
|
357
|
+
datFile = openSync(dataFiles.city, 'r');
|
|
358
|
+
datSize = fstatSync(datFile).size;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
if (err.code !== 'ENOENT' && err.code !== 'EBADF' && err.code !== 'EMPTY_FILE') {
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
datFile = openSync(dataFiles.country, 'r');
|
|
365
|
+
datSize = fstatSync(datFile).size;
|
|
366
|
+
cache4.recordSize = RECORD_SIZE;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
cache4.mainBuffer = Buffer.alloc(datSize);
|
|
370
|
+
readSync(datFile, cache4.mainBuffer, 0, datSize, 0);
|
|
371
|
+
closeSync(datFile);
|
|
372
|
+
|
|
373
|
+
cache4.lastLine = (datSize / cache4.recordSize) - 1;
|
|
374
|
+
cache4.lastIP = cache4.mainBuffer.readUInt32BE((cache4.lastLine * cache4.recordSize) + 4);
|
|
375
|
+
cache4.firstIP = cache4.mainBuffer.readUInt32BE(0);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ============================================================================
|
|
380
|
+
// Data Loading Functions - IPv6
|
|
381
|
+
// ============================================================================
|
|
382
|
+
|
|
383
|
+
function preload6(callback) {
|
|
384
|
+
let datFile;
|
|
385
|
+
let datSize;
|
|
386
|
+
const asyncCache6 = { ...conf6 };
|
|
387
|
+
|
|
388
|
+
// When the preload function receives a callback, do the task asynchronously
|
|
389
|
+
if (typeof arguments[0] === 'function') {
|
|
390
|
+
async.series([
|
|
391
|
+
cb => {
|
|
392
|
+
async.series([
|
|
393
|
+
cb2 => {
|
|
394
|
+
open(dataFiles.city6, 'r', (err, file) => {
|
|
395
|
+
datFile = file;
|
|
396
|
+
cb2(err);
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
cb2 => {
|
|
400
|
+
fstat(datFile, (err, stats) => {
|
|
401
|
+
datSize = stats.size;
|
|
402
|
+
cb2(err);
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
], err => {
|
|
406
|
+
if (err) {
|
|
407
|
+
if (err.code !== 'ENOENT' && err.code !== 'EBADF') {
|
|
408
|
+
throw err;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
open(dataFiles.country6, 'r', (err, file) => {
|
|
412
|
+
if (err) {
|
|
413
|
+
cb(err);
|
|
414
|
+
} else {
|
|
415
|
+
datFile = file;
|
|
416
|
+
fstat(datFile, (err, stats) => {
|
|
417
|
+
datSize = stats.size;
|
|
418
|
+
asyncCache6.recordSize = RECORD_SIZE6;
|
|
419
|
+
|
|
420
|
+
cb();
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
} else {
|
|
425
|
+
cb();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}, () => {
|
|
429
|
+
asyncCache6.mainBuffer = Buffer.alloc(datSize);
|
|
430
|
+
|
|
431
|
+
async.series([
|
|
432
|
+
cb2 => {
|
|
433
|
+
read(datFile, asyncCache6.mainBuffer, 0, datSize, 0, cb2);
|
|
434
|
+
},
|
|
435
|
+
cb2 => {
|
|
436
|
+
close(datFile, cb2);
|
|
437
|
+
},
|
|
438
|
+
], err => {
|
|
439
|
+
if (!err) {
|
|
440
|
+
asyncCache6.lastLine = (datSize / asyncCache6.recordSize) - 1;
|
|
441
|
+
asyncCache6.lastIP = readIp6(asyncCache6.mainBuffer, asyncCache6.lastLine, asyncCache6.recordSize, 1);
|
|
442
|
+
asyncCache6.firstIP = readIp6(asyncCache6.mainBuffer, 0, asyncCache6.recordSize, 0);
|
|
443
|
+
cache6 = asyncCache6;
|
|
444
|
+
}
|
|
445
|
+
callback(err);
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
} else {
|
|
450
|
+
try {
|
|
451
|
+
datFile = openSync(dataFiles.city6, 'r');
|
|
452
|
+
datSize = fstatSync(datFile).size;
|
|
453
|
+
|
|
454
|
+
if (datSize === 0) {
|
|
455
|
+
const err = new Error('Empty file');
|
|
456
|
+
err.code = 'EMPTY_FILE';
|
|
457
|
+
throw err;
|
|
458
|
+
}
|
|
459
|
+
} catch (err) {
|
|
460
|
+
if (err.code !== 'ENOENT' && err.code !== 'EBADF' && err.code !== 'EMPTY_FILE') {
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
datFile = openSync(dataFiles.country6, 'r');
|
|
465
|
+
datSize = fstatSync(datFile).size;
|
|
466
|
+
cache6.recordSize = RECORD_SIZE6;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
cache6.mainBuffer = Buffer.alloc(datSize);
|
|
470
|
+
readSync(datFile, cache6.mainBuffer, 0, datSize, 0);
|
|
471
|
+
closeSync(datFile);
|
|
472
|
+
|
|
473
|
+
cache6.lastLine = (datSize / cache6.recordSize) - 1;
|
|
474
|
+
cache6.lastIP = readIp6(cache6.mainBuffer, cache6.lastLine, cache6.recordSize, 1);
|
|
475
|
+
cache6.firstIP = readIp6(cache6.mainBuffer, 0, cache6.recordSize, 0);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ============================================================================
|
|
480
|
+
// Public API
|
|
481
|
+
// ============================================================================
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
cmp,
|
|
485
|
+
|
|
486
|
+
lookup: ip => {
|
|
487
|
+
if (!ip) {
|
|
488
|
+
return null;
|
|
489
|
+
} else if (typeof ip === 'number') {
|
|
490
|
+
return lookup4(ip);
|
|
491
|
+
} else if (isIP(ip) === 4) {
|
|
492
|
+
return lookup4(aton4(ip));
|
|
493
|
+
} else if (isIP(ip) === 6) {
|
|
494
|
+
const ipv4 = get4mapped(ip);
|
|
495
|
+
if (ipv4) {
|
|
496
|
+
return lookup4(aton4(ipv4));
|
|
497
|
+
} else {
|
|
498
|
+
return lookup6(aton6(ip));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return null;
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
pretty: n => {
|
|
506
|
+
if (typeof n === 'string') {
|
|
507
|
+
return n;
|
|
508
|
+
} else if (typeof n === 'number') {
|
|
509
|
+
return ntoa4(n);
|
|
510
|
+
} else if (Array.isArray(n)) {
|
|
511
|
+
return ntoa6(n);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return n;
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
// Start watching for data updates. The watcher waits one minute for file transfer to
|
|
518
|
+
// complete before triggering the callback.
|
|
519
|
+
startWatchingDataUpdate: callback => {
|
|
520
|
+
fsWatcher.makeFsWatchFilter(watcherName, geoDataDir, 60 * 1000, () => {
|
|
521
|
+
// Reload data
|
|
522
|
+
async.series([
|
|
523
|
+
cb => {
|
|
524
|
+
preload(cb);
|
|
525
|
+
}, cb => {
|
|
526
|
+
preload6(cb);
|
|
527
|
+
},
|
|
528
|
+
], callback);
|
|
529
|
+
});
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
// Stop watching for data updates.
|
|
533
|
+
stopWatchingDataUpdate: () => fsWatcher.stopWatching(watcherName),
|
|
534
|
+
|
|
535
|
+
// Clear data
|
|
536
|
+
clear: () => {
|
|
537
|
+
cache4 = { ...conf4 };
|
|
538
|
+
cache6 = { ...conf6 };
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
// Reload data synchronously
|
|
542
|
+
reloadDataSync: () => {
|
|
543
|
+
preload();
|
|
544
|
+
preload6();
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
// Reload data asynchronously
|
|
548
|
+
reloadData: callback => {
|
|
549
|
+
// Reload data
|
|
550
|
+
async.series([
|
|
551
|
+
cb => {
|
|
552
|
+
preload(cb);
|
|
553
|
+
},
|
|
554
|
+
cb => {
|
|
555
|
+
preload6(cb);
|
|
556
|
+
},
|
|
557
|
+
], callback);
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
version,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// ============================================================================
|
|
564
|
+
// Initialize - Load data on module startup
|
|
565
|
+
// ============================================================================
|
|
566
|
+
|
|
567
|
+
preload();
|
|
568
|
+
preload6();
|
|
569
|
+
|
|
570
|
+
// lookup4 = gen_lookup('geoip-country.dat', 4);
|
|
571
|
+
// lookup6 = gen_lookup('geoip-country6.dat', 16);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geoip-lite2",
|
|
3
|
-
"version": "2.2.8-beta.
|
|
3
|
+
"version": "2.2.8-beta.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,7 +52,7 @@
|
|
|
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": "index.js",
|
|
56
56
|
"types": "index.d.ts",
|
|
57
57
|
"directories": {
|
|
58
58
|
"lib": "lib",
|
|
@@ -60,15 +60,18 @@
|
|
|
60
60
|
},
|
|
61
61
|
"files": [
|
|
62
62
|
"data",
|
|
63
|
-
"dist",
|
|
64
63
|
"tools",
|
|
65
64
|
"AUTHORS",
|
|
65
|
+
"fsWatcher.js",
|
|
66
66
|
"index.d.ts",
|
|
67
|
+
"index.js",
|
|
67
68
|
"LICENSE",
|
|
68
|
-
"README.md"
|
|
69
|
+
"README.md",
|
|
70
|
+
"utils.js"
|
|
69
71
|
],
|
|
70
72
|
"scripts": {
|
|
71
|
-
"
|
|
73
|
+
"lint": "eslint .",
|
|
74
|
+
"lint:fix": "eslint . --fix",
|
|
72
75
|
"test": "jest test",
|
|
73
76
|
"up": "ncu -u && npm install && npm update && npm audit fix",
|
|
74
77
|
"updatedb": "node tools/updatedb.js",
|
|
@@ -80,15 +83,14 @@
|
|
|
80
83
|
"async": "^3.2.6",
|
|
81
84
|
"iconv-lite": "0.7.0",
|
|
82
85
|
"ip-address": "^10.1.0",
|
|
83
|
-
"rimraf": "^6.1.
|
|
86
|
+
"rimraf": "^6.1.2"
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
86
89
|
"@eslint/js": "^9.39.1",
|
|
87
90
|
"globals": "^16.5.0",
|
|
88
|
-
"jest": "^30.2.0"
|
|
89
|
-
"terser": "^5.44.1"
|
|
91
|
+
"jest": "^30.2.0"
|
|
90
92
|
},
|
|
91
93
|
"engines": {
|
|
92
|
-
"node": ">=
|
|
94
|
+
"node": ">=20.0.0"
|
|
93
95
|
}
|
|
94
96
|
}
|
package/tools/updatedb.js
CHANGED
|
@@ -23,7 +23,7 @@ const async = require('async');
|
|
|
23
23
|
const { decodeStream } = require('iconv-lite');
|
|
24
24
|
const rimraf = require('rimraf').sync;
|
|
25
25
|
const AdmZip = require('adm-zip');
|
|
26
|
-
const utils = require('../
|
|
26
|
+
const utils = require('../utils.js');
|
|
27
27
|
const { Address6, Address4 } = require('ip-address');
|
|
28
28
|
|
|
29
29
|
// ============================================================================
|
|
@@ -32,12 +32,10 @@ const { Address6, Address4 } = require('ip-address');
|
|
|
32
32
|
|
|
33
33
|
// Logging utility for consistent and readable output
|
|
34
34
|
const log = {
|
|
35
|
-
info: (msg, ...logArgs) => console.log(
|
|
36
|
-
success: (msg, ...logArgs) => console.log(
|
|
37
|
-
warn: (msg, ...logArgs) => console.warn(
|
|
38
|
-
error: (msg, ...logArgs) => console.error(
|
|
39
|
-
progress: (msg) => process.stdout.write(`[INFO] ${msg}... `),
|
|
40
|
-
done: () => console.log('Done'),
|
|
35
|
+
info: (msg, ...logArgs) => console.log(`[INFO] ${msg}`, ...logArgs),
|
|
36
|
+
success: (msg, ...logArgs) => console.log(`[SUCCESS] ${msg}`, ...logArgs),
|
|
37
|
+
warn: (msg, ...logArgs) => console.warn(`[WARN] ${msg}`, ...logArgs),
|
|
38
|
+
error: (msg, ...logArgs) => console.error(`[ERROR] ${msg}`, ...logArgs),
|
|
41
39
|
};
|
|
42
40
|
|
|
43
41
|
// ============================================================================
|
|
@@ -114,8 +112,8 @@ function tryFixingLine(line) {
|
|
|
114
112
|
pos1 = pos2;
|
|
115
113
|
pos2 = line.indexOf(',', pos1 + 1);
|
|
116
114
|
if (pos2 < 0) pos2 = line.length;
|
|
117
|
-
if (line.indexOf('\'', (pos1 || 0)) > -1 && line.indexOf('\'', pos1) < pos2 && line[pos1 + 1]
|
|
118
|
-
line = line.
|
|
115
|
+
if (line.indexOf('\'', (pos1 || 0)) > -1 && line.indexOf('\'', pos1) < pos2 && line[pos1 + 1] !== '"' && line[pos2 - 1] !== '"') {
|
|
116
|
+
line = line.substring(0, pos1 + 1) + '"' + line.substring(pos1 + 1, pos2) + '"' + line.substring(pos2);
|
|
119
117
|
pos2 = line.indexOf(',', pos2 + 1);
|
|
120
118
|
if (pos2 < 0) pos2 = line.length;
|
|
121
119
|
}
|
|
@@ -267,14 +265,12 @@ function fetch(database, cb) {
|
|
|
267
265
|
}
|
|
268
266
|
|
|
269
267
|
tmpFilePipe.on('close', () => {
|
|
270
|
-
log.
|
|
268
|
+
log.info(`Retrieved ${fileName}`);
|
|
271
269
|
cb(null, tmpFile, fileName, database);
|
|
272
270
|
});
|
|
273
271
|
}
|
|
274
272
|
|
|
275
273
|
mkdir(tmpFile);
|
|
276
|
-
|
|
277
|
-
log.progress(`Retrieving ${fileName}`);
|
|
278
274
|
}
|
|
279
275
|
|
|
280
276
|
function extract(tmpFile, tmpFileName, database, cb) {
|
|
@@ -283,7 +279,7 @@ function extract(tmpFile, tmpFileName, database, cb) {
|
|
|
283
279
|
if (path.extname(tmpFileName) !== '.zip') {
|
|
284
280
|
cb(null, database);
|
|
285
281
|
} else {
|
|
286
|
-
log.
|
|
282
|
+
log.info('Extracting ' + tmpFileName);
|
|
287
283
|
const zip = new AdmZip(tmpFile);
|
|
288
284
|
const zipEntries = zip.getEntries();
|
|
289
285
|
|
|
@@ -297,7 +293,7 @@ function extract(tmpFile, tmpFileName, database, cb) {
|
|
|
297
293
|
fs.writeFileSync(destinationPath, entry.getData());
|
|
298
294
|
});
|
|
299
295
|
|
|
300
|
-
log.
|
|
296
|
+
log.info('Extracted ' + tmpFileName);
|
|
301
297
|
cb(null, database);
|
|
302
298
|
}
|
|
303
299
|
}
|
|
@@ -313,7 +309,7 @@ function processLookupCountry(src, cb) {
|
|
|
313
309
|
}
|
|
314
310
|
const tmpDataFile = path.join(tmpPath, src);
|
|
315
311
|
|
|
316
|
-
log.
|
|
312
|
+
log.info('Processing lookup data');
|
|
317
313
|
|
|
318
314
|
const rl = readline.createInterface({ input: fs.createReadStream(tmpDataFile).pipe(decodeStream('latin1')), output: process.stdout, terminal: false });
|
|
319
315
|
|
|
@@ -324,7 +320,7 @@ function processLookupCountry(src, cb) {
|
|
|
324
320
|
});
|
|
325
321
|
|
|
326
322
|
rl.on('close', () => {
|
|
327
|
-
log.
|
|
323
|
+
log.info('Processed lookup data');
|
|
328
324
|
cb();
|
|
329
325
|
});
|
|
330
326
|
}
|
|
@@ -341,8 +337,7 @@ async function processCountryData(src, dest) {
|
|
|
341
337
|
rimraf(dataFile);
|
|
342
338
|
mkdir(dataFile);
|
|
343
339
|
|
|
344
|
-
|
|
345
|
-
log.progress('Processing country data (this may take a moment)');
|
|
340
|
+
log.info('Processing country data');
|
|
346
341
|
let tstart = Date.now();
|
|
347
342
|
const datFile = fs.createWriteStream(dataFile);
|
|
348
343
|
|
|
@@ -380,8 +375,8 @@ async function processCountryData(src, dest) {
|
|
|
380
375
|
bsz = 10;
|
|
381
376
|
|
|
382
377
|
rngip = new Address4(fields[0]);
|
|
383
|
-
sip = parseInt(rngip.startAddress().bigInt(), 10);
|
|
384
|
-
eip = parseInt(rngip.endAddress().bigInt(), 10);
|
|
378
|
+
sip = parseInt(rngip.startAddress().bigInt().toString(), 10);
|
|
379
|
+
eip = parseInt(rngip.endAddress().bigInt().toString(), 10);
|
|
385
380
|
|
|
386
381
|
b = Buffer.alloc(bsz);
|
|
387
382
|
b.fill(0);
|
|
@@ -392,7 +387,7 @@ async function processCountryData(src, dest) {
|
|
|
392
387
|
b.write(cc, bsz - 2);
|
|
393
388
|
if (Date.now() - tstart > 5000) {
|
|
394
389
|
tstart = Date.now();
|
|
395
|
-
|
|
390
|
+
log.info(`Processing country data (${lines} entries)`);
|
|
396
391
|
}
|
|
397
392
|
|
|
398
393
|
if (datFile._writableState.needDrain) {
|
|
@@ -405,15 +400,53 @@ async function processCountryData(src, dest) {
|
|
|
405
400
|
}
|
|
406
401
|
}
|
|
407
402
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
i
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
403
|
+
await new Promise((resolve, reject) => {
|
|
404
|
+
const rl = readline.createInterface({ input: fs.createReadStream(tmpDataFile), crlfDelay: Infinity });
|
|
405
|
+
let settled = false;
|
|
406
|
+
let i = 0;
|
|
407
|
+
|
|
408
|
+
function finish(err) {
|
|
409
|
+
if (settled) return;
|
|
410
|
+
settled = true;
|
|
411
|
+
if (!rl.closed) rl.close();
|
|
412
|
+
if (err) reject(err);
|
|
413
|
+
else resolve();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function resume() {
|
|
417
|
+
if (!settled && !rl.closed) rl.resume();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
rl.on('line', line => {
|
|
421
|
+
rl.pause();
|
|
422
|
+
i++;
|
|
423
|
+
if (i === 1) {
|
|
424
|
+
resume();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
let result;
|
|
429
|
+
try {
|
|
430
|
+
result = processLine(line);
|
|
431
|
+
} catch (err) {
|
|
432
|
+
finish(err);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (result && typeof result.then === 'function') {
|
|
437
|
+
result.then(() => {
|
|
438
|
+
resume();
|
|
439
|
+
}).catch(finish);
|
|
440
|
+
} else {
|
|
441
|
+
resume();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
rl.on('close', () => finish());
|
|
446
|
+
rl.on('error', finish);
|
|
447
|
+
});
|
|
415
448
|
datFile.close();
|
|
416
|
-
log.
|
|
449
|
+
log.info('Processed country data');
|
|
417
450
|
}
|
|
418
451
|
|
|
419
452
|
async function processCityData(src, dest) {
|
|
@@ -423,8 +456,7 @@ async function processCityData(src, dest) {
|
|
|
423
456
|
|
|
424
457
|
rimraf(dataFile);
|
|
425
458
|
|
|
426
|
-
|
|
427
|
-
log.progress('Processing city data (this may take a moment)');
|
|
459
|
+
log.info('Processing city data');
|
|
428
460
|
let tstart = Date.now();
|
|
429
461
|
const datFile = fs.createWriteStream(dataFile);
|
|
430
462
|
|
|
@@ -482,8 +514,8 @@ async function processCityData(src, dest) {
|
|
|
482
514
|
bsz = 24;
|
|
483
515
|
|
|
484
516
|
rngip = new Address4(fields[0]);
|
|
485
|
-
sip = parseInt(rngip.startAddress().bigInt(), 10);
|
|
486
|
-
eip = parseInt(rngip.endAddress().bigInt(), 10);
|
|
517
|
+
sip = parseInt(rngip.startAddress().bigInt().toString(), 10);
|
|
518
|
+
eip = parseInt(rngip.endAddress().bigInt().toString(), 10);
|
|
487
519
|
locId = parseInt(fields[1], 10);
|
|
488
520
|
locId = cityLookup[locId];
|
|
489
521
|
b = Buffer.alloc(bsz);
|
|
@@ -502,7 +534,7 @@ async function processCityData(src, dest) {
|
|
|
502
534
|
|
|
503
535
|
if (Date.now() - tstart > 5000) {
|
|
504
536
|
tstart = Date.now();
|
|
505
|
-
|
|
537
|
+
log.info(`Processing city data (${lines} entries)`);
|
|
506
538
|
}
|
|
507
539
|
|
|
508
540
|
if (datFile._writableState.needDrain) {
|
|
@@ -514,13 +546,51 @@ async function processCityData(src, dest) {
|
|
|
514
546
|
}
|
|
515
547
|
}
|
|
516
548
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
i
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
549
|
+
await new Promise((resolve, reject) => {
|
|
550
|
+
const rl = readline.createInterface({ input: fs.createReadStream(tmpDataFile), crlfDelay: Infinity });
|
|
551
|
+
let settled = false;
|
|
552
|
+
let i = 0;
|
|
553
|
+
|
|
554
|
+
function finish(err) {
|
|
555
|
+
if (settled) return;
|
|
556
|
+
settled = true;
|
|
557
|
+
if (!rl.closed) rl.close();
|
|
558
|
+
if (err) reject(err);
|
|
559
|
+
else resolve();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function resume() {
|
|
563
|
+
if (!settled && !rl.closed) rl.resume();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
rl.on('line', line => {
|
|
567
|
+
rl.pause();
|
|
568
|
+
i++;
|
|
569
|
+
if (i === 1) {
|
|
570
|
+
resume();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
let result;
|
|
575
|
+
try {
|
|
576
|
+
result = processLine(line);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
finish(err);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (result && typeof result.then === 'function') {
|
|
583
|
+
result.then(() => {
|
|
584
|
+
resume();
|
|
585
|
+
}).catch(finish);
|
|
586
|
+
} else {
|
|
587
|
+
resume();
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
rl.on('close', () => finish());
|
|
592
|
+
rl.on('error', finish);
|
|
593
|
+
});
|
|
524
594
|
datFile.close();
|
|
525
595
|
}
|
|
526
596
|
|
|
@@ -610,11 +680,10 @@ function processData(database, cb) {
|
|
|
610
680
|
} else if (type === 'city') {
|
|
611
681
|
processCityDataNames(src[0], dest[0], () => {
|
|
612
682
|
processCityData(src[1], dest[1]).then(() => {
|
|
613
|
-
|
|
614
|
-
log.info('City IPv4 data processed');
|
|
683
|
+
log.info('Processed city IPv4 data');
|
|
615
684
|
return processCityData(src[2], dest[2]);
|
|
616
685
|
}).then(() => {
|
|
617
|
-
log.info('
|
|
686
|
+
log.info('Processed city IPv6 data');
|
|
618
687
|
cb(null, database);
|
|
619
688
|
});
|
|
620
689
|
});
|
|
@@ -653,10 +722,9 @@ async.eachSeries(databases, (database, nextDatabase) => {
|
|
|
653
722
|
log.error('Failed to update databases from MaxMind!', err);
|
|
654
723
|
process.exit(1);
|
|
655
724
|
} else {
|
|
656
|
-
process.stdout.write('\n');
|
|
657
725
|
log.success('All databases have been successfully updated from MaxMind');
|
|
658
726
|
if (args.indexOf('debug') !== -1) {
|
|
659
|
-
|
|
727
|
+
log.info('Debug mode: temporary files preserved at ' + tmpPath);
|
|
660
728
|
} else {
|
|
661
729
|
rimraf(tmpPath);
|
|
662
730
|
}
|
package/utils.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Utility Functions for IP Address Conversion and Comparison
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
const utils = module.exports = {};
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// IPv4 Conversion Functions
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
utils.aton4 = a => {
|
|
12
|
+
const parts = a.split('.');
|
|
13
|
+
return ((parseInt(parts[0], 10) << 24) >>> 0) + ((parseInt(parts[1], 10) << 16) >>> 0) + ((parseInt(parts[2], 10) << 8) >>> 0) + (parseInt(parts[3], 10) >>> 0);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
utils.ntoa4 = n => {
|
|
17
|
+
return ((n >>> 24) & 0xff) + '.' + ((n >>> 16) & 0xff) + '.' + ((n >>> 8) & 0xff) + '.' + (n & 0xff);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// IPv6 Conversion Functions
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
utils.aton6 = a => {
|
|
25
|
+
a = a.replace(/"/g, '').split(':');
|
|
26
|
+
|
|
27
|
+
const l = a.length - 1;
|
|
28
|
+
let i;
|
|
29
|
+
|
|
30
|
+
if (a[l] === '') {
|
|
31
|
+
a[l] = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (l < 7) {
|
|
35
|
+
a.length = 8;
|
|
36
|
+
|
|
37
|
+
for (i = l; i >= 0 && a[i] !== ''; i--) {
|
|
38
|
+
a[7 - l + i] = a[i];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (i = 0; i < 8; i++) {
|
|
43
|
+
if (!a[i]) {
|
|
44
|
+
a[i] = 0;
|
|
45
|
+
} else {
|
|
46
|
+
a[i] = parseInt(a[i], 16);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const r = [];
|
|
51
|
+
for (i = 0; i < 4; i++) {
|
|
52
|
+
r.push(((a[2 * i] << 16) + a[2 * i + 1]) >>> 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return r;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
utils.ntoa6 = n => {
|
|
59
|
+
let a = '[';
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < n.length; i++) {
|
|
62
|
+
a += (n[i] >>> 16).toString(16) + ':';
|
|
63
|
+
a += (n[i] & 0xffff).toString(16) + ':';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
a = a.replace(/:$/, ']').replace(/:0+/g, ':').replace(/::+/, '::');
|
|
67
|
+
return a;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Comparison Functions
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
utils.cmp = (a, b) => {
|
|
75
|
+
if (typeof a === 'number' && typeof b === 'number') return (a < b ? -1 : (a > b ? 1 : 0));
|
|
76
|
+
if (a instanceof Array && b instanceof Array) return utils.cmp6(a, b);
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
utils.cmp6 = (a, b) => {
|
|
82
|
+
for (let ii = 0; ii < 2; ii++) {
|
|
83
|
+
if (a[ii] < b[ii]) return -1;
|
|
84
|
+
if (a[ii] > b[ii]) return 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return 0;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// IP Address Validation
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
utils.isPrivateIP = addr => {
|
|
95
|
+
const str = addr.toString();
|
|
96
|
+
return str.startsWith('10.') ||
|
|
97
|
+
str.startsWith('192.168.') ||
|
|
98
|
+
str.startsWith('172.16.') ||
|
|
99
|
+
str.startsWith('127.') ||
|
|
100
|
+
str.startsWith('169.254.') ||
|
|
101
|
+
str.startsWith('fc00:') ||
|
|
102
|
+
str.startsWith('fe80:');
|
|
103
|
+
};
|
package/dist/fsWatcher.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
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
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const{open:e,fstat:r,read:t,close:n,openSync:i,fstatSync:o,readSync:a,closeSync:l}=require('node:fs'),{join:f,resolve:u}=require('node:path'),{isIP:c}=require('node:net'),s=require('async'),{aton4:d,aton6:B,cmp6:y,ntoa4:I,ntoa6:m,cmp:E}=require('./utils.js'),S=require('./fsWatcher.js'),{version:g}=require('../package.json'),z='dataWatcher',p=u(__dirname,global.geoDataDir||process.env.GEODATADIR||'../data/'),P={city:f(p,'geoip-city.dat'),city6:f(p,'geoip-city6.dat'),cityNames:f(p,'geoip-city-names.dat'),country:f(p,'geoip-country.dat'),country6:f(p,'geoip-country6.dat')},h=[[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')]],F={firstIP:null,lastIP:null,lastLine:0,locationBuffer:null,locationRecordSize:88,mainBuffer:null,recordSize:24},L={firstIP:null,lastIP:null,lastLine:0,mainBuffer:null,recordSize:48};let U={...F},D={...L};const N=e=>{const r=e.indexOf('\0');return-1===r?e:e.substring(0,r)},T=(e,r,t,n)=>{const i=[];for(let o=0;o<2;o++)i.push(e.readUInt32BE(r*t+16*n+4*o));return i},w=e=>{let r,t,n=0,i=U.lastLine,o=U.lastIP,a=U.firstIP;const l=U.mainBuffer,f=U.locationBuffer,u=h,c=U.recordSize,s=U.locationRecordSize,d={range:[null,null],country:'',region:'',eu:'',timezone:'',city:'',ll:[null,null],metro:null,area:null};if(e>U.lastIP||e<U.firstIP)return null;for(let r=0;r<u.length;r++)if(e>=u[r][0]&&e<=u[r][1])return null;for(;;){r=Math.round((i-n)/2)+n;const u=r*c;if(o=l.readUInt32BE(u),a=l.readUInt32BE(u+4),o<=e&&a>=e){if(d.range=[o,a],10===c)d.country=l.toString('utf8',u+8,u+10);else if(t=l.readUInt32BE(u+8),-1>>>0>t){const e=t*s;d.country=N(f.toString('utf8',e,e+2)),d.region=N(f.toString('utf8',e+2,e+5)),d.metro=f.readInt32BE(e+5),d.ll[0]=l.readInt32BE(u+12)/1e4,d.ll[1]=l.readInt32BE(u+16)/1e4,d.area=l.readUInt32BE(u+20),d.eu=N(f.toString('utf8',e+9,e+10)),d.timezone=N(f.toString('utf8',e+10,e+42)),d.city=N(f.toString('utf8',e+42,e+s))}return d}if(n===i)return null;n===i-1?r===n?n=i:i=n:o>e?i=r:a<e&&(n=r)}};function A(f){let u,c;const d={...F};if('function'==typeof arguments[0])s.series([i=>{s.series([r=>{e(P.cityNames,'r',(e,t)=>{u=t,r(e)})},e=>{r(u,(r,t)=>{c=t.size,d.locationBuffer=Buffer.alloc(c),e(r)})},e=>{t(u,d.locationBuffer,0,c,0,e)},e=>{n(u,e)},r=>{e(P.city,'r',(e,t)=>{u=t,r(e)})},e=>{r(u,(r,t)=>{c=t.size,e(r)})}],t=>{if(t){if('ENOENT'!==t.code&&'EBADF'!==t.code)throw t;e(P.country,'r',(e,t)=>{e?i(e):(u=t,r(u,(e,r)=>{c=r.size,d.recordSize=10,i()}))})}else i()})},()=>{d.mainBuffer=Buffer.alloc(c),s.series([e=>{t(u,d.mainBuffer,0,c,0,e)},e=>{n(u,e)}],e=>{e||(d.lastLine=c/d.recordSize-1,d.lastIP=d.mainBuffer.readUInt32BE(d.lastLine*d.recordSize+4),d.firstIP=d.mainBuffer.readUInt32BE(0),U=d),f(e)})}]);else{try{if(u=i(P.cityNames,'r'),c=o(u).size,0===c){const e=new Error('Empty file');throw e.code='EMPTY_FILE',e}U.locationBuffer=Buffer.alloc(c),a(u,U.locationBuffer,0,c,0),l(u),u=i(P.city,'r'),c=o(u).size}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;u=i(P.country,'r'),c=o(u).size,U.recordSize=10}U.mainBuffer=Buffer.alloc(c),a(u,U.mainBuffer,0,c,0),l(u),U.lastLine=c/U.recordSize-1,U.lastIP=U.mainBuffer.readUInt32BE(U.lastLine*U.recordSize+4),U.firstIP=U.mainBuffer.readUInt32BE(0)}}function W(f){let u,c;const d={...L};if('function'==typeof arguments[0])s.series([t=>{s.series([r=>{e(P.city6,'r',(e,t)=>{u=t,r(e)})},e=>{r(u,(r,t)=>{c=t.size,e(r)})}],n=>{if(n){if('ENOENT'!==n.code&&'EBADF'!==n.code)throw n;e(P.country6,'r',(e,n)=>{e?t(e):(u=n,r(u,(e,r)=>{c=r.size,d.recordSize=34,t()}))})}else t()})},()=>{d.mainBuffer=Buffer.alloc(c),s.series([e=>{t(u,d.mainBuffer,0,c,0,e)},e=>{n(u,e)}],e=>{e||(d.lastLine=c/d.recordSize-1,d.lastIP=T(d.mainBuffer,d.lastLine,d.recordSize,1),d.firstIP=T(d.mainBuffer,0,d.recordSize,0),D=d),f(e)})}]);else{try{if(u=i(P.city6,'r'),c=o(u).size,0===c){const e=new Error('Empty file');throw e.code='EMPTY_FILE',e}}catch(e){if('ENOENT'!==e.code&&'EBADF'!==e.code&&'EMPTY_FILE'!==e.code)throw e;u=i(P.country6,'r'),c=o(u).size,D.recordSize=34}D.mainBuffer=Buffer.alloc(c),a(u,D.mainBuffer,0,c,0),l(u),D.lastLine=c/D.recordSize-1,D.lastIP=T(D.mainBuffer,D.lastLine,D.recordSize,1),D.firstIP=T(D.mainBuffer,0,D.recordSize,0)}}module.exports={cmp:E,lookup:e=>{if(!e)return null;if('number'==typeof e)return w(e);if(4===c(e))return w(d(e));if(6===c(e)){const r=(e=>{const r=e.toUpperCase();return r.startsWith("0:0:0:0:0:FFFF:")?r.substring(15):r.startsWith("::FFFF:")?r.substring(7):null})(e);return r?w(d(r)):(e=>{const r=D.mainBuffer,t=D.recordSize,n=U.locationBuffer,i=U.locationRecordSize,o={range:[null,null],country:'',region:'',eu:'',timezone:'',city:'',ll:[null,null],metro:null,area:null};let a,l,f=0,u=D.lastLine,c=D.lastIP,s=D.firstIP;if(y(e,D.lastIP)>0||y(e,D.firstIP)<0)return null;for(;;){if(a=Math.round((u-f)/2)+f,c=T(r,a,t,0),s=T(r,a,t,1),y(c,e)<=0&&y(s,e)>=0){const e=a*t;if(34===t)o.country=N(r.toString('utf8',e+32,e+34));else if(l=r.readUInt32BE(e+32),-1>>>0>l){const t=l*i;o.country=N(n.toString('utf8',t,t+2)),o.region=N(n.toString('utf8',t+2,t+5)),o.metro=n.readInt32BE(t+5),o.ll[0]=r.readInt32BE(e+36)/1e4,o.ll[1]=r.readInt32BE(e+40)/1e4,o.area=r.readUInt32BE(e+44),o.eu=N(n.toString('utf8',t+9,t+10)),o.timezone=N(n.toString('utf8',t+10,t+42)),o.city=N(n.toString('utf8',t+42,t+i))}return o}if(f===u)return null;f===u-1?a===f?f=u:u=f:y(c,e)>0?u=a:y(s,e)<0&&(f=a)}})(B(e))}return null},pretty:e=>'string'==typeof e?e:'number'==typeof e?I(e):Array.isArray(e)?m(e):e,startWatchingDataUpdate:e=>{S.makeFsWatchFilter(z,p,6e4,()=>{s.series([e=>{A(e)},e=>{W(e)}],e)})},stopWatchingDataUpdate:()=>S.stopWatching(z),clear:()=>{U={...F},D={...L}},reloadDataSync:()=>{A(),W()},reloadData:e=>{s.series([e=>{A(e)},e=>{W(e)}],e)},version:g},A(),W();
|
package/dist/utils.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const t=module.exports={};t.aton4=t=>{const r=t.split('.');return(parseInt(r[0],10)<<24>>>0)+(parseInt(r[1],10)<<16>>>0)+(parseInt(r[2],10)<<8>>>0)+(parseInt(r[3],10)>>>0)},t.ntoa4=t=>(t>>>24&255)+'.'+(t>>>16&255)+'.'+(t>>>8&255)+'.'+(255&t),t.aton6=t=>{const r=(t=t.replace(/"/g,'').split(':')).length-1;let e;if(''===t[r]&&(t[r]=0),r<7)for(t.length=8,e=r;e>=0&&''!==t[e];e--)t[7-r+e]=t[e];for(e=0;e<8;e++)t[e]?t[e]=parseInt(t[e],16):t[e]=0;const n=[];for(e=0;e<4;e++)n.push((t[2*e]<<16)+t[2*e+1]>>>0);return n},t.ntoa6=t=>{let r='[';for(let e=0;e<t.length;e++)r+=(t[e]>>>16).toString(16)+':',r+=(65535&t[e]).toString(16)+':';return r=r.replace(/:$/,']').replace(/:0+/g,':').replace(/::+/,'::'),r},t.cmp=(r,e)=>'number'==typeof r&&'number'==typeof e?r<e?-1:r>e?1:0:r instanceof Array&&e instanceof Array?t.cmp6(r,e):null,t.cmp6=(t,r)=>{for(let e=0;e<2;e++){if(t[e]<r[e])return-1;if(t[e]>r[e])return 1}return 0},t.isPrivateIP=t=>{const r=t.toString();return r.startsWith('10.')||r.startsWith('192.168.')||r.startsWith('172.16.')||r.startsWith('127.')||r.startsWith('169.254.')||r.startsWith('fc00:')||r.startsWith('fe80:')};
|