1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
extern crate maxminddb;
extern crate reqwest;
extern crate libflate;
mod emoji;
use std::io;
use std::fs;
use std::env;
use std::path::Path;
use std::net::IpAddr;
use std::str::FromStr;
use std::error::Error;
use maxminddb::geoip2;
use libflate::gzip::Decoder;
static SOURCE: &'static str = "https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz";
static TEMPFILE: &'static str = "GeoLite2-City.mmdb.gz";
static FILE: &'static str = "/usr/local/share/glip/GeoLite2-City.mmdb";
static EXPIRATION_DURATION: u64 = 3600 * 24 * 32;
static EMPTY_TXT: &'static str = "unknown";
#[derive(Clone, Debug)]
pub struct GeoIp {
pub country: String,
pub subdivision: String,
pub city: String,
pub flag: String,
}
#[derive(Debug)]
pub enum GeoIpError {
ParseError(std::net::AddrParseError),
DBError(maxminddb::MaxMindDBError),
}
impl GeoIp {
fn extract_file(fname: &str) {
let mut file = fs::File::open(fname).unwrap();
let mut decoder = Decoder::new(&mut file).unwrap();
let mut out = fs::File::create(FILE).expect("failed to create file");
io::copy(&mut decoder, &mut out).expect("failed to copy content");
}
fn download_file(fname: &str) {
let path = Path::new(FILE);
let pdir = path.parent().unwrap();
if !Path::new(pdir).exists() {
fs::create_dir_all(pdir).expect("failed to create dir");
}
let mut resp = reqwest::get(SOURCE).expect("download failed");
let mut out: fs::File = fs::File::create(fname).expect("failed to create file");
io::copy(&mut resp, &mut out).expect("failed to copy content");
}
fn file_expired() -> bool {
let metadata = fs::metadata(FILE);
let modified = metadata.unwrap().modified().unwrap();
let elapsed = modified.elapsed().unwrap();
return elapsed.as_secs() >= EXPIRATION_DURATION;
}
fn download_and_extract_file_if_not_exists_or_old() {
if Path::new(FILE).exists() && !Self::file_expired() {
return
}
let mut dir = env::temp_dir();
dir.push(TEMPFILE);
let fname = dir.as_path().to_str().unwrap();
println!("==> Database downloading...");
Self::download_file(fname);
println!(" Saved to {}", fname);
println!("==> Database extracting...");
Self::extract_file(fname);
println!(" Extracted to {}", FILE);
}
fn reader() -> Result<maxminddb::Reader<Vec<u8>>, maxminddb::MaxMindDBError> {
Self::download_and_extract_file_if_not_exists_or_old();
let rr = maxminddb::Reader::open_readfile(FILE);
if let Err(err) = rr {
panic!(format!("error opening mmdb: {:?}", err));
}
rr
}
fn pickup_subdivision(src: Option<Vec<geoip2::model::Subdivision>>) -> String {
if src.is_none() {
return EMPTY_TXT.to_string();
}
let mut subdivs = src.unwrap();
let subdiv_model = subdivs.pop().unwrap().names.unwrap();
return subdiv_model.get("en").unwrap().to_string();
}
fn pickup_city(src: Option<geoip2::model::City>) -> String {
if src.is_none() {
return EMPTY_TXT.to_string();
}
let city_model = src.unwrap().names.unwrap();
return city_model.get("en").unwrap().to_string();
}
pub fn new(ip: &str) -> Result<Self, Box<Error>> {
let ipaddr: IpAddr = FromStr::from_str(ip)?;
let geoip: geoip2::City = Self::reader().unwrap().lookup(ipaddr)?;
let country_model = geoip.country.unwrap().names.unwrap();
let country = country_model.get("en").unwrap();
Ok(GeoIp {
country: country.to_string(),
subdivision: Self::pickup_subdivision(geoip.subdivisions),
city: Self::pickup_city(geoip.city),
flag: emoji::flag(country),
})
}
}