├── .gitignore ├── LICENSE.md ├── README.md ├── _error ├── 404.css ├── 404.html ├── geoip32.png └── geoip48.png ├── _lib ├── lib.js ├── package-lock.json ├── package.json └── stats.js ├── _public ├── icons │ ├── geoip128.png │ ├── geoip32.png │ ├── geoip48.png │ └── geoip64.png └── sprites │ └── sprites.png ├── client ├── index.css ├── index.html ├── index.js ├── robots.txt └── sitemap.xml └── server ├── geoip.js ├── geoipTask.js ├── package-lock.json ├── package.json └── test └── geoip_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore these directories 2 | _aux/ 3 | node_modules/ 4 | tmp/ 5 | 6 | #ignore these files 7 | # Ignore Mac DS_Store files 8 | .env 9 | .DS_Store 10 | config.js 11 | config.json 12 | private.json 13 | secret.json 14 | stats.json 15 | 16 | # ip databases 17 | _lib/db 18 | _lib/csv 19 | GeoLite2-City.mmdb 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Jolav All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Maintained YES](https://img.shields.io/badge/Maintained%3F-yes-green.svg) 3 | ![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg) 4 | 5 | # ![logo](https://github.com/jolav/geoip-tools/blob/master/_public/icons/geoip128.png?raw=true) **[GEOIP-TOOLS](https://geoip.tools)** 6 | 7 | 8 | Free GeoIP service that provides a public HTTPS (SSL access) API to retrieve geolocation information from any IPv4, IPv6 or hostname. 9 | 10 | version 0.2.5 11 | 12 | ### **[API DOCS](https://geoip.tools)** 13 | 14 | HTTP Request Template: 15 | `GET https://geoip.tools/v1/{format}/?q={IP-or-hostname}` 16 | 17 | Supported formats : json, xml and csv
18 | If no IP or hostname is provided it retrieves your own IP
19 | IPv4 and IPv6 supported
20 | CORS support out of the box makes geoip.tools perfect to your front end apps
21 | 22 | 23 | * **Examples:** 24 | 25 | [https://geoip.tools/v1/json](https://geoip.tools/v1/json) 26 | 27 | 28 | [https://geoip.tools/v1/json/?q=geoip.tools](https://geoip.tools/v1/json/?q=geoip.tools) 29 | 30 | 31 | [https://geoip.tools/v1/xml/?q=192.168.200.200](https://geoip.tools/v1/xml/?q=192.168.200.200) 32 | 33 | [https://geoip.tools/v1/xml/?q=2a00:1450:4006:803::200e](https://geoip.tools/v1/xml/?q=2a00:1450:4006:803::200e) 34 | 35 | 36 | * **Usage Limits:** 37 | 38 | 300 requests per minute (432.000 API requests daily). Once reached subsequent requests will result in error 503 until your quota is cleared. 39 | If you need more quota contact us. 40 | Our API requires no key or signup. 41 | 42 | * **Example** 43 | 44 | ```json 45 | { 46 | "ip": "192.168.200.200", 47 | "country_code": "GB", 48 | "country_name": "United Kingdom", 49 | "region_code": "ENG", 50 | "region_name": "England", 51 | "city": "London", 52 | "zip_code": "SL1", 53 | "time_zone": "Europe/London", 54 | "latitude": 50.0500, 55 | "longitude": 0.6172 56 | } 57 | ``` 58 | 59 |
60 | 61 | ## **Acknowledgment** 62 | 63 | * This site includes GeoLite2 data created by MaxMind, available from [maxmind.com](http://maxmind.com) 64 | -------------------------------------------------------------------------------- /_error/404.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'arial', sans-serif; 3 | background-color: #fafafa; 4 | font-size: 1em; 5 | margin: auto; 6 | max-width: 960px; 7 | -webkit-user-select: none; 8 | /* Chrome all / Safari all */ 9 | -moz-user-select: none; 10 | /* Firefox all */ 11 | -ms-user-select: none; 12 | /* IE 10+ */ 13 | user-select: none; 14 | color: #404040; 15 | } 16 | 17 | @media (max-width: 540px) { 18 | .error { 19 | flex-direction: column; 20 | align-items: center; 21 | } 22 | .type { 23 | margin-top: 20px; 24 | } 25 | } 26 | 27 | @media (min-width: 541px) { 28 | .error { 29 | align-items: baseline; 30 | } 31 | .number { 32 | padding-right: 20px; 33 | } 34 | .type { 35 | padding-left: 20px; 36 | } 37 | } 38 | 39 | .error { 40 | display: flex; 41 | } 42 | 43 | .container { 44 | margin-top: 50px; 45 | display: flex; 46 | flex-flow: column nowrap; 47 | align-items: center; 48 | } 49 | 50 | .number { 51 | font-size: 5em; 52 | font-weight: bold; 53 | display: flex; 54 | } 55 | 56 | .digit { 57 | border: 1px solid #404040; 58 | padding: 5px 10px; 59 | } 60 | 61 | .digit + .digit { 62 | border-left: none; 63 | } 64 | 65 | .type { 66 | font-size: 2em; 67 | } 68 | 69 | .explanation { 70 | margin-top: 30px; 71 | } 72 | 73 | .options { 74 | font-size: 1.2em; 75 | margin-top: 10px; 76 | } 77 | 78 | a.link { 79 | color: #2cc0e9; 80 | /* color: #375EAB;*/ 81 | cursor: pointer; 82 | text-decoration: none; 83 | } 84 | 85 | a.link:hover { 86 | color: lightskyblue; 87 | } 88 | 89 | 90 | /* TITLE */ 91 | 92 | .titleBox { 93 | padding: 0px; 94 | font-size: 2.5em; 95 | text-align: center; 96 | color: #e9552c; 97 | } 98 | 99 | .titleBox img { 100 | vertical-align: middle; 101 | max-height: 48px; 102 | padding: 0px 15px 8px 0px; 103 | } -------------------------------------------------------------------------------- /_error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 Page Not Found 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |

25 | geoip.toolsgeoIP.tools 26 |

27 | 28 |
29 |
30 | 4 31 | 0 32 | 4 33 |
34 |
35 | Page Not Found 36 |
37 |
38 | 39 |
40 | We can't find the page you are looking for. 41 |
42 |
43 | « Go Back  /  44 | Go Home » 45 |
46 | 47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /_error/geoip32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_error/geoip32.png -------------------------------------------------------------------------------- /_error/geoip48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_error/geoip48.png -------------------------------------------------------------------------------- /_lib/lib.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | 3 | function getIP (req) { 4 | return (req.headers['x-forwarded-for'] || 5 | req.connection.remoteAddress || req.socket.remoteAddress || 6 | req.connection.socket.remoteAddress).split(',')[0]; 7 | } 8 | 9 | function isValidIP (ip) { 10 | if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip)) { 11 | return true; 12 | } else if (/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/.test(ip)) { 13 | return true; 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | function isValidHostname (hostname) { 20 | let condition = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$/; 21 | if (condition.test(hostname)) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | function addDays (date, days) { 28 | var result = new Date(date); 29 | result.setDate(result.getDate() + days); 30 | return result; 31 | } 32 | 33 | module.exports = { 34 | getIP: getIP, 35 | isValidIP: isValidIP, 36 | isValidHostname: isValidHostname, 37 | addDays: addDays 38 | }; 39 | -------------------------------------------------------------------------------- /_lib/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_lib", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "big-integer": { 8 | "version": "1.6.32", 9 | "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.32.tgz", 10 | "integrity": "sha512-ljKJdR3wk9thHfLj4DtrNiOSTxvGFaMjWrG4pW75juXC4j7+XuKJVFdg4kgFMYp85PVkO05dFMj2dk2xVsH4xw==" 11 | }, 12 | "bignumber.js": { 13 | "version": "4.1.0", 14 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", 15 | "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" 16 | }, 17 | "core-util-is": { 18 | "version": "1.0.2", 19 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 20 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 21 | }, 22 | "dotenv": { 23 | "version": "6.0.0", 24 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", 25 | "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" 26 | }, 27 | "inherits": { 28 | "version": "2.0.3", 29 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 30 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 31 | }, 32 | "isarray": { 33 | "version": "1.0.0", 34 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 35 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 36 | }, 37 | "maxmind": { 38 | "version": "2.7.0", 39 | "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-2.7.0.tgz", 40 | "integrity": "sha512-blmBnRbEkYBGz+7kxNQzuYUwyMyJ5PYGhrMYe6RXmMSBxZXe2665INgNP5/8WuYdWnDJ1Z8osC/LVKdsbT2fnQ==", 41 | "requires": { 42 | "big-integer": "^1.6.31", 43 | "tiny-lru": "^1.6.1" 44 | } 45 | }, 46 | "mysql": { 47 | "version": "2.16.0", 48 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.16.0.tgz", 49 | "integrity": "sha512-dPbN2LHonQp7D5ja5DJXNbCLe/HRdu+f3v61aguzNRQIrmZLOeRoymBYyeThrR6ug+FqzDL95Gc9maqZUJS+Gw==", 50 | "requires": { 51 | "bignumber.js": "4.1.0", 52 | "readable-stream": "2.3.6", 53 | "safe-buffer": "5.1.2", 54 | "sqlstring": "2.3.1" 55 | } 56 | }, 57 | "process-nextick-args": { 58 | "version": "2.0.0", 59 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 60 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 61 | }, 62 | "readable-stream": { 63 | "version": "2.3.6", 64 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 65 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 66 | "requires": { 67 | "core-util-is": "~1.0.0", 68 | "inherits": "~2.0.3", 69 | "isarray": "~1.0.0", 70 | "process-nextick-args": "~2.0.0", 71 | "safe-buffer": "~5.1.1", 72 | "string_decoder": "~1.1.1", 73 | "util-deprecate": "~1.0.1" 74 | } 75 | }, 76 | "safe-buffer": { 77 | "version": "5.1.2", 78 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 79 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 80 | }, 81 | "sqlstring": { 82 | "version": "2.3.1", 83 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 84 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" 85 | }, 86 | "string_decoder": { 87 | "version": "1.1.1", 88 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 89 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 90 | "requires": { 91 | "safe-buffer": "~5.1.0" 92 | } 93 | }, 94 | "tiny-lru": { 95 | "version": "1.6.1", 96 | "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-1.6.1.tgz", 97 | "integrity": "sha512-m8oyPnHjnQlbDk8+MCw33qRMp6+BxPxoayN9C743VToeyQ5zZV6F6vkklrYVEI0z9MQ3+jmc+22tKmvPg4gmoA==" 98 | }, 99 | "util-deprecate": { 100 | "version": "1.0.2", 101 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 102 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /_lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_lib", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "lib.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "jolav", 11 | "license": "BSD-3-Clause", 12 | "dependencies": { 13 | "dotenv": "^6.0.0", 14 | "maxmind": "^2.7.0", 15 | "mysql": "^2.16.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /_lib/stats.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | require('dotenv').config(); 3 | const lib = require('./lib.js'); 4 | 5 | const TABLE = process.env.TABLE; 6 | 7 | const mysql = require('mysql'); 8 | const con = mysql.createConnection({ 9 | host: process.env.HOST, 10 | user: process.env.MYSQLUSER, 11 | password: process.env.PASSWORD, 12 | database: process.env.DB_GEOIP 13 | }); 14 | 15 | function updateStats (req, res, next) { 16 | const test = lib.getIP(req); 17 | if (test) { 18 | const time = new Date().toISOString().split('T')[0]; 19 | if (process.env.NODE_ENV === 'production') { 20 | insertHit(time); 21 | } else { 22 | console.log('SAVE TEST ...'); 23 | } 24 | } else { 25 | console.log(test, 'DONT SAVE => '); 26 | } 27 | next(); 28 | } 29 | 30 | function insertHit (time) { 31 | let sql = 'INSERT INTO ?? (day, geoip)'; 32 | sql += ' VALUES (?, 0)'; 33 | sql += ` ON DUPLICATE KEY UPDATE geoip = geoip + 1;`; 34 | const inserts = [TABLE, time]; 35 | sql = mysql.format(sql, inserts); 36 | con.query(sql, function (err, rows) { 37 | if (err) { 38 | console.log('Insert HIT error =>', err); 39 | // throw err 40 | } else { 41 | // console.log(rows) 42 | } 43 | }); 44 | } 45 | 46 | function testDB () { 47 | console.log('Connecting ......'); 48 | // console.log(con) 49 | con.connect(function (err) { 50 | if (err) { 51 | console.log('Error connecting to DB => ', err); 52 | } else { 53 | console.log('Connection OK'); 54 | } 55 | }); 56 | } 57 | 58 | module.exports = { 59 | updateStats: updateStats, 60 | testDB: testDB 61 | }; 62 | -------------------------------------------------------------------------------- /_public/icons/geoip128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_public/icons/geoip128.png -------------------------------------------------------------------------------- /_public/icons/geoip32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_public/icons/geoip32.png -------------------------------------------------------------------------------- /_public/icons/geoip48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_public/icons/geoip48.png -------------------------------------------------------------------------------- /_public/icons/geoip64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_public/icons/geoip64.png -------------------------------------------------------------------------------- /_public/sprites/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apilayer/geoip-tools/ab5fdbdfac0202cf9645109365dc0248b1948a11/_public/sprites/sprites.png -------------------------------------------------------------------------------- /client/index.css: -------------------------------------------------------------------------------- 1 | /* BIG SMALL */ 2 | 3 | @media only screen and (max-width: 681px) { 4 | .big { 5 | display: none 6 | } 7 | .small { 8 | display: block; 9 | } 10 | h1 { 11 | font-size: 2em; 12 | } 13 | } 14 | 15 | @media only screen and (min-width: 681px) { 16 | .big { 17 | display: block 18 | } 19 | .small { 20 | display: none; 21 | } 22 | h1 { 23 | font-size: 3em; 24 | } 25 | } 26 | 27 | @media only screen and (max-width: 681px) { 28 | body { 29 | padding: 5px; 30 | } 31 | .data { 32 | display: flex; 33 | flex-flow: column wrap; 34 | align-items: center; 35 | } 36 | .data1 { 37 | width: 80%; 38 | max-width: 400px; 39 | margin-bottom: 10px; 40 | } 41 | .data2 { 42 | width: 80%; 43 | max-width: 400px; 44 | } 45 | } 46 | 47 | @media only screen and (min-width: 681px) { 48 | body { 49 | padding: 10px; 50 | } 51 | .data { 52 | display: flex; 53 | flex-flow: row wrap; 54 | align-items: center; 55 | } 56 | .data1 { 57 | flex: 3; 58 | margin-right: 15px 59 | } 60 | .data2 { 61 | flex: 2; 62 | margin-left: 15px; 63 | } 64 | } 65 | 66 | body { 67 | font-family: "Varela", "arial", sans-serif; 68 | background-color: #eee; 69 | color: #404040; 70 | font-size: 1em; 71 | margin: auto; 72 | max-width: 780px; 73 | -webkit-user-select: none; 74 | /* Chrome all / Safari all */ 75 | -moz-user-select: none; 76 | /* Firefox all */ 77 | -ms-user-select: none; 78 | /* IE 10+ */ 79 | user-select: none; 80 | } 81 | 82 | /* GENERICS */ 83 | 84 | .centerImage { 85 | display: flex; 86 | margin: 0 auto; 87 | max-width: 60%; 88 | } 89 | 90 | .verticalImage { 91 | vertical-align: middle; 92 | } 93 | 94 | .centerText { 95 | text-align: center; 96 | } 97 | 98 | /* HEADER */ 99 | 100 | .header { 101 | margin-bottom: 10px; 102 | } 103 | 104 | h1 { 105 | margin: 0px; 106 | color: #e9552c; 107 | } 108 | 109 | /* DATA = RESULTS + MAP */ 110 | 111 | .data {} 112 | 113 | .data1 { 114 | padding: 10px; 115 | background-color: lightblue; 116 | border-radius: 10px; 117 | } 118 | 119 | .form input[type=text] { 120 | display: flex; 121 | margin: 0px auto 10px auto; 122 | width: 80%; 123 | text-align: center; 124 | font-size: 1.3em; 125 | border: none; 126 | border-bottom: 2px solid #e9552c; 127 | background-color: #eee; 128 | } 129 | 130 | .table { 131 | width: 100%; 132 | } 133 | 134 | tr.row td.key, tr.row td.value { 135 | border-bottom: 1px solid #bbbbbb; 136 | } 137 | 138 | tr.row td.key { 139 | width: 35%; 140 | } 141 | 142 | tr.row td.value { 143 | width: 65%; 144 | } 145 | 146 | .key { 147 | text-align: left; 148 | font-size: 0.8em; 149 | } 150 | 151 | .value { 152 | font-size: 0.95em; 153 | } 154 | 155 | .data2 { 156 | /* background-color: lightcoral;*/ 157 | } 158 | 159 | .map { 160 | padding: 10px; 161 | width: 90%; 162 | margin: 0px auto; 163 | } 164 | 165 | .map #mapid { 166 | height: 220px; 167 | } 168 | 169 | @media only screen and (max-width: 681px) { 170 | .docs { 171 | flex-flow: column wrap; 172 | } 173 | .docs1 { 174 | width: 90%; 175 | } 176 | .docs2 { 177 | margin-top: 15px; 178 | } 179 | } 180 | 181 | @media only screen and (min-width: 681px) { 182 | .doc { 183 | flex-flow: row nowrap; 184 | } 185 | .docs1 { 186 | margin-right: 30px; 187 | } 188 | } 189 | 190 | /* DOCS */ 191 | 192 | .docs { 193 | font-size: 0.9em; 194 | margin-top: 10px; 195 | display: flex; 196 | justify-content: space-around; 197 | align-items: center; 198 | } 199 | 200 | code, pre { 201 | font-size: 1.15em; 202 | } 203 | 204 | .red { 205 | color: #e9552c; 206 | } 207 | 208 | /* HR */ 209 | 210 | hr { 211 | margin: 0px 10%; 212 | border: 0; 213 | height: 1px; 214 | background: #333; 215 | background-image: linear-gradient(to right, #ccc, #888, #ccc); 216 | } 217 | 218 | /* FOOTER */ 219 | 220 | .footer { 221 | margin-top: 10px; 222 | text-align: center; 223 | } 224 | 225 | a.hard { 226 | color: #375EAB; 227 | } 228 | 229 | a.newSite { 230 | color: #375EAB; 231 | } 232 | 233 | a { 234 | color: #2cc0e9; 235 | text-decoration: none; 236 | } 237 | 238 | /* Generated by http://css.spritegen.com CSS Sprite Generator */ 239 | 240 | .geoip128, .geoip64 { 241 | display: inline-block; 242 | background: url('/_public/sprites/sprites.png') no-repeat; 243 | overflow: hidden; 244 | text-indent: -9999px; 245 | text-align: left; 246 | } 247 | 248 | .geoip128 { 249 | background-position: -0px -0px; 250 | width: 128px; 251 | height: 128px; 252 | } 253 | 254 | .geoip64 { 255 | background-position: -0px -128px; 256 | width: 64px; 257 | height: 64px; 258 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | geoIP.tools 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |

28 |
geoIP.tools 29 |

30 |

31 |
geoIP.tools 32 |

33 | 34 | Welcome to 35 | geoip.tools, a free GeoIP service that provides a public HTTPS (SSL access) API to retrieve geolocation from any 36 | IP or hostname. CORS support. 37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
IP
City
Region/Code
Country/Code
Zip Code
Time Zone
Lat/Lon
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 |
84 |

85 | API doc 86 | 87 | (version 0.2.5) 88 | 89 |

90 |
91 | 92 |
93 |
94 | 95 | HTTP Request template: 96 | 97 |
98 | GET 99 | https://geoip.tools/v1/{format}/?q={IP-or-hostname} 100 | 101 |
102 | Supported formats : 103 | json, 104 | xml 105 | and 106 | csv 107 |
108 | If no IP or hostname is provided it retrieves your own IP 109 |
110 | IPv4 and IPv6 supported 111 |
112 | CORS support out of the box makes geoip.tools perfect to your front end apps or webs 113 |
114 | 115 | 116 | Examples: 117 | 118 |
119 | - https://geoip.tools/v1/json 120 |
121 | - https://geoip.tools/v1/json/?q=geoip.tools 122 |
123 | - https://geoip.tools/v1/xml/?q=192.168.200.200 124 |
125 | - https://geoip.tools/v1/xml/?q=2a00:1450:4006:803::200e 126 |
127 |
128 | 129 | Usage Limits: 130 | 131 |
132 | 300 requests per minute (432.000 API requests daily) Once reached subsequent requests will result in error 503 until 133 | your quota is cleared 134 |
135 | If you need more quota contact us 136 |
137 | Our API requires no key or signup. 138 |
139 |
140 | 141 |
142 |
143 | 144 | JSON response example 145 | 146 |
147 | {   
148 |   "ip": "192.168.200.200",
149 |   "country_code": "GB",
150 |   "country_name": "United Kingdom",
151 |   "region_code": "ENG",
152 |   "region_name": "England",
153 |   "city": "London",
154 |   "zip_code": "SL1",
155 |   "time_zone": "Europe/London",
156 |   "latitude": 50.0500,
157 |   "longitude": 0.6172   
158 | }
159 |       
160 |
161 |
162 | 163 |
164 | 165 | 166 | 181 |
182 | 183 | 184 |
185 | 186 | 187 | 197 | 198 | 199 | 212 | 213 | 214 | 215 | 217 | 218 | 219 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | /* global L value1 value2 value3 value4 value5 value6 value7 */ 2 | const geoip = (function () { 3 | 'use strict'; 4 | /* code here */ 5 | 6 | // let geoipUrl = 'http://localhost:3000/json' 7 | let geoipUrl = 'https://geoip.tools/v1/json'; 8 | let q = ''; 9 | let data = {}; 10 | 11 | function init () { 12 | q = getURLParameter('q'); 13 | if (q === '' || q === null || q === undefined) { 14 | getAjaxData(geoipUrl, drawMap); 15 | } else { 16 | // console.log(geoipUrl + '?q=' + q) 17 | getAjaxData(geoipUrl + '?q=' + q, drawMap); 18 | } 19 | } 20 | 21 | function drawMap (data) { 22 | console.log(data); 23 | if (q === '' || q === null || q === undefined) { 24 | document.getElementById('q').value = data.ip; 25 | } else { 26 | document.getElementById('q').value = q; 27 | } 28 | value1.innerText = data.ip; 29 | value2.innerText = data.city; 30 | value3.innerText = data.region_name + ' ' + data.region_code; 31 | value4.innerText = data.country_name + ' ' + data.country_code; 32 | value5.innerText = data.zip_code; 33 | value6.innerText = data.time_zone; 34 | value7.innerText = data.latitude + ' , ' + data.longitude; 35 | 36 | let map = L.map('mapid').setView([data.latitude, data.longitude], 4); 37 | let popupText = `You Are Here
IP : ${data.ip}`; 38 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 39 | attribution: '© OpenStreetMap contributors' 40 | }).addTo(map); 41 | L.marker([data.latitude, data.longitude]) 42 | .addTo(map) 43 | .bindPopup(popupText) 44 | .openPopup(); 45 | } 46 | 47 | function getAjaxData (urlData, callback) { 48 | const xhr = new XMLHttpRequest(); 49 | xhr.onreadystatechange = function () { 50 | if (xhr.readyState === 4) { // 4 = "DONE" 51 | if (xhr.status === 200) { // 200 ="OK" 52 | callback(JSON.parse(xhr.responseText)); 53 | } else { 54 | console.log('Error: ' + xhr.status); 55 | } 56 | } 57 | }; 58 | xhr.open('GET', urlData); // add false to synchronous request 59 | xhr.send(); 60 | } 61 | 62 | function getURLParameter (name) { 63 | return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null; 64 | } 65 | 66 | return { 67 | init: init 68 | }; 69 | }()); 70 | 71 | window.addEventListener('load', geoip.init); 72 | -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / 3 | 4 | -------------------------------------------------------------------------------- /client/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://geoip.tools/daily1.00 4 | http://geoip.tools/daily1.00 5 | https://geoip.toolsdaily1.00 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /server/geoip.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | 3 | const express = require('express'); 4 | const app = express(); 5 | 6 | require('dotenv').config({ path: __dirname + '/../_lib/.env' }); 7 | const job = require(__dirname + '/geoipTask.js'); 8 | const stats = require(__dirname + '/../_lib/stats.js'); 9 | 10 | let port = 3000; 11 | if (process.env.NODE_ENV === 'production') { 12 | port = process.env.PORT_GEOIP; 13 | } 14 | 15 | app.disable('x-powered-by'); 16 | 17 | app.use(function (req, res, next) { 18 | stats.updateStats(req, res, next); 19 | }); 20 | 21 | app.get('/json/', (req, res) => { 22 | job.getJson(req, res, 'json'); 23 | }); 24 | 25 | app.get('/xml/', (req, res) => { 26 | job.getJson(req, res, 'xml'); 27 | }); 28 | 29 | app.get('/csv/', (req, res) => { 30 | job.getJson(req, res, 'csv'); 31 | }); 32 | 33 | app.get('*', (req, res) => { 34 | res.redirect('https://geoip.tools/notFound'); 35 | // res.status(404).send('Not Found') 36 | }); 37 | 38 | app.listen(port, function () { 39 | const time = new Date().toUTCString().split(',')[1]; 40 | console.log('Express server on port ' + port + ' - ' + time); 41 | }); 42 | 43 | module.exports = app; 44 | -------------------------------------------------------------------------------- /server/geoipTask.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | const dns = require('dns'); 3 | const maxmind = require('maxmind'); 4 | const json2xml = require('js2xmlparser'); 5 | const json2csv = require('json2csv'); 6 | const fs = require('fs'); 7 | const lib = require(__dirname + '/../_lib/lib.js'); 8 | const path = require('path'); 9 | 10 | /* DATABASE IN MEMORY */ 11 | const MMDBReader = require('mmdb-reader'); 12 | const dbpath = path.join(__dirname , '/../_lib/db/GeoLite2-City.mmdb'); 13 | const reader = new MMDBReader(dbpath); 14 | 15 | let data; 16 | let geoData; 17 | 18 | function getJson (req, res, format) { 19 | cleanData(); 20 | let q = req.query.q; 21 | if (!q) { 22 | q = lib.getIP(req); 23 | } 24 | if (lib.isValidIP(q)) { 25 | getDataFromRAM(req, res, format, q); 26 | return; 27 | } 28 | if (lib.isValidHostname(q)) { 29 | dns.lookup(q, function (err, ip) { 30 | if (err) { 31 | let text = `${q} is a unknown host, not a valid IP or hostname`; 32 | sendResult(res, 'json', text, 400); 33 | return; 34 | } 35 | if (ip === null || ip === undefined) { 36 | let text = `${q} is a unknown host, not a valid IP or hostname`; 37 | sendResult(res, 'json', text, 400); 38 | return; 39 | } 40 | getDataFromRAM(req, res, format, ip); 41 | return; 42 | }); 43 | return; 44 | } 45 | let text = `${q} is a unknown host, not a valid IP or hostname`; 46 | sendResult(res, 'json', text, 400); 47 | } 48 | 49 | function getDataFromDB (req, res, format, ip) { 50 | const dbpath = path.join(__dirname , '/../_lib/db/GeoLite2-City.mmdb'); 51 | maxmind.open(dbpath, function (err, cityLookup) { 52 | if (err) { 53 | console.log('Error => ', err); 54 | let text = 'Error accessing database. Try again'; 55 | sendResult(res, 'json', text, 500); 56 | return; 57 | } 58 | data = cityLookup.get(ip); 59 | if (data === null) { // not in database 60 | sendResult(res, format, geoData, 200); 61 | return; 62 | } 63 | geoData = handleGeoData(ip, data); 64 | sendResult(res, format, geoData, 200); 65 | return; 66 | }); 67 | } 68 | 69 | function getDataFromRAM (req, res, format, ip) { 70 | data = reader.lookup(ip); 71 | if (data === null) { // not in database 72 | sendResult(res, format, geoData, 200); 73 | return; 74 | } 75 | geoData = handleGeoData(ip, data); 76 | sendResult(res, format, geoData, 200); 77 | return; 78 | } 79 | 80 | function sendResult (res, format, data, status) { 81 | if (format === 'json') { 82 | res.header('Content-Type', 'application/json'); 83 | res.status(status).send(JSON.stringify(data, null, 3)); 84 | } else if (format === 'xml') { 85 | res.header('Content-Type', 'text/xml'); 86 | res.status(status).send(json2xml.parse('myGeoData', data)); 87 | } else if (format === 'csv') { 88 | let fields = [ 89 | 'ip', 90 | 'country_code', 91 | 'country_name', 92 | 'region_code', 93 | 'region_name', 94 | 'city', 95 | 'zip_code', 96 | 'time_zone', 97 | 'latitude', 98 | 'longitude']; 99 | try { 100 | let fileName = data.ip + '.csv'; 101 | let result = json2csv({ data: data, fields: fields }); 102 | fs.writeFile(fileName, result, function (err) { 103 | if (err) { 104 | throw err; 105 | } 106 | res.download(fileName, fileName, function (err) { 107 | if (err) { 108 | res.status(404).end(); 109 | return; 110 | } 111 | fs.unlink(fileName, undefined); 112 | }); 113 | }); 114 | } catch (err) { 115 | res.end(); 116 | } 117 | } 118 | cleanData(); 119 | return; 120 | } 121 | 122 | function handleGeoData (ip, data) { 123 | geoData.ip = ip; 124 | if (data.country !== undefined) { 125 | geoData.country_code = data.country.iso_code || ''; 126 | geoData.country_name = data.country.names['en'] || ''; 127 | } 128 | if (data.subdivisions !== undefined) { 129 | geoData.region_code = data.subdivisions[0].iso_code || ''; 130 | geoData.region_name = data.subdivisions[0].names['en'] || ''; 131 | } 132 | if (data.city !== undefined) { 133 | geoData.city = data.city.names['en'] || ''; 134 | } 135 | if (data.postal !== undefined) { 136 | geoData.zip_code = data.postal.code || ''; 137 | } 138 | if (data.location !== undefined) { 139 | geoData.time_zone = data.location.time_zone || ''; 140 | geoData.latitude = data.location.latitude.toFixed(4) || ''; 141 | geoData.longitude = data.location.longitude.toFixed(4) || ''; 142 | } 143 | return geoData; 144 | } 145 | 146 | function cleanData () { 147 | data = {}; 148 | geoData = { 149 | 'ip': '', 150 | 'country_code': '', 151 | 'country_name': '', 152 | 'region_code': '', 153 | 'region_name': '', 154 | 'city': '', 155 | 'zip_code': '', 156 | 'time_zone': '', 157 | 'latitude': '', 158 | 'longitude': '' 159 | }; 160 | } 161 | 162 | module.exports = { 163 | getJson: getJson 164 | }; 165 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geoip", 3 | "version": "0.2.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "assertion-error": { 22 | "version": "1.1.0", 23 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 24 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 25 | "dev": true 26 | }, 27 | "asynckit": { 28 | "version": "0.4.0", 29 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 30 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 31 | "dev": true 32 | }, 33 | "big-integer": { 34 | "version": "1.6.32", 35 | "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.32.tgz", 36 | "integrity": "sha512-ljKJdR3wk9thHfLj4DtrNiOSTxvGFaMjWrG4pW75juXC4j7+XuKJVFdg4kgFMYp85PVkO05dFMj2dk2xVsH4xw==" 37 | }, 38 | "body-parser": { 39 | "version": "1.18.2", 40 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 41 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 42 | "requires": { 43 | "bytes": "3.0.0", 44 | "content-type": "~1.0.4", 45 | "debug": "2.6.9", 46 | "depd": "~1.1.1", 47 | "http-errors": "~1.6.2", 48 | "iconv-lite": "0.4.19", 49 | "on-finished": "~2.3.0", 50 | "qs": "6.5.1", 51 | "raw-body": "2.3.2", 52 | "type-is": "~1.6.15" 53 | } 54 | }, 55 | "bytes": { 56 | "version": "3.0.0", 57 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 58 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 59 | }, 60 | "chai": { 61 | "version": "4.1.2", 62 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", 63 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", 64 | "dev": true, 65 | "requires": { 66 | "assertion-error": "^1.0.1", 67 | "check-error": "^1.0.1", 68 | "deep-eql": "^3.0.0", 69 | "get-func-name": "^2.0.0", 70 | "pathval": "^1.0.0", 71 | "type-detect": "^4.0.0" 72 | } 73 | }, 74 | "chai-http": { 75 | "version": "4.0.0", 76 | "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.0.0.tgz", 77 | "integrity": "sha512-R30Lj3JHHPhknOyurh09ZEBgyO4iSSeTjbLmyLvTr88IFC+zwRjAmaxBwj9TbEAGi0IV2uW+RHaTxeah5rdSaQ==", 78 | "dev": true, 79 | "requires": { 80 | "cookiejar": "^2.1.1", 81 | "is-ip": "^2.0.0", 82 | "methods": "^1.1.2", 83 | "qs": "^6.5.1", 84 | "superagent": "^3.7.0" 85 | } 86 | }, 87 | "chai-xml": { 88 | "version": "0.3.2", 89 | "resolved": "https://registry.npmjs.org/chai-xml/-/chai-xml-0.3.2.tgz", 90 | "integrity": "sha512-HAyFPmJE0MEleo+sjWhJUxj+/aYBoUQg5EF/eGhbv1IZtx8mNGyGKD4jKlohhD4DsZaH5jay+Y/xtzBacBLdHw==", 91 | "dev": true, 92 | "requires": { 93 | "chai": "^1.9.1", 94 | "xml2js": "^0.4.4" 95 | }, 96 | "dependencies": { 97 | "assertion-error": { 98 | "version": "1.0.0", 99 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", 100 | "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", 101 | "dev": true 102 | }, 103 | "chai": { 104 | "version": "1.10.0", 105 | "resolved": "https://registry.npmjs.org/chai/-/chai-1.10.0.tgz", 106 | "integrity": "sha1-5AMcyHZURhp1lD5aNatG6vOcHrk=", 107 | "dev": true, 108 | "requires": { 109 | "assertion-error": "1.0.0", 110 | "deep-eql": "0.1.3" 111 | } 112 | }, 113 | "deep-eql": { 114 | "version": "0.1.3", 115 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 116 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 117 | "dev": true, 118 | "requires": { 119 | "type-detect": "0.1.1" 120 | } 121 | }, 122 | "type-detect": { 123 | "version": "0.1.1", 124 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 125 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 126 | "dev": true 127 | } 128 | } 129 | }, 130 | "check-error": { 131 | "version": "1.0.2", 132 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 133 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 134 | "dev": true 135 | }, 136 | "cli-table": { 137 | "version": "0.3.1", 138 | "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", 139 | "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", 140 | "requires": { 141 | "colors": "1.0.3" 142 | } 143 | }, 144 | "colors": { 145 | "version": "1.0.3", 146 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 147 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 148 | }, 149 | "combined-stream": { 150 | "version": "1.0.6", 151 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 152 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 153 | "dev": true, 154 | "requires": { 155 | "delayed-stream": "~1.0.0" 156 | } 157 | }, 158 | "commander": { 159 | "version": "2.15.1", 160 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 161 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" 162 | }, 163 | "component-emitter": { 164 | "version": "1.2.1", 165 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 166 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 167 | "dev": true 168 | }, 169 | "content-disposition": { 170 | "version": "0.5.2", 171 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 172 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 173 | }, 174 | "content-type": { 175 | "version": "1.0.4", 176 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 177 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 178 | }, 179 | "cookie": { 180 | "version": "0.3.1", 181 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 182 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 183 | }, 184 | "cookie-signature": { 185 | "version": "1.0.6", 186 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 187 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 188 | }, 189 | "cookiejar": { 190 | "version": "2.1.1", 191 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", 192 | "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=", 193 | "dev": true 194 | }, 195 | "core-util-is": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 198 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 199 | "dev": true 200 | }, 201 | "debug": { 202 | "version": "2.6.9", 203 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 204 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 205 | "requires": { 206 | "ms": "2.0.0" 207 | } 208 | }, 209 | "deep-eql": { 210 | "version": "3.0.1", 211 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 212 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 213 | "dev": true, 214 | "requires": { 215 | "type-detect": "^4.0.0" 216 | } 217 | }, 218 | "delayed-stream": { 219 | "version": "1.0.0", 220 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 221 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 222 | "dev": true 223 | }, 224 | "depd": { 225 | "version": "1.1.2", 226 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 227 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 228 | }, 229 | "destroy": { 230 | "version": "1.0.4", 231 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 232 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 233 | }, 234 | "dotenv": { 235 | "version": "6.0.0", 236 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", 237 | "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" 238 | }, 239 | "ee-first": { 240 | "version": "1.1.1", 241 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 242 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 243 | }, 244 | "encodeurl": { 245 | "version": "1.0.2", 246 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 247 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 248 | }, 249 | "escape-html": { 250 | "version": "1.0.3", 251 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 252 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 253 | }, 254 | "etag": { 255 | "version": "1.8.1", 256 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 257 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 258 | }, 259 | "express": { 260 | "version": "4.16.3", 261 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 262 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 263 | "requires": { 264 | "accepts": "~1.3.5", 265 | "array-flatten": "1.1.1", 266 | "body-parser": "1.18.2", 267 | "content-disposition": "0.5.2", 268 | "content-type": "~1.0.4", 269 | "cookie": "0.3.1", 270 | "cookie-signature": "1.0.6", 271 | "debug": "2.6.9", 272 | "depd": "~1.1.2", 273 | "encodeurl": "~1.0.2", 274 | "escape-html": "~1.0.3", 275 | "etag": "~1.8.1", 276 | "finalhandler": "1.1.1", 277 | "fresh": "0.5.2", 278 | "merge-descriptors": "1.0.1", 279 | "methods": "~1.1.2", 280 | "on-finished": "~2.3.0", 281 | "parseurl": "~1.3.2", 282 | "path-to-regexp": "0.1.7", 283 | "proxy-addr": "~2.0.3", 284 | "qs": "6.5.1", 285 | "range-parser": "~1.2.0", 286 | "safe-buffer": "5.1.1", 287 | "send": "0.16.2", 288 | "serve-static": "1.13.2", 289 | "setprototypeof": "1.1.0", 290 | "statuses": "~1.4.0", 291 | "type-is": "~1.6.16", 292 | "utils-merge": "1.0.1", 293 | "vary": "~1.1.2" 294 | } 295 | }, 296 | "extend": { 297 | "version": "3.0.1", 298 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 299 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", 300 | "dev": true 301 | }, 302 | "finalhandler": { 303 | "version": "1.1.1", 304 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 305 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 306 | "requires": { 307 | "debug": "2.6.9", 308 | "encodeurl": "~1.0.2", 309 | "escape-html": "~1.0.3", 310 | "on-finished": "~2.3.0", 311 | "parseurl": "~1.3.2", 312 | "statuses": "~1.4.0", 313 | "unpipe": "~1.0.0" 314 | } 315 | }, 316 | "flat": { 317 | "version": "4.0.0", 318 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz", 319 | "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==", 320 | "requires": { 321 | "is-buffer": "~1.1.5" 322 | } 323 | }, 324 | "form-data": { 325 | "version": "2.3.2", 326 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 327 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 328 | "dev": true, 329 | "requires": { 330 | "asynckit": "^0.4.0", 331 | "combined-stream": "1.0.6", 332 | "mime-types": "^2.1.12" 333 | } 334 | }, 335 | "formidable": { 336 | "version": "1.2.1", 337 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", 338 | "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", 339 | "dev": true 340 | }, 341 | "forwarded": { 342 | "version": "0.1.2", 343 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 344 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 345 | }, 346 | "fresh": { 347 | "version": "0.5.2", 348 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 349 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 350 | }, 351 | "get-func-name": { 352 | "version": "2.0.0", 353 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 354 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 355 | "dev": true 356 | }, 357 | "hashlru": { 358 | "version": "2.2.1", 359 | "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.2.1.tgz", 360 | "integrity": "sha1-EPIJmg18BaQPK+r1wdOc8vfavzY=" 361 | }, 362 | "http-errors": { 363 | "version": "1.6.3", 364 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 365 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 366 | "requires": { 367 | "depd": "~1.1.2", 368 | "inherits": "2.0.3", 369 | "setprototypeof": "1.1.0", 370 | "statuses": ">= 1.4.0 < 2" 371 | } 372 | }, 373 | "iconv-lite": { 374 | "version": "0.4.19", 375 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 376 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 377 | }, 378 | "inherits": { 379 | "version": "2.0.3", 380 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 381 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 382 | }, 383 | "ip-regex": { 384 | "version": "2.1.0", 385 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", 386 | "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", 387 | "dev": true 388 | }, 389 | "ipaddr.js": { 390 | "version": "1.6.0", 391 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 392 | "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 393 | }, 394 | "is-buffer": { 395 | "version": "1.1.6", 396 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 397 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 398 | }, 399 | "is-ip": { 400 | "version": "2.0.0", 401 | "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", 402 | "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", 403 | "dev": true, 404 | "requires": { 405 | "ip-regex": "^2.0.0" 406 | } 407 | }, 408 | "isarray": { 409 | "version": "1.0.0", 410 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 411 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 412 | "dev": true 413 | }, 414 | "js2xmlparser": { 415 | "version": "3.0.0", 416 | "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", 417 | "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", 418 | "requires": { 419 | "xmlcreate": "^1.0.1" 420 | } 421 | }, 422 | "json2csv": { 423 | "version": "3.11.5", 424 | "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-3.11.5.tgz", 425 | "integrity": "sha512-ORsw84BuRKMLxfI+HFZuvxRDnsJps53D5fIGr6tLn4ZY+ymcG8XU00E+JJ2wfAiHx5w2QRNmOLE8xHiGAeSfuQ==", 426 | "requires": { 427 | "cli-table": "^0.3.1", 428 | "commander": "^2.8.1", 429 | "debug": "^3.1.0", 430 | "flat": "^4.0.0", 431 | "lodash.clonedeep": "^4.5.0", 432 | "lodash.flatten": "^4.4.0", 433 | "lodash.get": "^4.4.0", 434 | "lodash.set": "^4.3.0", 435 | "lodash.uniq": "^4.5.0", 436 | "path-is-absolute": "^1.0.0" 437 | }, 438 | "dependencies": { 439 | "debug": { 440 | "version": "3.1.0", 441 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 442 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 443 | "requires": { 444 | "ms": "2.0.0" 445 | } 446 | } 447 | } 448 | }, 449 | "lodash.clonedeep": { 450 | "version": "4.5.0", 451 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 452 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" 453 | }, 454 | "lodash.flatten": { 455 | "version": "4.4.0", 456 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 457 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 458 | }, 459 | "lodash.get": { 460 | "version": "4.4.2", 461 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 462 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" 463 | }, 464 | "lodash.set": { 465 | "version": "4.3.2", 466 | "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", 467 | "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" 468 | }, 469 | "lodash.uniq": { 470 | "version": "4.5.0", 471 | "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", 472 | "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" 473 | }, 474 | "maxmind": { 475 | "version": "2.7.0", 476 | "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-2.7.0.tgz", 477 | "integrity": "sha512-blmBnRbEkYBGz+7kxNQzuYUwyMyJ5PYGhrMYe6RXmMSBxZXe2665INgNP5/8WuYdWnDJ1Z8osC/LVKdsbT2fnQ==", 478 | "requires": { 479 | "big-integer": "^1.6.31", 480 | "tiny-lru": "^1.6.1" 481 | } 482 | }, 483 | "media-typer": { 484 | "version": "0.3.0", 485 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 486 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 487 | }, 488 | "merge-descriptors": { 489 | "version": "1.0.1", 490 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 491 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 492 | }, 493 | "methods": { 494 | "version": "1.1.2", 495 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 496 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 497 | }, 498 | "mime": { 499 | "version": "1.4.1", 500 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 501 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 502 | }, 503 | "mime-db": { 504 | "version": "1.33.0", 505 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 506 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 507 | }, 508 | "mime-types": { 509 | "version": "2.1.18", 510 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 511 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 512 | "requires": { 513 | "mime-db": "~1.33.0" 514 | } 515 | }, 516 | "mmdb-reader": { 517 | "version": "1.2.0", 518 | "resolved": "https://registry.npmjs.org/mmdb-reader/-/mmdb-reader-1.2.0.tgz", 519 | "integrity": "sha512-H6B2VFogE+2VM/1qxEhGWbIxOy6hATZ1vx1uW7/8wIltC0+4vOirnyeqquCslCXCEIgJ4VGrSxknL3BhNzkASg==", 520 | "requires": { 521 | "big-integer": "^1.5.4", 522 | "hashlru": "^2.0.0" 523 | } 524 | }, 525 | "ms": { 526 | "version": "2.0.0", 527 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 528 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 529 | }, 530 | "negotiator": { 531 | "version": "0.6.1", 532 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 533 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 534 | }, 535 | "on-finished": { 536 | "version": "2.3.0", 537 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 538 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 539 | "requires": { 540 | "ee-first": "1.1.1" 541 | } 542 | }, 543 | "parseurl": { 544 | "version": "1.3.2", 545 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 546 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 547 | }, 548 | "path-is-absolute": { 549 | "version": "1.0.1", 550 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 551 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 552 | }, 553 | "path-to-regexp": { 554 | "version": "0.1.7", 555 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 556 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 557 | }, 558 | "pathval": { 559 | "version": "1.1.0", 560 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 561 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 562 | "dev": true 563 | }, 564 | "process-nextick-args": { 565 | "version": "2.0.0", 566 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 567 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 568 | "dev": true 569 | }, 570 | "proxy-addr": { 571 | "version": "2.0.3", 572 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", 573 | "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", 574 | "requires": { 575 | "forwarded": "~0.1.2", 576 | "ipaddr.js": "1.6.0" 577 | } 578 | }, 579 | "qs": { 580 | "version": "6.5.1", 581 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 582 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 583 | }, 584 | "range-parser": { 585 | "version": "1.2.0", 586 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 587 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 588 | }, 589 | "raw-body": { 590 | "version": "2.3.2", 591 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 592 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 593 | "requires": { 594 | "bytes": "3.0.0", 595 | "http-errors": "1.6.2", 596 | "iconv-lite": "0.4.19", 597 | "unpipe": "1.0.0" 598 | }, 599 | "dependencies": { 600 | "depd": { 601 | "version": "1.1.1", 602 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 603 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 604 | }, 605 | "http-errors": { 606 | "version": "1.6.2", 607 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 608 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 609 | "requires": { 610 | "depd": "1.1.1", 611 | "inherits": "2.0.3", 612 | "setprototypeof": "1.0.3", 613 | "statuses": ">= 1.3.1 < 2" 614 | } 615 | }, 616 | "setprototypeof": { 617 | "version": "1.0.3", 618 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 619 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 620 | } 621 | } 622 | }, 623 | "readable-stream": { 624 | "version": "2.3.6", 625 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 626 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 627 | "dev": true, 628 | "requires": { 629 | "core-util-is": "~1.0.0", 630 | "inherits": "~2.0.3", 631 | "isarray": "~1.0.0", 632 | "process-nextick-args": "~2.0.0", 633 | "safe-buffer": "~5.1.1", 634 | "string_decoder": "~1.1.1", 635 | "util-deprecate": "~1.0.1" 636 | } 637 | }, 638 | "safe-buffer": { 639 | "version": "5.1.1", 640 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 641 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 642 | }, 643 | "sax": { 644 | "version": "1.2.4", 645 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 646 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", 647 | "dev": true 648 | }, 649 | "send": { 650 | "version": "0.16.2", 651 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 652 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 653 | "requires": { 654 | "debug": "2.6.9", 655 | "depd": "~1.1.2", 656 | "destroy": "~1.0.4", 657 | "encodeurl": "~1.0.2", 658 | "escape-html": "~1.0.3", 659 | "etag": "~1.8.1", 660 | "fresh": "0.5.2", 661 | "http-errors": "~1.6.2", 662 | "mime": "1.4.1", 663 | "ms": "2.0.0", 664 | "on-finished": "~2.3.0", 665 | "range-parser": "~1.2.0", 666 | "statuses": "~1.4.0" 667 | } 668 | }, 669 | "serve-static": { 670 | "version": "1.13.2", 671 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 672 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 673 | "requires": { 674 | "encodeurl": "~1.0.2", 675 | "escape-html": "~1.0.3", 676 | "parseurl": "~1.3.2", 677 | "send": "0.16.2" 678 | } 679 | }, 680 | "setprototypeof": { 681 | "version": "1.1.0", 682 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 683 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 684 | }, 685 | "statuses": { 686 | "version": "1.4.0", 687 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 688 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 689 | }, 690 | "string_decoder": { 691 | "version": "1.1.1", 692 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 693 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 694 | "dev": true, 695 | "requires": { 696 | "safe-buffer": "~5.1.0" 697 | } 698 | }, 699 | "superagent": { 700 | "version": "3.8.2", 701 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", 702 | "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", 703 | "dev": true, 704 | "requires": { 705 | "component-emitter": "^1.2.0", 706 | "cookiejar": "^2.1.0", 707 | "debug": "^3.1.0", 708 | "extend": "^3.0.0", 709 | "form-data": "^2.3.1", 710 | "formidable": "^1.1.1", 711 | "methods": "^1.1.1", 712 | "mime": "^1.4.1", 713 | "qs": "^6.5.1", 714 | "readable-stream": "^2.0.5" 715 | }, 716 | "dependencies": { 717 | "debug": { 718 | "version": "3.1.0", 719 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 720 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 721 | "dev": true, 722 | "requires": { 723 | "ms": "2.0.0" 724 | } 725 | } 726 | } 727 | }, 728 | "tiny-lru": { 729 | "version": "1.6.1", 730 | "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-1.6.1.tgz", 731 | "integrity": "sha512-m8oyPnHjnQlbDk8+MCw33qRMp6+BxPxoayN9C743VToeyQ5zZV6F6vkklrYVEI0z9MQ3+jmc+22tKmvPg4gmoA==" 732 | }, 733 | "type-detect": { 734 | "version": "4.0.8", 735 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 736 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 737 | "dev": true 738 | }, 739 | "type-is": { 740 | "version": "1.6.16", 741 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 742 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 743 | "requires": { 744 | "media-typer": "0.3.0", 745 | "mime-types": "~2.1.18" 746 | } 747 | }, 748 | "unpipe": { 749 | "version": "1.0.0", 750 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 751 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 752 | }, 753 | "util-deprecate": { 754 | "version": "1.0.2", 755 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 756 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 757 | "dev": true 758 | }, 759 | "utils-merge": { 760 | "version": "1.0.1", 761 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 762 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 763 | }, 764 | "vary": { 765 | "version": "1.1.2", 766 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 767 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 768 | }, 769 | "xml2js": { 770 | "version": "0.4.19", 771 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 772 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 773 | "dev": true, 774 | "requires": { 775 | "sax": ">=0.6.0", 776 | "xmlbuilder": "~9.0.1" 777 | } 778 | }, 779 | "xmlbuilder": { 780 | "version": "9.0.7", 781 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 782 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 783 | "dev": true 784 | }, 785 | "xmlcreate": { 786 | "version": "1.0.2", 787 | "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", 788 | "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=" 789 | } 790 | } 791 | } 792 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geoip", 3 | "version": "0.2.5", 4 | "description": "API, geolocation IP", 5 | "main": "geoip.js", 6 | "keywords": [], 7 | "scripts": { 8 | "test": "mocha test/*.js --timeout 15000", 9 | "start": "node geoip.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jolav/geoip-tools.git" 14 | }, 15 | "author": "jolav", 16 | "license": "BSD-3-Clause", 17 | "bugs": { 18 | "url": "https://github.com/jolav/geoip-tools/issues" 19 | }, 20 | "homepage": "https://github.com/jolav/geoip-tools#readme", 21 | "dependencies": { 22 | "dotenv": "^6.0.0", 23 | "express": "^4.16.3", 24 | "js2xmlparser": "^3.0.0", 25 | "json2csv": "^3.11.5", 26 | "maxmind": "^2.7.0", 27 | "mmdb-reader": "^1.2.0" 28 | }, 29 | "devDependencies": { 30 | "chai": "^4.1.2", 31 | "chai-http": "^4.0.0", 32 | "chai-xml": "^0.3.2" 33 | } 34 | } -------------------------------------------------------------------------------- /server/test/geoip_test.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | 3 | const chai = require('chai'); 4 | const chaiHttp = require('chai-http'); 5 | const chaiXml = require('chai-xml'); 6 | 7 | const expect = require('chai').expect; 8 | 9 | chai.use(chaiHttp); 10 | chai.use(chaiXml); 11 | 12 | // const url = 'https://geoip.tools/v1' 13 | const url = 'http://localhost:3000'; 14 | 15 | describe('GEOIP TEST JSON', function () { 16 | before(function (done) { 17 | done(); 18 | }); 19 | it('JSON /json no parameters', function (done) { 20 | chai.request(url) 21 | .get('/json') 22 | .query({}) 23 | .end(function (err, res) { 24 | // console.log('PATH=> ', res.req.path) 25 | expect(err).to.be.null; 26 | expect(res).to.have.status(200); 27 | expect(res).to.be.json; 28 | expect(res.body).to.be.an('object'); 29 | expect(res.body).to.have.property('ip'); 30 | expect(res.body).to.have.property('country_code'); 31 | expect(res.body).to.have.property('country_name'); 32 | expect(res.body).to.have.property('region_code'); 33 | expect(res.body).to.have.property('region_name'); 34 | expect(res.body).to.have.property('city'); 35 | expect(res.body).to.have.property('zip_code'); 36 | expect(res.body).to.have.property('time_zone'); 37 | expect(res.body).to.have.property('latitude'); 38 | expect(res.body).to.have.property('longitude'); 39 | done(); 40 | }); 41 | }); 42 | it('JSON /json?q=208.67.222.222 valid ip', function (done) { 43 | chai.request(url) 44 | .get('/json') 45 | .query({q: '208.67.222.222'}) 46 | .end(function (err, res) { 47 | // console.log('PATH=> ', res.req.path) 48 | expect(err).to.be.null; 49 | expect(res).to.have.status(200); 50 | expect(res).to.be.json; 51 | expect(res.body).to.be.an('object'); 52 | expect(res.body).to.have.property('ip', '208.67.222.222'); 53 | expect(res.body).to.have.property('country_code', 'US'); 54 | expect(res.body).to.have.property('country_name', 'United States'); 55 | expect(res.body).to.have.property('region_code', 'CA'); 56 | expect(res.body).to.have.property('region_name', 'California'); 57 | expect(res.body).to.have.property('city', 'San Francisco'); 58 | expect(res.body).to.have.property('zip_code', '94107'); 59 | expect(res.body).to.have.property('time_zone', 'America/Los_Angeles'); 60 | expect(res.body).to.have.property('latitude', '37.7697'); 61 | expect(res.body).to.have.property('longitude', '-122.3933'); 62 | done(); 63 | }); 64 | }); 65 | it('JSON /json?q=8.8.8.8 valid ip', function (done) { 66 | chai.request(url) 67 | .get('/json') 68 | .query({q: '8.8.8.8'}) 69 | .end(function (err, res) { 70 | // console.log('PATH=> ', res.req.path) 71 | expect(err).to.be.null; 72 | expect(res).to.have.status(200); 73 | expect(res).to.be.json; 74 | expect(res.body).to.be.an('object'); 75 | expect(res.body).to.have.property('ip', '8.8.8.8'); 76 | expect(res.body).to.have.property('country_code', 'US'); 77 | expect(res.body).to.have.property('country_name', 'United States'); 78 | expect(res.body).to.have.property('region_code', ''); 79 | expect(res.body).to.have.property('region_name', ''); 80 | expect(res.body).to.have.property('city', ''); 81 | expect(res.body).to.have.property('zip_code', ''); 82 | expect(res.body).to.have.property('time_zone', ''); 83 | expect(res.body).to.have.property('latitude', '37.7510'); 84 | expect(res.body).to.have.property('longitude', '-97.8220'); 85 | done(); 86 | }); 87 | }); 88 | it('JSON /json?q=2a00:1450:4006:803::200e valid ipv6', function (done) { 89 | chai.request(url) 90 | .get('/json') 91 | .query({q: '2a00:1450:4006:803::200e'}) 92 | .end(function (err, res) { 93 | // console.log('PATH=> ', res.req.path) 94 | expect(err).to.be.null; 95 | expect(res).to.have.status(200); 96 | expect(res).to.be.json; 97 | expect(res.body).to.be.an('object'); 98 | expect(res.body).to.have.property('ip', '2a00:1450:4006:803::200e'); 99 | expect(res.body).to.have.property('country_code', 'IE'); 100 | expect(res.body).to.have.property('country_name', 'Ireland'); 101 | expect(res.body).to.have.property('region_code', ''); 102 | expect(res.body).to.have.property('region_name', ''); 103 | expect(res.body).to.have.property('city', ''); 104 | expect(res.body).to.have.property('zip_code', ''); 105 | expect(res.body).to.have.property('time_zone', 'Europe/Dublin'); 106 | expect(res.body).to.have.property('latitude', '53.0000'); 107 | expect(res.body).to.have.property('longitude', '-8.0000'); 108 | done(); 109 | }); 110 | }); 111 | it('JSON /json?q=260.50.50.50 no valid ip', function (done) { 112 | chai.request(url) 113 | .get('/json') 114 | .query({q: '260.50.50.50'}) 115 | .end(function (err, res) { 116 | // console.log('PATH=> ', res.req.path) 117 | expect(err).to.be.null; 118 | expect(res).to.have.status(400); 119 | expect(res).to.be.json; 120 | expect(res.body).to.equal(text.json[0]); 121 | done(); 122 | }); 123 | }); 124 | it('JSON /json?q=20.20.-5.20 no valid ip', function (done) { 125 | chai.request(url) 126 | .get('/json') 127 | .query({q: '20.20.-5.20'}) 128 | .end(function (err, res) { 129 | // console.log('PATH=> ', res.req.path) 130 | expect(err).to.be.null; 131 | expect(res).to.have.status(400); 132 | expect(res).to.be.json; 133 | expect(res.body).to.equal(text.json[1]); 134 | done(); 135 | }); 136 | }); 137 | it('JSON /json?q=github.com valid hostname', function (done) { 138 | chai.request(url) 139 | .get('/json') 140 | .query({q: 'github.com'}) 141 | .end(function (err, res) { 142 | // console.log('PATH=> ', res.req.path) 143 | expect(err).to.be.null; 144 | expect(res).to.have.status(200); 145 | expect(res).to.be.json; 146 | expect(res.body).to.be.an('object'); 147 | // expect(res.body).to.have.property('ip', '192.30.253.112') 148 | expect(res.body.ip).to.be.oneOf(['192.30.253.112', '192.30.253.113']); 149 | expect(res.body).to.have.property('country_code', 'US'); 150 | expect(res.body).to.have.property('country_name', 'United States'); 151 | expect(res.body).to.have.property('region_code', 'CA'); 152 | expect(res.body).to.have.property('region_name', 'California'); 153 | expect(res.body).to.have.property('city', 'San Francisco'); 154 | expect(res.body).to.have.property('zip_code', '94107'); 155 | expect(res.body).to.have.property('time_zone', 'America/Los_Angeles'); 156 | expect(res.body).to.have.property('latitude', '37.7697'); 157 | expect(res.body).to.have.property('longitude', '-122.3933'); 158 | done(); 159 | }); 160 | }); 161 | it('JSON /json?q=no-valid.com no valid hostname', function (done) { 162 | chai.request(url) 163 | .get('/json') 164 | .query({q: 'no-valid.com'}) 165 | .end(function (err, res) { 166 | // console.log('PATH=> ', res.req.path) 167 | expect(err).to.be.null; 168 | expect(res).to.have.status(400); 169 | expect(res).to.be.json; 170 | expect(res.body).to.equal(text.json[2]); 171 | done(); 172 | }); 173 | }); 174 | }); 175 | 176 | describe('GEOIP TEST XML', function () { 177 | before(function (done) { 178 | done(); 179 | }); 180 | it('XML /xml no parameters', function (done) { 181 | chai.request(url) 182 | .get('/xml') 183 | // .query({q: '8.8.8.8'}) 184 | .end(function (err, res) { 185 | expect(err).to.be.null; 186 | expect(res).to.have.status(200); 187 | expect(res.text).xml.to.be.valid(); 188 | done(); 189 | }); 190 | }); 191 | it('XML /xml?q=208.67.222.222 valid ip ', function (done) { 192 | chai.request(url) 193 | .get('/xml') 194 | .query({q: '208.67.222.222'}) 195 | .end(function (err, res) { 196 | expect(err).to.be.null; 197 | expect(res).to.have.status(200); 198 | expect(res.text).xml.to.be.valid(); 199 | expect(res.text).xml.to.equal(text.xml[1]); 200 | done(); 201 | }); 202 | }); 203 | it('XML /xml?q=8.8.8.8 valid ip ', function (done) { 204 | chai.request(url) 205 | .get('/xml') 206 | .query({q: '8.8.8.8'}) 207 | .end(function (err, res) { 208 | expect(err).to.be.null; 209 | expect(res).to.have.status(200); 210 | expect(res.text).xml.to.be.valid(); 211 | expect(res.text).xml.to.equal(text.xml[2]); 212 | done(); 213 | }); 214 | }); 215 | it('XML /xml?q=2a00:1450:4006:803::200e valid ipv6 ', function (done) { 216 | chai.request(url) 217 | .get('/xml') 218 | .query({q: '2a00:1450:4006:803::200e'}) 219 | .end(function (err, res) { 220 | expect(err).to.be.null; 221 | expect(res).to.have.status(200); 222 | expect(res.text).xml.to.be.valid(); 223 | expect(res.text).xml.to.equal(text.xml[3]); 224 | done(); 225 | }); 226 | }); 227 | it('XML /xml?q=260.50.50.50 no valid ip ', function (done) { 228 | chai.request(url) 229 | .get('/xml') 230 | .query({q: '260.50.50.50'}) 231 | .end(function (err, res) { 232 | expect(err).to.be.null; 233 | expect(res).to.have.status(400); 234 | expect(res.body).to.equal(text.xml[4]); 235 | done(); 236 | }); 237 | }); 238 | it('XML /xml?q=20.20.-5.20 no valid ip ', function (done) { 239 | chai.request(url) 240 | .get('/xml') 241 | .query({q: '20.20.-5.20'}) 242 | .end(function (err, res) { 243 | expect(err).to.be.null; 244 | expect(res).to.have.status(400); 245 | expect(res.body).to.equal(text.xml[5]); 246 | done(); 247 | }); 248 | }); 249 | it('XML /xml?q=github.com valid hostname ', function (done) { 250 | chai.request(url) 251 | .get('/xml') 252 | .query({q: 'github.com'}) 253 | .end(function (err, res) { 254 | expect(err).to.be.null; 255 | expect(res).to.have.status(200); 256 | expect(res.text).xml.to.be.valid(); 257 | if (res.text.indexOf('192.30.253.113') === -1) { 258 | expect(res.text).xml.to.equal(text.xml[6]); 259 | } else { 260 | expect(res.text).xml.to.equal(text.xml[7]); 261 | } 262 | done(); 263 | }); 264 | }); 265 | it('XML /xml?q=no-valid.com ', function (done) { 266 | chai.request(url) 267 | .get('/xml') 268 | .query({q: 'no-valid.com'}) 269 | .end(function (err, res) { 270 | expect(err).to.be.null; 271 | expect(res).to.have.status(400); 272 | expect(res.body).to.equal(text.xml[8]); 273 | done(); 274 | }); 275 | }); 276 | }); 277 | 278 | const text = { 279 | 'json': [ 280 | '260.50.50.50 is a unknown host, not a valid IP or hostname', 281 | '20.20.-5.20 is a unknown host, not a valid IP or hostname', 282 | 'no-valid.com is a unknown host, not a valid IP or hostname' 283 | ], 284 | 'xml': [ 285 | '', 286 | 287 | '208.67.222.222USUnited StatesCACaliforniaSan Francisco94107America/Los_Angeles37.7697-122.3933', 288 | 289 | '8.8.8.8USUnited States37.7510-97.8220', 290 | 291 | '2a00:1450:4006:803::200eIEIrelandEurope/Dublin53.0000-8.0000', 292 | 293 | '260.50.50.50 is a unknown host, not a valid IP or hostname', 294 | 295 | '20.20.-5.20 is a unknown host, not a valid IP or hostname', 296 | 297 | '192.30.253.112USUnited StatesCACaliforniaSan Francisco94107America/Los_Angeles37.7697-122.3933', 298 | 299 | '192.30.253.113USUnited StatesCACaliforniaSan Francisco94107America/Los_Angeles37.7697-122.3933', 300 | 301 | 'no-valid.com is a unknown host, not a valid IP or hostname' 302 | 303 | ] 304 | }; 305 | --------------------------------------------------------------------------------