maxminddb-0.13.0/Cargo.toml.orig010064400017500001750000000017031342141221400146410ustar0000000000000000[package] name = "maxminddb" version = "0.13.0" authors = [ "Gregory J. Oschwald " ] description = "Library for reading MaxMind DB format used by GeoIP2 and GeoLite2" readme = "README.md" keywords = ["MaxMind", "GeoIP2", "GeoIP", "geolocation", "ip"] categories = ["database", "network-programming"] homepage = "https://github.com/oschwald/maxminddb-rust" documentation = "http://oschwald.github.io/maxminddb-rust/maxminddb/struct.Reader.html" repository = "https://github.com/oschwald/maxminddb-rust" license = "ISC" include = ["/Cargo.toml", "/src/**/*.rs", "/README.md", "/LICENSE"] [features] default = [] mmap = ["memmap"] [lib] name ="maxminddb" crate_type = ["lib", "dylib", "staticlib"] path = "src/maxminddb/lib.rs" [dependencies] log = "0.4" serde = "1.0" serde_derive = "1.0" memmap = { version = "0.7.0", optional = true } [dev-dependencies] env_logger = "0.5" [badges] travis-ci = { repository = "oschwald/maxminddb-rust" } maxminddb-0.13.0/Cargo.toml0000644000000030150000000000000111140ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "maxminddb" version = "0.13.0" authors = ["Gregory J. Oschwald "] include = ["/Cargo.toml", "/src/**/*.rs", "/README.md", "/LICENSE"] description = "Library for reading MaxMind DB format used by GeoIP2 and GeoLite2" homepage = "https://github.com/oschwald/maxminddb-rust" documentation = "http://oschwald.github.io/maxminddb-rust/maxminddb/struct.Reader.html" readme = "README.md" keywords = ["MaxMind", "GeoIP2", "GeoIP", "geolocation", "ip"] categories = ["database", "network-programming"] license = "ISC" repository = "https://github.com/oschwald/maxminddb-rust" [lib] name = "maxminddb" crate_type = ["lib", "dylib", "staticlib"] path = "src/maxminddb/lib.rs" [dependencies.log] version = "0.4" [dependencies.memmap] version = "0.7.0" optional = true [dependencies.serde] version = "1.0" [dependencies.serde_derive] version = "1.0" [dev-dependencies.env_logger] version = "0.5" [features] default = [] mmap = ["memmap"] [badges.travis-ci] repository = "oschwald/maxminddb-rust" maxminddb-0.13.0/LICENSE010064400017500001750000000014041301222650100127530ustar0000000000000000ISC License Copyright (c) 2015, Gregory J. Oschwald Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. maxminddb-0.13.0/README.md010064400017500001750000000025771333243421200132460ustar0000000000000000# Rust MaxMind DB Reader # [![Build Status](https://travis-ci.org/oschwald/maxminddb-rust.svg?branch=master)](https://travis-ci.org/oschwald/maxminddb-rust) [![crates.io]( https://img.shields.io/crates/v/maxminddb.svg)](https://crates.io/crates/maxminddb) [![Released API docs](https://docs.rs/maxminddb/badge.svg)](http://docs.rs/maxminddb) [![Master API docs](https://img.shields.io/badge/docs-master-green.svg)](https://oschwald.github.io/maxminddb-rust/) This library reads the MaxMind DB format, including the GeoIP2 and GeoLite2 databases. ## Building ## To build everything: ``` cargo build ``` ## Testing ## This crate manages its test data within a git submodule. To run the tests, you will first need to run the following command. ```bash git submodule update --init ``` ## Usage ## Add this to your `Cargo.toml`: ```toml [dependencies] maxminddb = "0.8.1" ``` and this to your crate root: ```rust extern crate maxminddb; ``` ## API Documentation ## The API docs are on [GitHub Pages](http://oschwald.github.io/maxminddb-rust/maxminddb/struct.Reader.html). ## Example ## See [`examples/lookup.rs`](https://github.com/oschwald/maxminddb-rust/blob/master/examples/lookup.rs) for a basic example. ## Contributing ## Contributions welcome! Please fork the repository and open a pull request with your changes. ## License ## This is free software, licensed under the ISC license. maxminddb-0.13.0/src/maxminddb/decoder.rs010064400017500001750000000254701337242373700165070ustar0000000000000000use std::collections::BTreeMap; use std::string; use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; use super::MaxMindDBError; use super::MaxMindDBError::DecodingError; pub type DbArray = Vec; pub type DbMap = BTreeMap; #[derive(Clone, Debug, PartialEq)] pub enum DataRecord { String(string::String), Double(f64), Byte(u8), Uint16(u16), Uint32(u32), Map(Box), Int32(i32), Uint64(u64), Boolean(bool), Array(DbArray), Float(f32), Null, } use self::DataRecord::{ Array, Boolean, Byte, Double, Float, Int32, Map, Null, String, Uint16, Uint32, Uint64, }; macro_rules! expect( ($e:expr, $t:ident) => ({ match $e { $t(v) => Ok(v), other => Err(DecodingError(format!("Error decoding {:?} as {:?}", other, stringify!($t)))) } }) ); #[derive(Debug)] pub struct Decoder { stack: Vec, } impl Decoder { /// Creates a new decoder instance for decoding the specified JSON value. pub fn new(record: DataRecord) -> Decoder { Decoder { stack: vec![record], } } } impl Decoder { fn pop(&mut self) -> DataRecord { self.stack.pop().unwrap() } fn peek(&self) -> Option<&DataRecord> { self.stack.get(self.stack.len() - 1) } } pub type DecodeResult = Result; // Much of this code was borrowed from the Rust JSON library, Serde Deserializer example impl<'de, 'a> de::Deserializer<'de> for &'a mut Decoder { type Error = MaxMindDBError; #[inline] fn deserialize_any(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("deserialize_any"); match self.peek() { Some(&String(_)) => self.deserialize_str(visitor), Some(&Double(_)) => self.deserialize_f64(visitor), Some(&Byte(_)) => self.deserialize_u8(visitor), Some(&Uint16(_)) => self.deserialize_u16(visitor), Some(&Uint32(_)) => self.deserialize_u32(visitor), Some(&Map(_)) => self.deserialize_map(visitor), Some(&Int32(_)) => self.deserialize_i32(visitor), Some(&Uint64(_)) => self.deserialize_u64(visitor), Some(&Boolean(_)) => self.deserialize_bool(visitor), Some(&Array(_)) => self.deserialize_seq(visitor), Some(&Float(_)) => self.deserialize_f32(visitor), Some(&Null) => self.deserialize_unit(visitor), None => Err(DecodingError("nothing left to deserialize".to_owned())), } } fn deserialize_u64(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_u64"); visitor.visit_u64(expect!(self.pop(), Uint64)?) } fn deserialize_u32(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_u32"); visitor.visit_u32(expect!(self.pop(), Uint32)?) } fn deserialize_u16(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_u16"); visitor.visit_u16(expect!(self.pop(), Uint16)?) } fn deserialize_u8(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_u8"); visitor.visit_u8(expect!(self.pop(), Byte)?) } fn deserialize_i64(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_i64"); self.deserialize_i32(visitor) } fn deserialize_i32(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_i32"); visitor.visit_i32(expect!(self.pop(), Int32)?) } fn deserialize_bool(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_bool"); visitor.visit_bool(expect!(self.pop(), Boolean)?) } fn deserialize_f64(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_f64"); visitor.visit_f64(expect!(self.pop(), Double)?) } fn deserialize_f32(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_f32"); visitor.visit_f32(expect!(self.pop(), Float)?) } fn deserialize_str(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { let string = expect!(self.pop(), String)?; debug!("read_str: {}", string); visitor.visit_str(&string) } fn deserialize_string(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_string"); self.deserialize_str(visitor) } // Structs look just like maps in JSON. // // Notice the `fields` parameter - a "struct" in the Serde data model means // that the `Deserialize` implementation is required to know what the fields // are before even looking at the input data. Any key-value pairing in which // the fields cannot be known ahead of time is probably a map. fn deserialize_struct( self, _name: &'static str, _fields: &'static [&'static str], visitor: V, ) -> DecodeResult where V: Visitor<'de>, { self.deserialize_map(visitor) } // Tuples look just like sequences in JSON. Some formats may be able to // represent tuples more efficiently. // // As indicated by the length parameter, the `Deserialize` implementation // for a tuple in the Serde data model is required to know the length of the // tuple before even looking at the input data. fn deserialize_tuple(self, _len: usize, visitor: V) -> DecodeResult where V: Visitor<'de>, { self.deserialize_seq(visitor) } // Tuple structs look just like sequences in JSON. fn deserialize_tuple_struct( self, _name: &'static str, _len: usize, visitor: V, ) -> DecodeResult where V: Visitor<'de>, { self.deserialize_seq(visitor) } fn deserialize_option(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_option()"); match self.pop() { Null => visitor.visit_none(), value => { self.stack.push(value); visitor.visit_some(self) } } } // In Serde, unit means an anonymous value containing no data. fn deserialize_unit(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_nil"); match self.pop() { Null => visitor.visit_unit(), other => Err(DecodingError(format!("Error decoding Null as {:?}", other))), } } // Unit struct means a named value containing no data. fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> DecodeResult where V: Visitor<'de>, { self.deserialize_unit(visitor) } // As is done here, serializers are encouraged to treat newtype structs as // insignificant wrappers around the data they contain. That means not // parsing anything other than the contained value. fn deserialize_newtype_struct( self, _name: &'static str, visitor: V, ) -> DecodeResult where V: Visitor<'de>, { visitor.visit_newtype_struct(self) } // An identifier in Serde is the type that identifies a field of a struct or // the variant of an enum. fn deserialize_identifier(self, visitor: V) -> DecodeResult where V: Visitor<'de>, { self.deserialize_str(visitor) } fn deserialize_seq(mut self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_seq()"); let list = expect!(self.pop(), Array)?; let len = list.len(); for v in list.into_iter().rev() { self.stack.push(v); } let value = visitor.visit_seq(ArrayAccess::new(&mut self, len))?; Ok(value) } // Much like `deserialize_seq` but calls the visitors `visit_map` method // with a `MapAccess` implementation, rather than the visitor's `visit_seq` // method with a `SeqAccess` implementation. fn deserialize_map(mut self, visitor: V) -> DecodeResult where V: Visitor<'de>, { debug!("read_map()"); let obj = expect!(self.pop(), Map)?; let len = obj.len(); for (key, value) in obj.into_iter() { self.stack.push(value); self.stack.push(String(key)); } let value = visitor.visit_map(MapAccessor::new(&mut self, len * 2))?; Ok(value) } forward_to_deserialize_any! { bytes byte_buf char enum i8 i16 ignored_any } } struct ArrayAccess<'a> { de: &'a mut Decoder, count: usize, } impl<'a> ArrayAccess<'a> { fn new(de: &'a mut Decoder, count: usize) -> Self { ArrayAccess { de, count } } } // `SeqAccess` is provided to the `Visitor` to give it the ability to iterate // through elements of the sequence. impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a> { type Error = MaxMindDBError; fn next_element_seed(&mut self, seed: T) -> DecodeResult> where T: DeserializeSeed<'de>, { // Check if there are no more elements. if self.count == 0 { return Ok(None); } self.count -= 1; // Deserialize an array element. seed.deserialize(&mut *self.de).map(Some) } } struct MapAccessor<'a> { de: &'a mut Decoder, count: usize, } impl<'a> MapAccessor<'a> { fn new(de: &'a mut Decoder, count: usize) -> Self { MapAccessor { de, count } } } // `MapAccess` is provided to the `Visitor` to give it the ability to iterate // through entries of the map. impl<'de, 'a> MapAccess<'de> for MapAccessor<'a> { type Error = MaxMindDBError; fn next_key_seed(&mut self, seed: K) -> DecodeResult> where K: DeserializeSeed<'de>, { // Check if there are no more entries. if self.count == 0 { return Ok(None); } self.count -= 1; // Deserialize a map key. seed.deserialize(&mut *self.de).map(Some) } fn next_value_seed(&mut self, seed: V) -> DecodeResult where V: DeserializeSeed<'de>, { // Check if there are no more entries. if self.count == 0 { return Err(DecodingError("no more entries".to_owned())); } self.count -= 1; // Deserialize a map value. seed.deserialize(&mut *self.de) } } maxminddb-0.13.0/src/maxminddb/geoip2.rs010064400017500001750000000072621342141030600162450ustar0000000000000000/// GeoIP2 Country record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Country { pub continent: Option, pub country: Option, pub registered_country: Option, pub represented_country: Option, pub traits: Option, } /// GeoIP2 City record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct City { pub city: Option, pub continent: Option, pub country: Option, pub location: Option, pub postal: Option, pub registered_country: Option, pub represented_country: Option, pub subdivisions: Option>, pub traits: Option, } /// GeoIP2 ISP record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Isp { pub autonomous_system_number: Option, pub autonomous_system_organization: Option, pub isp: Option, pub organization: Option, } /// GeoIP2 Connection-Type record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct ConnectionType { pub connection_type: Option, } /// GeoIP2 Anonymous Ip record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct AnonymousIp { pub is_anonymous: Option, pub is_anonymous_vpn: Option, pub is_hosting_provider: Option, pub is_public_proxy: Option, pub is_tor_exit_node: Option, } /// GeoIP2 DensityIncome record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct DensityIncome { pub average_income: Option, pub population_density: Option, } /// GeoIP2 Domain record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Domain { pub domain: Option, } /// GeoIP2 Asn record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Asn { pub autonomous_system_number: Option, pub autonomous_system_organization: Option, } pub mod model { use std::collections::BTreeMap; #[derive(Deserialize, Serialize, Clone, Debug)] pub struct City { pub geoname_id: Option, pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Continent { pub code: Option, pub geoname_id: Option, pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Country { pub geoname_id: Option, pub is_in_european_union: Option, pub iso_code: Option, pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Location { pub latitude: Option, pub longitude: Option, pub metro_code: Option, pub time_zone: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Postal { pub code: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct RepresentedCountry { pub geoname_id: Option, pub iso_code: Option, pub names: Option>, // pub type: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Subdivision { pub geoname_id: Option, pub iso_code: Option, pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Traits { pub is_anonymous_proxy: Option, pub is_satellite_provider: Option, } } maxminddb-0.13.0/src/maxminddb/lib.rs010064400017500001750000000540241342141031100156200ustar0000000000000000#![crate_name = "maxminddb"] #![crate_type = "dylib"] #![crate_type = "rlib"] #![deny( trivial_casts, trivial_numeric_casts, unstable_features, unused_import_braces )] #[macro_use] extern crate log; #[cfg(feature = "mmap")] extern crate memmap; #[macro_use] extern crate serde; #[macro_use] extern crate serde_derive; use std::collections::BTreeMap; use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::io; use std::net::IpAddr; #[cfg(feature = "mmap")] use memmap::Mmap; #[cfg(feature = "mmap")] use memmap::MmapOptions; use serde::{de, Deserialize}; #[cfg(feature = "mmap")] use std::fs::File; use std::path::Path; #[derive(Debug, PartialEq)] pub enum MaxMindDBError { AddressNotFoundError(String), InvalidDatabaseError(String), IoError(String), MapError(String), DecodingError(String), } impl From for MaxMindDBError { fn from(err: io::Error) -> MaxMindDBError { // clean up and clean up MaxMindDBError generally MaxMindDBError::IoError(err.description().to_owned()) } } impl Display for MaxMindDBError { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { match self { MaxMindDBError::AddressNotFoundError(msg) => { write!(fmt, "AddressNotFoundError: {}", msg)? } MaxMindDBError::InvalidDatabaseError(msg) => { write!(fmt, "InvalidDatabaseError: {}", msg)? } MaxMindDBError::IoError(msg) => write!(fmt, "IoError: {}", msg)?, MaxMindDBError::MapError(msg) => write!(fmt, "MapError: {}", msg)?, MaxMindDBError::DecodingError(msg) => write!(fmt, "DecodingError: {}", msg)?, } Ok(()) } } // Use default implementation for `std::error::Error` impl std::error::Error for MaxMindDBError {} impl de::Error for MaxMindDBError { fn custom(msg: T) -> Self { MaxMindDBError::DecodingError(format!("{}", msg)) } } type BinaryDecodeResult = (Result, usize); #[derive(Deserialize, Debug)] pub struct Metadata { pub binary_format_major_version: u16, pub binary_format_minor_version: u16, pub build_epoch: u64, pub database_type: String, pub description: BTreeMap, pub ip_version: u16, pub languages: Vec, pub node_count: u32, pub record_size: u16, } struct BinaryDecoder> { buf: T, pointer_base: usize, } impl> BinaryDecoder { fn decode_array(&self, size: usize, offset: usize) -> BinaryDecodeResult { let mut array = Vec::new(); let mut new_offset = offset; for _ in 0..size { let (val, tmp_offset) = match self.decode(new_offset) { (Ok(v), os) => (v, os), (Err(e), os) => return (Err(e), os), }; new_offset = tmp_offset; array.push(val); } (Ok(decoder::DataRecord::Array(array)), new_offset) } fn decode_bool(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { 0 | 1 => (Ok(decoder::DataRecord::Boolean(size != 0)), offset), s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "float of size {:?}", s ))), 0, ), } } fn decode_bytes(&self, size: usize, offset: usize) -> BinaryDecodeResult { let new_offset = offset + size; let u8_slice = &self.buf.as_ref()[offset..new_offset]; let bytes = u8_slice .iter() .map(|&b| decoder::DataRecord::Byte(b)) .collect(); (Ok(decoder::DataRecord::Array(bytes)), new_offset) } fn decode_float(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { 4 => { let new_offset = offset + size; let value = self.buf.as_ref()[offset..new_offset] .iter() .fold(0u32, |acc, &b| (acc << 8) | u32::from(b)); let float_value: f32 = f32::from_bits(value); (Ok(decoder::DataRecord::Float(float_value)), new_offset) } s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "float of size {:?}", s ))), 0, ), } } fn decode_double(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { 8 => { let new_offset = offset + size; let value = self.buf.as_ref()[offset..new_offset] .iter() .fold(0u64, |acc, &b| (acc << 8) | u64::from(b)); let float_value: f64 = f64::from_bits(value); (Ok(decoder::DataRecord::Double(float_value)), new_offset) } s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "double of size {:?}", s ))), 0, ), } } fn decode_uint64(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { s if s <= 8 => { let new_offset = offset + size; let value = self.buf.as_ref()[offset..new_offset] .iter() .fold(0u64, |acc, &b| (acc << 8) | u64::from(b)); (Ok(decoder::DataRecord::Uint64(value)), new_offset) } s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "u64 of size {:?}", s ))), 0, ), } } fn decode_uint32(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { s if s <= 4 => match self.decode_uint64(size, offset) { (Ok(decoder::DataRecord::Uint64(u)), o) => { (Ok(decoder::DataRecord::Uint32(u as u32)), o) } e => e, }, s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "u32 of size {:?}", s ))), 0, ), } } fn decode_uint16(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { s if s <= 4 => match self.decode_uint64(size, offset) { (Ok(decoder::DataRecord::Uint64(u)), o) => { (Ok(decoder::DataRecord::Uint16(u as u16)), o) } e => e, }, s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "u16 of size {:?}", s ))), 0, ), } } fn decode_int(&self, size: usize, offset: usize) -> BinaryDecodeResult { match size { s if s <= 4 => { let new_offset = offset + size; let value = self.buf.as_ref()[offset..new_offset] .iter() .fold(0i32, |acc, &b| (acc << 8) | i32::from(b)); (Ok(decoder::DataRecord::Int32(value)), new_offset) } s => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "int32 of size {:?}", s ))), 0, ), } } fn decode_map(&self, size: usize, offset: usize) -> BinaryDecodeResult { let mut values = Box::new(BTreeMap::new()); let mut new_offset = offset; for _ in 0..size { let (key, val_offset) = match self.decode(new_offset) { (Ok(v), os) => (v, os), (Err(e), os) => return (Err(e), os), }; let (val, tmp_offset) = match self.decode(val_offset) { (Ok(v), os) => (v, os), (Err(e), os) => return (Err(e), os), }; new_offset = tmp_offset; let str_key = match key { decoder::DataRecord::String(s) => s, v => { return ( Err(MaxMindDBError::InvalidDatabaseError(format!( "unexpected map \ key type {:?}", v ))), 0, ) } }; values.insert(str_key, val); } (Ok(decoder::DataRecord::Map(values)), new_offset) } fn decode_pointer( &self, size: usize, offset: usize, ) -> BinaryDecodeResult { let pointer_value_offset = [0, 0, 2048, 526_336, 0]; let pointer_size = ((size >> 3) & 0x3) + 1; let new_offset = offset + pointer_size; let pointer_bytes = &self.buf.as_ref()[offset..new_offset]; let base = if pointer_size == 4 { 0 } else { (size & 0x7) as u8 }; let unpacked = to_usize(base, pointer_bytes); let pointer = unpacked + self.pointer_base + pointer_value_offset[pointer_size]; // XXX fix cast. Use usize everywhere? let (result, _) = self.decode(pointer); (result, new_offset) } fn decode_string(&self, size: usize, offset: usize) -> BinaryDecodeResult { use std::str::from_utf8; let new_offset: usize = offset + size; let bytes = &self.buf.as_ref()[offset..new_offset]; match from_utf8(bytes) { Ok(v) => (Ok(decoder::DataRecord::String(v.to_owned())), new_offset), Err(_) => ( Err(MaxMindDBError::InvalidDatabaseError( "error decoding string".to_owned(), )), new_offset, ), } } fn decode(&self, offset: usize) -> BinaryDecodeResult { let mut new_offset = offset + 1; let ctrl_byte = self.buf.as_ref()[offset]; let mut type_num = ctrl_byte >> 5; // Extended type if type_num == 0 { type_num = self.buf.as_ref()[new_offset] + 7; new_offset += 1; } let (size, value_offset) = self.size_from_ctrl_byte(ctrl_byte, new_offset, type_num); self.decode_from_type(type_num, size, value_offset) } fn size_from_ctrl_byte(&self, ctrl_byte: u8, offset: usize, type_num: u8) -> (usize, usize) { let mut size = (ctrl_byte & 0x1f) as usize; // extended if type_num == 0 { return (size, offset); } let bytes_to_read = if size > 28 { size - 28 } else { 0 }; let new_offset = offset + bytes_to_read; let size_bytes = &self.buf.as_ref()[offset..new_offset]; size = match size { s if s < 29 => s, 29 => 29usize + size_bytes[0] as usize, 30 => 285usize + to_usize(0, size_bytes), _ => 65_821usize + to_usize(0, size_bytes), }; (size, new_offset) } fn decode_from_type( &self, data_type: u8, size: usize, offset: usize, ) -> BinaryDecodeResult { match data_type { 1 => self.decode_pointer(size, offset), 2 => self.decode_string(size, offset), 3 => self.decode_double(size, offset), 4 => self.decode_bytes(size, offset), 5 => self.decode_uint16(size, offset), 6 => self.decode_uint32(size, offset), 7 => self.decode_map(size, offset), 8 => self.decode_int(size, offset), 9 => self.decode_uint64(size, offset), // XXX - this is u128. The return value for this is subject to change. 10 => self.decode_bytes(size, offset), 11 => self.decode_array(size, offset), 14 => self.decode_bool(size, offset), 15 => self.decode_float(size, offset), u => ( Err(MaxMindDBError::InvalidDatabaseError(format!( "Unknown data type: {:?}", u ))), offset, ), } } } /// A reader for the MaxMind DB format. The lifetime `'data` is tied to the lifetime of the underlying buffer holding the contents of the database file. pub struct Reader> { decoder: BinaryDecoder, pub metadata: Metadata, ipv4_start: usize, } #[cfg(feature = "mmap")] impl<'de> Reader { /// Open a MaxMind DB database file by memory mapping it. /// /// # Example /// /// ``` /// let reader = maxminddb::Reader::open_mmap("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap(); /// ``` pub fn open_mmap>(database: P) -> Result, MaxMindDBError> { let file_read = File::open(database)?; let mmap = unsafe { MmapOptions::new().map(&file_read) }?; Reader::from_source(mmap) } } impl<'de> Reader> { /// Open a MaxMind DB database file by loading it into memory. /// /// # Example /// /// ``` /// let reader = maxminddb::Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap(); /// ``` pub fn open_readfile>(database: P) -> Result>, MaxMindDBError> { use std::fs; let buf: Vec = fs::read(&database)?; Reader::from_source(buf) } } impl<'de, S: AsRef<[u8]>> Reader { /// Open a MaxMind DB database from anything that implements AsRef<[u8]> /// /// # Example /// /// ``` /// use std::fs; /// let buf = fs::read("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap(); /// let reader = maxminddb::Reader::from_source(buf).unwrap(); /// ``` pub fn from_source(buf: S) -> Result, MaxMindDBError> { let data_section_separator_size = 16; let metadata_start = find_metadata_start(buf.as_ref())?; let metadata_decoder = BinaryDecoder { buf, pointer_base: metadata_start, }; let raw_metadata = match metadata_decoder.decode(metadata_start) { (Ok(m), _) => m, m => { return Err(MaxMindDBError::InvalidDatabaseError(format!( "metadata of wrong \ type: {:?}", m ))) } }; let mut type_decoder = decoder::Decoder::new(raw_metadata); let metadata = Metadata::deserialize(&mut type_decoder)?; let search_tree_size = (metadata.node_count as usize) * (metadata.record_size as usize) / 4; let decoder = BinaryDecoder { buf: metadata_decoder.buf, pointer_base: search_tree_size + data_section_separator_size, }; let mut reader = Reader { decoder, metadata, ipv4_start: 0, }; reader.ipv4_start = reader.find_ipv4_start()?; Ok(reader) } /// Lookup the socket address in the opened MaxMind DB /// /// Example: /// /// ``` /// use maxminddb::geoip2; /// use std::net::IpAddr; /// use std::str::FromStr; /// /// let reader = maxminddb::Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap(); /// /// let ip: IpAddr = FromStr::from_str("89.160.20.128").unwrap(); /// let city: geoip2::City = reader.lookup(ip).unwrap(); /// print!("{:?}", city); /// ``` pub fn lookup(&self, address: IpAddr) -> Result where T: Deserialize<'de>, { let ip_bytes = ip_to_bytes(address); let pointer = self.find_address_in_tree(&ip_bytes)?; if pointer == 0 { return Err(MaxMindDBError::AddressNotFoundError( "Address not found in database".to_owned(), )); } let rec = self.resolve_data_pointer(pointer)?; let mut decoder = decoder::Decoder::new(rec); T::deserialize(&mut decoder) } fn find_address_in_tree(&self, ip_address: &[u8]) -> Result { let bit_count = ip_address.len() * 8; let mut node = self.start_node(bit_count)?; let node_count = self.metadata.node_count as usize; for i in 0..bit_count { if node >= node_count { break; } let bit = 1 & (ip_address[i >> 3] >> (7 - (i % 8))); node = self.read_node(node, bit as usize)?; } if node == node_count { Ok(0) } else if node > node_count { Ok(node) } else { Err(MaxMindDBError::InvalidDatabaseError( "invalid node in search tree".to_owned(), )) } } fn start_node(&self, length: usize) -> Result { if length == 128 { Ok(0) } else { Ok(self.ipv4_start) } } fn find_ipv4_start(&self) -> Result { if self.metadata.ip_version != 6 { return Ok(0); } // We are looking up an IPv4 address in an IPv6 tree. Skip over the // first 96 nodes. let mut node: usize = 0usize; for _ in 0u8..96 { if node >= self.metadata.node_count as usize { break; } node = self.read_node(node, 0)?; } Ok(node) } fn read_node(&self, node_number: usize, index: usize) -> Result { let buf = self.decoder.buf.as_ref(); let base_offset = node_number * (self.metadata.record_size as usize) / 4; let val = match self.metadata.record_size { 24 => { let offset = base_offset + index * 3; to_usize(0, &buf[offset..offset + 3]) } 28 => { let mut middle = buf[base_offset + 3]; if index != 0 { middle &= 0x0F } else { middle = (0xF0 & middle) >> 4 } let offset = base_offset + index * 4; to_usize(middle, &buf[offset..offset + 3]) } 32 => { let offset = base_offset + index * 4; to_usize(0, &buf[offset..offset + 4]) } s => { return Err(MaxMindDBError::InvalidDatabaseError(format!( "unknown record size: \ {:?}", s ))) } }; Ok(val) } fn resolve_data_pointer(&self, pointer: usize) -> Result { let search_tree_size = (self.metadata.record_size as usize) * (self.metadata.node_count as usize) / 4; let resolved = pointer - (self.metadata.node_count as usize) + search_tree_size; if resolved > self.decoder.buf.as_ref().len() { return Err(MaxMindDBError::InvalidDatabaseError( "the MaxMind DB file's search tree \ is corrupt" .to_owned(), )); } let (record, _) = self.decoder.decode(resolved); record } } // I haven't moved all patterns of this form to a generic function as // the FromPrimitive trait is unstable fn to_usize(base: u8, bytes: &[u8]) -> usize { bytes .iter() .fold(base as usize, |acc, &b| (acc << 8) | b as usize) } fn ip_to_bytes(address: IpAddr) -> Vec { match address { IpAddr::V4(a) => a.octets().to_vec(), IpAddr::V6(a) => a.octets().to_vec(), } } fn find_metadata_start(buf: &[u8]) -> Result { // This is reversed to make the loop below a bit simpler let metadata_start_marker: [u8; 14] = [ 0x6d, 0x6f, 0x63, 0x2e, 0x64, 0x6e, 0x69, 0x4d, 0x78, 0x61, 0x4d, 0xEF, 0xCD, 0xAB, ]; let marker_length = metadata_start_marker.len(); // XXX - ugly code for start_position in marker_length..buf.len() - 1 { let mut not_found = false; for (offset, marker_byte) in metadata_start_marker.iter().enumerate() { let file_byte = buf[buf.len() - start_position - offset - 1]; if file_byte != *marker_byte { not_found = true; break; } } if !not_found { return Ok(buf.len() - start_position); } } Err(MaxMindDBError::InvalidDatabaseError( "Could not find MaxMind DB metadata in file.".to_owned(), )) } mod decoder; pub mod geoip2; #[cfg(test)] mod reader_test; #[cfg(test)] mod tests { use super::MaxMindDBError; #[test] fn test_error_display() { assert_eq!( format!( "{}", MaxMindDBError::AddressNotFoundError("something went wrong".to_owned()) ), "AddressNotFoundError: something went wrong".to_owned(), ); assert_eq!( format!( "{}", MaxMindDBError::InvalidDatabaseError("something went wrong".to_owned()) ), "InvalidDatabaseError: something went wrong".to_owned(), ); assert_eq!( format!( "{}", MaxMindDBError::IoError("something went wrong".to_owned()) ), "IoError: something went wrong".to_owned(), ); assert_eq!( format!( "{}", MaxMindDBError::MapError("something went wrong".to_owned()) ), "MapError: something went wrong".to_owned(), ); assert_eq!( format!( "{}", MaxMindDBError::DecodingError("something went wrong".to_owned()) ), "DecodingError: something went wrong".to_owned(), ); } } maxminddb-0.13.0/src/maxminddb/reader_test.rs010064400017500001750000000264501342141030600173610ustar0000000000000000extern crate env_logger; use super::{MaxMindDBError, Reader}; use std::net::IpAddr; use std::str::FromStr; #[allow(clippy::float_cmp)] #[test] fn test_decoder() { let _ = env_logger::try_init(); #[allow(non_snake_case)] #[derive(Deserialize, Debug, Eq, PartialEq)] struct MapXType { arrayX: Vec, utf8_stringX: String, }; #[allow(non_snake_case)] #[derive(Deserialize, Debug, Eq, PartialEq)] struct MapType { mapX: MapXType, }; #[derive(Deserialize, Debug)] struct TestType { array: Vec, boolean: bool, bytes: Vec, double: f64, float: f32, int32: i32, map: MapType, uint16: u16, uint32: u32, uint64: u64, uint128: Vec, utf8_string: String, } let r = Reader::open_readfile("test-data/test-data/MaxMind-DB-test-decoder.mmdb"); if let Err(err) = r { panic!(format!("error opening mmdb: {:?}", err)); } let r = r.unwrap(); let ip: IpAddr = FromStr::from_str("1.1.1.0").unwrap(); let result: TestType = r.lookup(ip).unwrap(); assert_eq!(result.array, vec![1u32, 2u32, 3u32]); assert_eq!(result.boolean, true); assert_eq!(result.bytes, vec![0u8, 0u8, 0u8, 42u8]); assert_eq!(result.double, 42.123_456); assert_eq!(result.float, 1.1); assert_eq!(result.int32, -268_435_456); assert_eq!( result.map, MapType { mapX: MapXType { arrayX: vec![7, 8, 9], utf8_stringX: "hello".to_string(), }, } ); assert_eq!(result.uint16, 100); assert_eq!(result.uint32, 268_435_456); assert_eq!(result.uint64, 1_152_921_504_606_846_976); assert_eq!( result.uint128, vec![1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ); assert_eq!(result.utf8_string, "unicode! ☯ - ♫".to_string()); } #[test] fn test_broken_database() { let _ = env_logger::try_init(); let r = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb") .ok() .unwrap(); let ip: IpAddr = FromStr::from_str("2001:220::").unwrap(); #[derive(Deserialize, Debug)] struct TestType; match r.lookup::(ip) { Err(e) => assert_eq!( e, MaxMindDBError::InvalidDatabaseError("double of size 2".to_string()) ), Ok(_) => panic!("Error expected"), } } #[test] fn test_missing_database() { let _ = env_logger::try_init(); let r = Reader::open_readfile("file-does-not-exist.mmdb"); match r { Ok(_) => panic!("Received Reader when opening non-existent file"), Err(MaxMindDBError::IoError(_)) => assert!(true), Err(_) => assert!(false), } } #[test] fn test_non_database() { let _ = env_logger::try_init(); let r = Reader::open_readfile("README.md"); match r { Ok(_) => panic!("Received Reader when opening a non-MMDB file"), Err(e) => assert_eq!( e, MaxMindDBError::InvalidDatabaseError( "Could not find MaxMind DB metadata \ in file." .to_string(), ) ), } } #[test] fn test_reader() { let _ = env_logger::try_init(); let sizes = [24usize, 28, 32]; for record_size in sizes.iter() { let versions = [4usize, 6]; for ip_version in versions.iter() { let filename = format!( "test-data/test-data/MaxMind-DB-test-ipv{}-{}.mmdb", ip_version, record_size ); let reader = Reader::open_readfile(filename).ok().unwrap(); check_metadata(&reader, *ip_version, *record_size); check_ip(&reader, *ip_version); } } } /// Create Reader by explicitly reading the entire file into a buffer. #[test] fn test_reader_readfile() { let _ = env_logger::try_init(); let sizes = [24usize, 28, 32]; for record_size in sizes.iter() { let versions = [4usize, 6]; for ip_version in versions.iter() { let filename = format!( "test-data/test-data/MaxMind-DB-test-ipv{}-{}.mmdb", ip_version, record_size ); let reader = Reader::open_readfile(filename).ok().unwrap(); check_metadata(&reader, *ip_version, *record_size); check_ip(&reader, *ip_version); } } } #[test] #[cfg(feature = "mmap")] fn test_reader_mmap() { let _ = env_logger::try_init(); let sizes = [24usize, 28, 32]; for record_size in sizes.iter() { let versions = [4usize, 6]; for ip_version in versions.iter() { let filename = format!( "test-data/test-data/MaxMind-DB-test-ipv{}-{}.mmdb", ip_version, record_size ); let reader = Reader::open_mmap(filename).ok().unwrap(); check_metadata(&reader, *ip_version, *record_size); check_ip(&reader, *ip_version); } } } #[test] fn test_lookup_city() { use super::geoip2::City; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-City-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("89.160.20.112").unwrap(); let city: City = reader.lookup(ip).unwrap(); let iso_code = city.country.and_then(|cy| cy.iso_code); assert_eq!(iso_code, Some("SE".to_owned())); } #[test] fn test_lookup_country() { use super::geoip2::Country; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-Country-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("89.160.20.112").unwrap(); let country: Country = reader.lookup(ip).unwrap(); let country: super::geoip2::model::Country = country.country.unwrap(); assert_eq!(country.iso_code, Some("SE".to_owned())); assert_eq!(country.is_in_european_union, Some(true)); } #[test] fn test_lookup_connection_type() { use super::geoip2::ConnectionType; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-Connection-Type-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("96.1.20.112").unwrap(); let connection_type: ConnectionType = reader.lookup(ip).unwrap(); assert_eq!( connection_type.connection_type, Some("Cable/DSL".to_owned()) ); } #[test] fn test_lookup_annonymous_ip() { use super::geoip2::AnonymousIp; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("81.2.69.123").unwrap(); let anonymous_ip: AnonymousIp = reader.lookup(ip).unwrap(); assert_eq!(anonymous_ip.is_anonymous, Some(true)); assert_eq!(anonymous_ip.is_public_proxy, Some(true)); assert_eq!(anonymous_ip.is_anonymous_vpn, Some(true)); assert_eq!(anonymous_ip.is_hosting_provider, Some(true)); assert_eq!(anonymous_ip.is_tor_exit_node, Some(true)) } #[test] fn test_lookup_density_income() { use super::geoip2::DensityIncome; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-DensityIncome-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("5.83.124.123").unwrap(); let density_income: DensityIncome = reader.lookup(ip).unwrap(); assert_eq!(density_income.average_income, Some(32323)); assert_eq!(density_income.population_density, Some(1232)) } #[test] fn test_lookup_domain() { use super::geoip2::Domain; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-Domain-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("66.92.80.123").unwrap(); let domain: Domain = reader.lookup(ip).unwrap(); assert_eq!(domain.domain, Some("speakeasy.net".to_owned())); } #[test] fn test_lookup_isp() { use super::geoip2::Isp; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-ISP-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("12.87.118.123").unwrap(); let isp: Isp = reader.lookup(ip).unwrap(); assert_eq!(isp.autonomous_system_number, Some(7018)); assert_eq!(isp.isp, Some("AT&T Services".to_owned())); assert_eq!(isp.organization, Some("AT&T Worldnet Services".to_owned())); } #[test] fn test_lookup_asn() { use super::geoip2::Asn; let _ = env_logger::try_init(); let filename = "test-data/test-data/GeoIP2-ISP-Test.mmdb"; let reader = Reader::open_readfile(filename).unwrap(); let ip: IpAddr = FromStr::from_str("1.128.0.123").unwrap(); let asn: Asn = reader.lookup(ip).unwrap(); assert_eq!(asn.autonomous_system_number, Some(1221)); assert_eq!( asn.autonomous_system_organization, Some("Telstra Pty Ltd".to_owned()) ); } fn check_metadata>(reader: &Reader, ip_version: usize, record_size: usize) { let metadata = &reader.metadata; assert_eq!(metadata.binary_format_major_version, 2u16); assert_eq!(metadata.binary_format_minor_version, 0u16); assert!(metadata.build_epoch >= 1_397_457_605); assert_eq!(metadata.database_type, "Test".to_string()); assert_eq!( *metadata.description[&"en".to_string()], "Test Database".to_string() ); assert_eq!( *metadata.description[&"zh".to_string()], "Test Database Chinese".to_string() ); assert_eq!(metadata.ip_version, ip_version as u16); assert_eq!(metadata.languages, vec!["en".to_string(), "zh".to_string()]); if ip_version == 4 { assert_eq!(metadata.node_count, 164) } else { assert_eq!(metadata.node_count, 416) } assert_eq!(metadata.record_size, record_size as u16) } fn check_ip>(reader: &Reader, ip_version: usize) { let subnets = match ip_version { 6 => [ "::1:ffff:ffff", "::2:0:0", "::2:0:0", "::2:0:0", "::2:0:0", "::2:0:40", "::2:0:40", "::2:0:40", "::2:0:50", "::2:0:50", "::2:0:50", "::2:0:58", "::2:0:58", ], _ => [ "1.1.1.1", "1.1.1.2", "1.1.1.2", "1.1.1.4", "1.1.1.4", "1.1.1.4", "1.1.1.4", "1.1.1.8", "1.1.1.8", "1.1.1.8", "1.1.1.16", "1.1.1.16", "1.1.1.16", ], }; #[derive(Deserialize, Debug)] struct IpType { ip: String, } for subnet in subnets.iter() { let ip: IpAddr = FromStr::from_str(&subnet).unwrap(); let value: IpType = reader.lookup(ip).unwrap(); assert_eq!(value.ip, subnet.to_string()); } let no_record = ["1.1.1.33", "255.254.253.123", "89fa::"]; for &address in no_record.iter() { let ip: IpAddr = FromStr::from_str(address).unwrap(); match reader.lookup::(ip) { Ok(v) => panic!("received an unexpected value: {:?}", v), Err(e) => assert_eq!( e, MaxMindDBError::AddressNotFoundError("Address not found in database".to_string()) ), } } } maxminddb-0.13.0/.cargo_vcs_info.json0000644000000001120000000000000131110ustar00{ "git": { "sha1": "5e43c30c8bb30384b7e59fa54803fbe359c818a4" } }