geoip-lite2 2.2.8-beta.6 → 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.
@@ -1 +1 @@
1
- e6e3de413a870dd57e1801fbf3f3123c0798aee81c6c01dad9da53fcb023bc00 GeoLite2-City-CSV_20251121.zip
1
+ 1e2d74273ff207a2a7d129a195b300dbc45b22a4358cd3e45de17ae47ccab4f8 GeoLite2-City-CSV_20251128.zip
@@ -1 +1 @@
1
- 098468175e0cecd62499b0430c8876e412471df27c32f8c7a2618b8e90025708 GeoLite2-Country-CSV_20251121.zip
1
+ 67ac2aeddc82fc748089eaf5b01edf2ce0895c0c383a4a3f6812270a93bb3333 GeoLite2-Country-CSV_20251128.zip
Binary file
Binary file
Binary file
Binary file
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.6",
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": "dist/main.js",
55
+ "main": "index.js",
56
56
  "types": "index.d.ts",
57
57
  "directories": {
58
58
  "lib": "lib",
@@ -60,17 +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 .",
72
74
  "lint:fix": "eslint . --fix",
73
- "minify": "node minify.js",
74
75
  "test": "jest test",
75
76
  "up": "ncu -u && npm install && npm update && npm audit fix",
76
77
  "updatedb": "node tools/updatedb.js",
@@ -87,8 +88,7 @@
87
88
  "devDependencies": {
88
89
  "@eslint/js": "^9.39.1",
89
90
  "globals": "^16.5.0",
90
- "jest": "^30.2.0",
91
- "terser": "^5.44.1"
91
+ "jest": "^30.2.0"
92
92
  },
93
93
  "engines": {
94
94
  "node": ">=20.0.0"
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('../lib/utils.js');
26
+ const utils = require('../utils.js');
27
27
  const { Address6, Address4 } = require('ip-address');
28
28
 
29
29
  // ============================================================================
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:')};