Index: pygeoip/__init__.py |
=================================================================== |
deleted file mode 100644 |
--- a/pygeoip/__init__.py |
+++ /dev/null |
@@ -1,668 +0,0 @@ |
-# -*- coding: utf-8 -*- |
-""" |
-Pure Python GeoIP API |
- |
-The API is based on MaxMind's C-based Python API, but the code itself is |
-ported from the Pure PHP GeoIP API by Jim Winstead and Hans Lellelid. |
- |
-@author: Jennifer Ennis <zaylea@gmail.com> |
- |
-@license: Copyright(C) 2004 MaxMind LLC |
- |
-This program is free software: you can redistribute it and/or modify |
-it under the terms of the GNU Lesser General Public License as published by |
-the Free Software Foundation, either version 3 of the License, or |
-(at your option) any later version. |
- |
-This program is distributed in the hope that it will be useful, |
-but WITHOUT ANY WARRANTY; without even the implied warranty of |
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
-GNU General Public License for more details. |
- |
-You should have received a copy of the GNU Lesser General Public License |
-along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>. |
-""" |
- |
-import os |
-import math |
-import socket |
-import mmap |
-import codecs |
-from threading import Lock |
- |
-try: |
- from StringIO import StringIO |
-except ImportError: |
- from io import StringIO, BytesIO |
- |
-from pygeoip import util, const |
-from pygeoip.const import PY2, PY3 |
-from pygeoip.timezone import time_zone_by_country_and_region |
- |
- |
-STANDARD = const.STANDARD |
-MMAP_CACHE = const.MMAP_CACHE |
-MEMORY_CACHE = const.MEMORY_CACHE |
- |
-ENCODING = const.ENCODING |
- |
- |
-class GeoIPError(Exception): |
- pass |
- |
- |
-class GeoIPMetaclass(type): |
- def __new__(cls, *args, **kwargs): |
- """ |
- Singleton method to gets an instance without reparsing the db. Unique |
- instances are instantiated based on the filename of the db. Flags are |
- ignored for this, i.e. if you initialize one with STANDARD |
- flag (default) and then try later to initialize with MEMORY_CACHE, it |
- will still return the STANDARD one. |
- """ |
- if not hasattr(cls, '_instances'): |
- cls._instances = {} |
- |
- if len(args) > 0: |
- filename = args[0] |
- elif 'filename' in kwargs: |
- filename = kwargs['filename'] |
- |
- if filename not in cls._instances: |
- cls._instances[filename] = type.__new__(cls, *args, **kwargs) |
- |
- return cls._instances[filename] |
- |
- |
-GeoIPBase = GeoIPMetaclass('GeoIPBase', (object,), {}) |
- |
- |
-class GeoIP(GeoIPBase): |
- def __init__(self, filename, flags=0): |
- """ |
- Initialize the class. |
- |
- @param filename: Path to a geoip database. |
- @type filename: str |
- @param flags: Flags that affect how the database is processed. |
- Currently supported flags are STANDARD (the default), |
- MEMORY_CACHE (preload the whole file into memory) and |
- MMAP_CACHE (access the file via mmap). |
- @type flags: int |
- """ |
- self._filename = filename |
- self._flags = flags |
- |
- if self._flags & const.MMAP_CACHE: |
- f = open(filename, 'rb') |
- access = mmap.ACCESS_READ |
- self._filehandle = mmap.mmap(f.fileno(), 0, access=access) |
- f.close() |
- |
- elif self._flags & const.MEMORY_CACHE: |
- f = open(filename, 'rb') |
- self._memoryBuffer = f.read() |
- iohandle = BytesIO if PY3 else StringIO |
- self._filehandle = iohandle(self._memoryBuffer) |
- f.close() |
- |
- else: |
- self._filehandle = codecs.open(filename, 'rb', ENCODING) |
- |
- self._lock = Lock() |
- self._setup_segments() |
- |
- def _setup_segments(self): |
- """ |
- Parses the database file to determine what kind of database is |
- being used and setup segment sizes and start points that will |
- be used by the seek*() methods later. |
- |
- Supported databases: |
- |
- * COUNTRY_EDITION |
- * COUNTRY_EDITION_V6 |
- * REGION_EDITION_REV0 |
- * REGION_EDITION_REV1 |
- * CITY_EDITION_REV0 |
- * CITY_EDITION_REV1 |
- * CITY_EDITION_REV1_V6 |
- * ORG_EDITION |
- * ISP_EDITION |
- * ASNUM_EDITION |
- * ASNUM_EDITION_V6 |
- |
- """ |
- self._databaseType = const.COUNTRY_EDITION |
- self._recordLength = const.STANDARD_RECORD_LENGTH |
- self._databaseSegments = const.COUNTRY_BEGIN |
- |
- self._lock.acquire() |
- filepos = self._filehandle.tell() |
- self._filehandle.seek(-3, os.SEEK_END) |
- |
- for i in range(const.STRUCTURE_INFO_MAX_SIZE): |
- chars = chr(255) * 3 |
- delim = self._filehandle.read(3) |
- |
- if PY3 and type(delim) is bytes: |
- delim = delim.decode(ENCODING) |
- |
- if PY2: |
- chars = chars.decode(ENCODING) |
- if type(delim) is str: |
- delim = delim.decode(ENCODING) |
- |
- if delim == chars: |
- byte = self._filehandle.read(1) |
- self._databaseType = ord(byte) |
- |
- # Compatibility with databases from April 2003 and earlier |
- if (self._databaseType >= 106): |
- self._databaseType -= 105 |
- |
- if self._databaseType == const.REGION_EDITION_REV0: |
- self._databaseSegments = const.STATE_BEGIN_REV0 |
- |
- elif self._databaseType == const.REGION_EDITION_REV1: |
- self._databaseSegments = const.STATE_BEGIN_REV1 |
- |
- elif self._databaseType in (const.CITY_EDITION_REV0, |
- const.CITY_EDITION_REV1, |
- const.CITY_EDITION_REV1_V6, |
- const.ORG_EDITION, |
- const.ISP_EDITION, |
- const.ASNUM_EDITION, |
- const.ASNUM_EDITION_V6): |
- self._databaseSegments = 0 |
- buf = self._filehandle.read(const.SEGMENT_RECORD_LENGTH) |
- |
- if PY3 and type(buf) is bytes: |
- buf = buf.decode(ENCODING) |
- |
- for j in range(const.SEGMENT_RECORD_LENGTH): |
- self._databaseSegments += (ord(buf[j]) << (j * 8)) |
- |
- LONG_RECORDS = (const.ORG_EDITION, const.ISP_EDITION) |
- if self._databaseType in LONG_RECORDS: |
- self._recordLength = const.ORG_RECORD_LENGTH |
- break |
- else: |
- self._filehandle.seek(-4, os.SEEK_CUR) |
- |
- self._filehandle.seek(filepos, os.SEEK_SET) |
- self._lock.release() |
- |
- def _seek_country(self, ipnum): |
- """ |
- Using the record length and appropriate start points, seek to the |
- country that corresponds to the converted IP address integer. |
- |
- @param ipnum: result of ip2long conversion |
- @type ipnum: int |
- @return: offset of start of record |
- @rtype: int |
- """ |
- try: |
- offset = 0 |
- seek_depth = 127 if len(str(ipnum)) > 10 else 31 |
- |
- for depth in range(seek_depth, -1, -1): |
- if self._flags & const.MEMORY_CACHE: |
- startIndex = 2 * self._recordLength * offset |
- endIndex = startIndex + (2 * self._recordLength) |
- buf = self._memoryBuffer[startIndex:endIndex] |
- else: |
- startIndex = 2 * self._recordLength * offset |
- readLength = 2 * self._recordLength |
- self._lock.acquire() |
- self._filehandle.seek(startIndex, os.SEEK_SET) |
- buf = self._filehandle.read(readLength) |
- self._lock.release() |
- |
- if PY3 and type(buf) is bytes: |
- buf = buf.decode(ENCODING) |
- |
- x = [0, 0] |
- for i in range(2): |
- for j in range(self._recordLength): |
- byte = buf[self._recordLength * i + j] |
- x[i] += ord(byte) << (j * 8) |
- if ipnum & (1 << depth): |
- if x[1] >= self._databaseSegments: |
- return x[1] |
- offset = x[1] |
- else: |
- if x[0] >= self._databaseSegments: |
- return x[0] |
- offset = x[0] |
- except: |
- pass |
- |
- raise GeoIPError('Corrupt database') |
- |
- def _get_org(self, ipnum): |
- """ |
- Seek and return organization or ISP name for ipnum. |
- @param ipnum: Converted IP address |
- @type ipnum: int |
- @return: org/isp name |
- @rtype: str |
- """ |
- seek_org = self._seek_country(ipnum) |
- if seek_org == self._databaseSegments: |
- return None |
- |
- read_length = (2 * self._recordLength - 1) * self._databaseSegments |
- self._lock.acquire() |
- self._filehandle.seek(seek_org + read_length, os.SEEK_SET) |
- buf = self._filehandle.read(const.MAX_ORG_RECORD_LENGTH) |
- self._lock.release() |
- |
- if PY3 and type(buf) is bytes: |
- buf = buf.decode(ENCODING) |
- |
- return buf[:buf.index(chr(0))] |
- |
- def _get_region(self, ipnum): |
- """ |
- Seek and return the region info (dict containing country_code |
- and region_name). |
- |
- @param ipnum: Converted IP address |
- @type ipnum: int |
- @return: dict containing country_code and region_name |
- @rtype: dict |
- """ |
- region = '' |
- country_code = '' |
- seek_country = self._seek_country(ipnum) |
- |
- def get_region_name(offset): |
- region1 = chr(offset // 26 + 65) |
- region2 = chr(offset % 26 + 65) |
- return ''.join([region1, region2]) |
- |
- if self._databaseType == const.REGION_EDITION_REV0: |
- seek_region = seek_country - const.STATE_BEGIN_REV0 |
- if seek_region >= 1000: |
- country_code = 'US' |
- region = get_region_name(seek_region - 1000) |
- else: |
- country_code = const.COUNTRY_CODES[seek_region] |
- elif self._databaseType == const.REGION_EDITION_REV1: |
- seek_region = seek_country - const.STATE_BEGIN_REV1 |
- if seek_region < const.US_OFFSET: |
- pass |
- elif seek_region < const.CANADA_OFFSET: |
- country_code = 'US' |
- region = get_region_name(seek_region - const.US_OFFSET) |
- elif seek_region < const.WORLD_OFFSET: |
- country_code = 'CA' |
- region = get_region_name(seek_region - const.CANADA_OFFSET) |
- else: |
- index = (seek_region - const.WORLD_OFFSET) // const.FIPS_RANGE |
- if index in const.COUNTRY_CODES: |
- country_code = const.COUNTRY_CODES[index] |
- elif self._databaseType in const.CITY_EDITIONS: |
- rec = self._get_record(ipnum) |
- region = rec.get('region_name', '') |
- country_code = rec.get('country_code', '') |
- |
- return {'country_code': country_code, 'region_name': region} |
- |
- def _get_record(self, ipnum): |
- """ |
- Populate location dict for converted IP. |
- |
- @param ipnum: Converted IP address |
- @type ipnum: int |
- @return: dict with country_code, country_code3, country_name, |
- region, city, postal_code, latitude, longitude, |
- dma_code, metro_code, area_code, region_name, time_zone |
- @rtype: dict |
- """ |
- seek_country = self._seek_country(ipnum) |
- if seek_country == self._databaseSegments: |
- return {} |
- |
- read_length = (2 * self._recordLength - 1) * self._databaseSegments |
- self._lock.acquire() |
- self._filehandle.seek(seek_country + read_length, os.SEEK_SET) |
- buf = self._filehandle.read(const.FULL_RECORD_LENGTH) |
- self._lock.release() |
- |
- if PY3 and type(buf) is bytes: |
- buf = buf.decode(ENCODING) |
- |
- record = { |
- 'dma_code': 0, |
- 'area_code': 0, |
- 'metro_code': '', |
- 'postal_code': '' |
- } |
- |
- latitude = 0 |
- longitude = 0 |
- buf_pos = 0 |
- |
- # Get country |
- char = ord(buf[buf_pos]) |
- record['country_code'] = const.COUNTRY_CODES[char] |
- record['country_code3'] = const.COUNTRY_CODES3[char] |
- record['country_name'] = const.COUNTRY_NAMES[char] |
- record['continent'] = const.CONTINENT_NAMES[char] |
- |
- buf_pos += 1 |
- def get_data(buf, buf_pos): |
- offset = buf_pos |
- char = ord(buf[offset]) |
- while (char != 0): |
- offset += 1 |
- char = ord(buf[offset]) |
- if offset > buf_pos: |
- return (offset, buf[buf_pos:offset]) |
- return (offset, '') |
- |
- offset, record['region_name'] = get_data(buf, buf_pos) |
- offset, record['city'] = get_data(buf, offset + 1) |
- offset, record['postal_code'] = get_data(buf, offset + 1) |
- buf_pos = offset + 1 |
- |
- for j in range(3): |
- char = ord(buf[buf_pos]) |
- buf_pos += 1 |
- latitude += (char << (j * 8)) |
- |
- for j in range(3): |
- char = ord(buf[buf_pos]) |
- buf_pos += 1 |
- longitude += (char << (j * 8)) |
- |
- record['latitude'] = (latitude / 10000.0) - 180.0 |
- record['longitude'] = (longitude / 10000.0) - 180.0 |
- |
- if self._databaseType in (const.CITY_EDITION_REV1, const.CITY_EDITION_REV1_V6): |
- dmaarea_combo = 0 |
- if record['country_code'] == 'US': |
- for j in range(3): |
- char = ord(buf[buf_pos]) |
- dmaarea_combo += (char << (j * 8)) |
- buf_pos += 1 |
- |
- record['dma_code'] = int(math.floor(dmaarea_combo / 1000)) |
- record['area_code'] = dmaarea_combo % 1000 |
- |
- record['metro_code'] = const.DMA_MAP.get(record['dma_code']) |
- params = (record['country_code'], record['region_name']) |
- record['time_zone'] = time_zone_by_country_and_region(*params) |
- |
- return record |
- |
- def _gethostbyname(self, hostname): |
- if self._databaseType in const.IPV6_EDITIONS: |
- try: |
- response = socket.getaddrinfo(hostname, 0, socket.AF_INET6) |
- family, socktype, proto, canonname, sockaddr = response[0] |
- address, port, flow, scope = sockaddr |
- return address |
- except socket.gaierror: |
- return '' |
- else: |
- return socket.gethostbyname(hostname) |
- |
- def id_by_addr(self, addr): |
- """ |
- Get the country index. |
- Looks up the index for the country which is the key for |
- the code and name. |
- |
- @param addr: The IP address |
- @type addr: str |
- @return: network byte order 32-bit integer |
- @rtype: int |
- """ |
- ipnum = util.ip2long(addr) |
- if not ipnum: |
- raise ValueError("Invalid IP address: %s" % addr) |
- |
- COUNTY_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) |
- if self._databaseType not in COUNTY_EDITIONS: |
- message = 'Invalid database type, expected Country' |
- raise GeoIPError(message) |
- |
- return self._seek_country(ipnum) - const.COUNTRY_BEGIN |
- |
- def country_code_by_addr(self, addr): |
- """ |
- Returns 2-letter country code (e.g. 'US') for specified IP address. |
- Use this method if you have a Country, Region, or City database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: 2-letter country code |
- @rtype: str |
- """ |
- try: |
- VALID_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) |
- if self._databaseType in VALID_EDITIONS: |
- ipv = 6 if addr.find(':') >= 0 else 4 |
- |
- if ipv == 4 and self._databaseType != const.COUNTRY_EDITION: |
- message = 'Invalid database type; expected IPv6 address' |
- raise ValueError(message) |
- if ipv == 6 and self._databaseType != const.COUNTRY_EDITION_V6: |
- message = 'Invalid database type; expected IPv4 address' |
- raise ValueError(message) |
- |
- country_id = self.id_by_addr(addr) |
- return const.COUNTRY_CODES[country_id] |
- elif self._databaseType in const.REGION_CITY_EDITIONS: |
- return self.region_by_addr(addr).get('country_code') |
- |
- message = 'Invalid database type, expected Country, City or Region' |
- raise GeoIPError(message) |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def country_code_by_name(self, hostname): |
- """ |
- Returns 2-letter country code (e.g. 'US') for specified hostname. |
- Use this method if you have a Country, Region, or City database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: 2-letter country code |
- @rtype: str |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.country_code_by_addr(addr) |
- |
- def country_name_by_addr(self, addr): |
- """ |
- Returns full country name for specified IP address. |
- Use this method if you have a Country or City database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: country name |
- @rtype: str |
- """ |
- try: |
- VALID_EDITIONS = (const.COUNTRY_EDITION, const.COUNTRY_EDITION_V6) |
- if self._databaseType in VALID_EDITIONS: |
- country_id = self.id_by_addr(addr) |
- return const.COUNTRY_NAMES[country_id] |
- elif self._databaseType in const.CITY_EDITIONS: |
- return self.record_by_addr(addr).get('country_name') |
- else: |
- message = 'Invalid database type, expected Country or City' |
- raise GeoIPError(message) |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def country_name_by_name(self, hostname): |
- """ |
- Returns full country name for specified hostname. |
- Use this method if you have a Country database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: country name |
- @rtype: str |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.country_name_by_addr(addr) |
- |
- def org_by_addr(self, addr): |
- """ |
- Lookup Organization, ISP or ASNum for given IP address. |
- Use this method if you have an Organization, ISP or ASNum database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: organization or ISP name |
- @rtype: str |
- """ |
- try: |
- ipnum = util.ip2long(addr) |
- if not ipnum: |
- raise ValueError('Invalid IP address') |
- |
- valid = (const.ORG_EDITION, const.ISP_EDITION, const.ASNUM_EDITION, const.ASNUM_EDITION_V6) |
- if self._databaseType not in valid: |
- message = 'Invalid database type, expected Org, ISP or ASNum' |
- raise GeoIPError(message) |
- |
- return self._get_org(ipnum) |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def org_by_name(self, hostname): |
- """ |
- Lookup the organization (or ISP) for hostname. |
- Use this method if you have an Organization/ISP database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: Organization or ISP name |
- @rtype: str |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.org_by_addr(addr) |
- |
- def record_by_addr(self, addr): |
- """ |
- Look up the record for a given IP address. |
- Use this method if you have a City database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: Dictionary with country_code, country_code3, country_name, |
- region, city, postal_code, latitude, longitude, dma_code, |
- metro_code, area_code, region_name, time_zone |
- @rtype: dict |
- """ |
- try: |
- ipnum = util.ip2long(addr) |
- if not ipnum: |
- raise ValueError('Invalid IP address') |
- |
- if self._databaseType not in const.CITY_EDITIONS: |
- message = 'Invalid database type, expected City' |
- raise GeoIPError(message) |
- |
- rec = self._get_record(ipnum) |
- if not rec: |
- return None |
- |
- return rec |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def record_by_name(self, hostname): |
- """ |
- Look up the record for a given hostname. |
- Use this method if you have a City database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: Dictionary with country_code, country_code3, country_name, |
- region, city, postal_code, latitude, longitude, dma_code, |
- metro_code, area_code, region_name, time_zone |
- @rtype: dict |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.record_by_addr(addr) |
- |
- def region_by_addr(self, addr): |
- """ |
- Lookup the region for given IP address. |
- Use this method if you have a Region database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: Dictionary containing country_code, region and region_name |
- @rtype: dict |
- """ |
- try: |
- ipnum = util.ip2long(addr) |
- if not ipnum: |
- raise ValueError('Invalid IP address') |
- |
- if self._databaseType not in const.REGION_CITY_EDITIONS: |
- message = 'Invalid database type, expected Region or City' |
- raise GeoIPError(message) |
- |
- return self._get_region(ipnum) |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def region_by_name(self, hostname): |
- """ |
- Lookup the region for given hostname. |
- Use this method if you have a Region database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: Dictionary containing country_code, region, and region_name |
- @rtype: dict |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.region_by_addr(addr) |
- |
- def time_zone_by_addr(self, addr): |
- """ |
- Look up the time zone for a given IP address. |
- Use this method if you have a Region or City database. |
- |
- @param addr: IP address |
- @type addr: str |
- @return: Time zone |
- @rtype: str |
- """ |
- try: |
- ipnum = util.ip2long(addr) |
- if not ipnum: |
- raise ValueError('Invalid IP address') |
- |
- if self._databaseType not in const.CITY_EDITIONS: |
- message = 'Invalid database type, expected City' |
- raise GeoIPError(message) |
- |
- return self._get_record(ipnum).get('time_zone') |
- except ValueError: |
- raise GeoIPError('Failed to lookup address %s' % addr) |
- |
- def time_zone_by_name(self, hostname): |
- """ |
- Look up the time zone for a given hostname. |
- Use this method if you have a Region or City database. |
- |
- @param hostname: Hostname |
- @type hostname: str |
- @return: Time zone |
- @rtype: str |
- """ |
- addr = self._gethostbyname(hostname) |
- return self.time_zone_by_addr(addr) |