| 63 | | |
|---|
| 64 | | # The flags for GeoIP memory caching. |
|---|
| 65 | | # GEOIP_STANDARD - read database from filesystem, uses least memory. |
|---|
| 66 | | # |
|---|
| 67 | | # GEOIP_MEMORY_CACHE - load database into memory, faster performance |
|---|
| 68 | | # but uses more memory |
|---|
| 69 | | # |
|---|
| 70 | | # GEOIP_CHECK_CACHE - check for updated database. If database has been updated, |
|---|
| 71 | | # reload filehandle and/or memory cache. |
|---|
| 72 | | # |
|---|
| 73 | | # GEOIP_INDEX_CACHE - just cache |
|---|
| 74 | | # the most frequently accessed index portion of the database, resulting |
|---|
| 75 | | # in faster lookups than GEOIP_STANDARD, but less memory usage than |
|---|
| 76 | | # GEOIP_MEMORY_CACHE - useful for larger databases such as |
|---|
| 77 | | # GeoIP Organization and GeoIP City. Note, for GeoIP Country, Region |
|---|
| 78 | | # and Netspeed databases, GEOIP_INDEX_CACHE is equivalent to GEOIP_MEMORY_CACHE |
|---|
| 79 | | # |
|---|
| 80 | | cache_options = {0 : c_int(0), # GEOIP_STANDARD |
|---|
| 81 | | 1 : c_int(1), # GEOIP_MEMORY_CACHE |
|---|
| 82 | | 2 : c_int(2), # GEOIP_CHECK_CACHE |
|---|
| 83 | | 4 : c_int(4), # GEOIP_INDEX_CACHE |
|---|
| 84 | | } |
|---|
| 85 | | |
|---|
| 86 | | # GeoIPRecord C Structure definition. |
|---|
| | 74 | free_regex = re.compile(r'^GEO-\d{3}FREE') |
|---|
| | 75 | lite_regex = re.compile(r'^GEO-\d{3}LITE') |
|---|
| | 76 | |
|---|
| | 77 | #### GeoIP C Structure definitions #### |
|---|
| 99 | | |
|---|
| 100 | | # ctypes function prototypes |
|---|
| 101 | | record_by_addr = lgeoip.GeoIP_record_by_addr |
|---|
| 102 | | record_by_addr.restype = POINTER(GeoIPRecord) |
|---|
| 103 | | record_by_name = lgeoip.GeoIP_record_by_name |
|---|
| 104 | | record_by_name.restype = POINTER(GeoIPRecord) |
|---|
| 105 | | |
|---|
| 106 | | # The exception class for GeoIP Errors. |
|---|
| 107 | | class GeoIPException(Exception): pass |
|---|
| 108 | | |
|---|
| | 90 | class GeoIPTag(Structure): pass |
|---|
| | 91 | |
|---|
| | 92 | #### ctypes function prototypes #### |
|---|
| | 93 | RECTYPE = POINTER(GeoIPRecord) |
|---|
| | 94 | DBTYPE = POINTER(GeoIPTag) |
|---|
| | 95 | |
|---|
| | 96 | # For retrieving records by name or address. |
|---|
| | 97 | def record_output(func): |
|---|
| | 98 | func.restype = RECTYPE |
|---|
| | 99 | return func |
|---|
| | 100 | rec_by_addr = record_output(lgeoip.GeoIP_record_by_addr) |
|---|
| | 101 | rec_by_name = record_output(lgeoip.GeoIP_record_by_name) |
|---|
| | 102 | |
|---|
| | 103 | # For opening up GeoIP databases. |
|---|
| | 104 | geoip_open = lgeoip.GeoIP_open |
|---|
| | 105 | geoip_open.restype = DBTYPE |
|---|
| | 106 | |
|---|
| | 107 | # String output routines. |
|---|
| | 108 | def string_output(func): |
|---|
| | 109 | func.restype = c_char_p |
|---|
| | 110 | return func |
|---|
| | 111 | geoip_dbinfo = string_output(lgeoip.GeoIP_database_info) |
|---|
| | 112 | cntry_code_by_addr = string_output(lgeoip.GeoIP_country_code_by_addr) |
|---|
| | 113 | cntry_code_by_name = string_output(lgeoip.GeoIP_country_code_by_name) |
|---|
| | 114 | cntry_name_by_addr = string_output(lgeoip.GeoIP_country_name_by_addr) |
|---|
| | 115 | cntry_name_by_name = string_output(lgeoip.GeoIP_country_name_by_name) |
|---|
| | 116 | |
|---|
| | 117 | #### GeoIP class #### |
|---|
| 110 | | def __init__(self, path=None, country=None, city=None, |
|---|
| 111 | | cache=0): |
|---|
| | 119 | # The flags for GeoIP memory caching. |
|---|
| | 120 | # GEOIP_STANDARD - read database from filesystem, uses least memory. |
|---|
| | 121 | # |
|---|
| | 122 | # GEOIP_MEMORY_CACHE - load database into memory, faster performance |
|---|
| | 123 | # but uses more memory |
|---|
| | 124 | # |
|---|
| | 125 | # GEOIP_CHECK_CACHE - check for updated database. If database has been updated, |
|---|
| | 126 | # reload filehandle and/or memory cache. |
|---|
| | 127 | # |
|---|
| | 128 | # GEOIP_INDEX_CACHE - just cache |
|---|
| | 129 | # the most frequently accessed index portion of the database, resulting |
|---|
| | 130 | # in faster lookups than GEOIP_STANDARD, but less memory usage than |
|---|
| | 131 | # GEOIP_MEMORY_CACHE - useful for larger databases such as |
|---|
| | 132 | # GeoIP Organization and GeoIP City. Note, for GeoIP Country, Region |
|---|
| | 133 | # and Netspeed databases, GEOIP_INDEX_CACHE is equivalent to GEOIP_MEMORY_CACHE |
|---|
| | 134 | # |
|---|
| | 135 | GEOIP_STANDARD = 0 |
|---|
| | 136 | GEOIP_MEMORY_CACHE = 1 |
|---|
| | 137 | GEOIP_CHECK_CACHE = 2 |
|---|
| | 138 | GEOIP_INDEX_CACHE = 4 |
|---|
| | 139 | cache_options = dict((opt, None) for opt in (0, 1, 2, 4)) |
|---|
| | 140 | |
|---|
| | 141 | def __init__(self, path=None, cache=0, country=None, city=None): |
|---|
| 140 | | try: |
|---|
| 141 | | self._path = settings.GEOIP_PATH |
|---|
| 142 | | except AttributeError: |
|---|
| 143 | | raise GeoIPException('Must specify GEOIP_PATH in your settings.py') |
|---|
| 144 | | else: |
|---|
| 145 | | self._path = path |
|---|
| 146 | | if not os.path.isdir(self._path): |
|---|
| 147 | | raise GeoIPException('GEOIP_PATH must be set to a directory.') |
|---|
| 148 | | |
|---|
| 149 | | # Getting the GeoIP country data file. |
|---|
| 150 | | if not country: |
|---|
| 151 | | try: |
|---|
| 152 | | cntry_file = settings.GEOIP_COUNTRY |
|---|
| 153 | | except AttributeError: |
|---|
| 154 | | cntry_file = 'GeoIP.dat' |
|---|
| 155 | | else: |
|---|
| 156 | | cntry_file = country |
|---|
| 157 | | self._country_file = os.path.join(self._path, cntry_file) |
|---|
| 158 | | |
|---|
| 159 | | # Getting the GeoIP city data file. |
|---|
| 160 | | if not city: |
|---|
| 161 | | try: |
|---|
| 162 | | city_file = settings.GEOIP_CITY |
|---|
| 163 | | except AttributeError: |
|---|
| 164 | | city_file = 'GeoLiteCity.dat' |
|---|
| 165 | | else: |
|---|
| 166 | | city_file = city |
|---|
| 167 | | self._city_file = os.path.join(self._path, city_file) |
|---|
| 168 | | |
|---|
| 169 | | # Opening up the GeoIP country data file. |
|---|
| 170 | | if os.path.isfile(self._country_file): |
|---|
| 171 | | self._country = lgeoip.GeoIP_open(c_char_p(self._country_file), self._cache) |
|---|
| 172 | | else: |
|---|
| 173 | | self._country = None |
|---|
| 174 | | |
|---|
| 175 | | # Opening the GeoIP city data file. |
|---|
| 176 | | if os.path.isfile(self._city_file): |
|---|
| 177 | | self._city = lgeoip.GeoIP_open(c_char_p(self._city_file), self._cache) |
|---|
| 178 | | else: |
|---|
| 179 | | self._city = None |
|---|
| 180 | | |
|---|
| 181 | | def country(self, query): |
|---|
| 182 | | """ |
|---|
| 183 | | Returns a dictonary with with the country code and name when given an |
|---|
| 184 | | IP address or a Fully Qualified Domain Name (FQDN). For example, both |
|---|
| 185 | | '24.124.1.80' and 'djangoproject.com' are valid parameters. |
|---|
| 186 | | """ |
|---|
| 187 | | if self._country is None: |
|---|
| | 173 | path = GEOIP_SETTINGS.get('GEOIP_PATH', None) |
|---|
| | 174 | if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.') |
|---|
| | 175 | if not isinstance(path, basestring): |
|---|
| | 176 | raise TypeError('Invalid path type: %s' % type(path).__name__) |
|---|
| | 177 | |
|---|
| | 178 | cntry_ptr, city_ptr = (None, None) |
|---|
| | 179 | if os.path.isdir(path): |
|---|
| | 180 | # Getting the country and city files using the settings |
|---|
| | 181 | # dictionary. If no settings are provided, default names |
|---|
| | 182 | # are assigned. |
|---|
| | 183 | country = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat')) |
|---|
| | 184 | city = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat')) |
|---|
| | 185 | elif os.path.isfile(path): |
|---|
| | 186 | # Otherwise, some detective work will be needed to figure |
|---|
| | 187 | # out whether the given database path is for the GeoIP country |
|---|
| | 188 | # or city databases. |
|---|
| | 189 | ptr = geoip_open(path, cache) |
|---|
| | 190 | info = geoip_dbinfo(ptr) |
|---|
| | 191 | if lite_regex.match(info): |
|---|
| | 192 | # GeoLite City database. |
|---|
| | 193 | city, city_ptr = path, ptr |
|---|
| | 194 | elif free_regex.match(info): |
|---|
| | 195 | # GeoIP Country database. |
|---|
| | 196 | country, cntry_ptr = path, ptr |
|---|
| | 197 | else: |
|---|
| | 198 | raise GeoIPException('Unable to recognize database edition: %s' % info) |
|---|
| | 199 | else: |
|---|
| | 200 | raise GeoIPException('GeoIP path must be a valid file or directory.') |
|---|
| | 201 | |
|---|
| | 202 | # `_init_db` does the dirty work. |
|---|
| | 203 | self._init_db(country, cache, '_country', cntry_ptr) |
|---|
| | 204 | self._init_db(city, cache, '_city', city_ptr) |
|---|
| | 205 | |
|---|
| | 206 | def _init_db(self, db_file, cache, attname, ptr=None): |
|---|
| | 207 | "Helper routine for setting GeoIP ctypes database properties." |
|---|
| | 208 | if ptr: |
|---|
| | 209 | # Pointer already retrieved. |
|---|
| | 210 | pass |
|---|
| | 211 | elif os.path.isfile(db_file or ''): |
|---|
| | 212 | ptr = geoip_open(db_file, cache) |
|---|
| | 213 | setattr(self, attname, ptr) |
|---|
| | 214 | setattr(self, '%s_file' % attname, db_file) |
|---|
| | 215 | |
|---|
| | 216 | def _check_query(self, query, country=False, city=False, city_or_country=False): |
|---|
| | 217 | "Helper routine for checking the query and database availability." |
|---|
| | 218 | # Making sure a string was passed in for the query. |
|---|
| | 219 | if not isinstance(query, basestring): |
|---|
| | 220 | raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__) |
|---|
| | 221 | |
|---|
| | 222 | # Extra checks for the existence of country and city databases. |
|---|
| | 223 | if city_or_country and self._country is None and self._city is None: |
|---|
| | 224 | raise GeoIPException('Invalid GeoIP country and city data files.') |
|---|
| | 225 | elif country and self._country is None: |
|---|
| 189 | | |
|---|
| 190 | | if ipregex.match(query): |
|---|
| 191 | | # If an IP address was passed in. |
|---|
| 192 | | code = lgeoip.GeoIP_country_code_by_addr(self._country, c_char_p(query)) |
|---|
| 193 | | name = lgeoip.GeoIP_country_name_by_addr(self._country, c_char_p(query)) |
|---|
| 194 | | else: |
|---|
| 195 | | # If a FQDN was passed in. |
|---|
| 196 | | code = lgeoip.GeoIP_country_code_by_name(self._country, c_char_p(query)) |
|---|
| 197 | | name = lgeoip.GeoIP_country_name_by_name(self._country, c_char_p(query)) |
|---|
| 198 | | |
|---|
| 199 | | # Checking our returned country code and name, setting each to |
|---|
| 200 | | # None, if pointer is invalid. |
|---|
| 201 | | if bool(code): code = string_at(code) |
|---|
| 202 | | else: code = None |
|---|
| 203 | | if bool(name): name = string_at(name) |
|---|
| 204 | | else: name = None |
|---|
| 205 | | |
|---|
| 206 | | # Returning the country code and name |
|---|
| 207 | | return {'country_code' : code, |
|---|
| 208 | | 'country_name' : name, |
|---|
| 209 | | } |
|---|
| | 227 | elif city and self._city is None: |
|---|
| | 228 | raise GeoIPException('Invalid GeoIP city data file: %s' % self._city_file) |
|---|
| | 251 | |
|---|
| | 252 | def country_code(self, query): |
|---|
| | 253 | "Returns the country code for the given IP Address or FQDN." |
|---|
| | 254 | self._check_query(query, city_or_country=True) |
|---|
| | 255 | if self._country: |
|---|
| | 256 | if ipregex.match(query): return cntry_code_by_addr(self._country, query) |
|---|
| | 257 | else: return cntry_code_by_name(self._country, query) |
|---|
| | 258 | else: |
|---|
| | 259 | return self.city(query)['country_code'] |
|---|
| | 260 | |
|---|
| | 261 | def country_name(self, query): |
|---|
| | 262 | "Returns the country name for the given IP Address or FQDN." |
|---|
| | 263 | self._check_query(query, city_or_country=True) |
|---|
| | 264 | if self._country: |
|---|
| | 265 | if ipregex.match(query): return cntry_name_by_addr(self._country, query) |
|---|
| | 266 | else: return cntry_name_by_name(self._country, query) |
|---|
| | 267 | else: |
|---|
| | 268 | return self.city(query)['country_name'] |
|---|
| | 269 | |
|---|
| | 270 | def country(self, query): |
|---|
| | 271 | """ |
|---|
| | 272 | Returns a dictonary with with the country code and name when given an |
|---|
| | 273 | IP address or a Fully Qualified Domain Name (FQDN). For example, both |
|---|
| | 274 | '24.124.1.80' and 'djangoproject.com' are valid parameters. |
|---|
| | 275 | """ |
|---|
| | 276 | # Returning the country code and name |
|---|
| | 277 | return {'country_code' : self.country_code(query), |
|---|
| | 278 | 'country_name' : self.country_name(query), |
|---|
| | 279 | } |
|---|