This repository has been archived by the owner on Oct 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
maxmind.go
136 lines (122 loc) · 3.33 KB
/
maxmind.go
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package main
import (
"archive/tar"
"compress/gzip"
"errors"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
)
// Fetch latest MaxMind GeoIP Country database with license key
// into the OS's temporary directory and return path to it
func getGeolocationDatabase(license string) (string, error) {
targetDir := os.TempDir()
targetFile := filepath.Join(targetDir, "caddy-analytics-maxmind-geolite2-country.mmdb")
url := "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=" + license + "&suffix=tar.gz"
// Fetch the latest database
fetch := func() error {
// Open target file
file, err := os.Create(targetFile)
if err != nil {
os.Remove(targetFile)
return err
}
defer file.Close()
// Download the database
response, err := http.Get(url)
if err != nil {
os.Remove(targetFile)
return err
}
defer response.Body.Close()
if response.StatusCode == 401 {
return errors.New("invalid MaxMind license key")
} else if response.StatusCode != 200 {
return errors.New("http request failed with status code " + strconv.Itoa(response.StatusCode))
}
// Decompress the file
gzipReader, err := gzip.NewReader(response.Body)
if err != nil {
os.Remove(targetFile)
return err
}
// Untar the file
tarReader := tar.NewReader(gzipReader)
if err != nil {
os.Remove(targetFile)
return err
}
// Search the archive for a .mmdb file and copy it to the target file
for {
header, err := tarReader.Next()
if err != nil {
os.Remove(targetFile)
return err
}
if header.Typeflag == tar.TypeReg {
if filepath.Ext(header.Name) == ".mmdb" {
_, err = io.Copy(file, tarReader)
if err != nil {
os.Remove(targetFile)
return err
}
return nil
}
}
}
}
// Create temporary directory if it doesn't exist
err := os.MkdirAll(targetDir, os.ModePerm)
if err != nil {
return "", err
}
// Get database file stats and fetch if it doesn't exist
info, err := os.Stat(targetFile)
if os.IsNotExist(err) {
log.Print("No cached database found, fetching...")
err := fetch()
if err != nil {
return "", err
}
return targetFile, nil
} else if err != nil {
return "", err
} else if info.Size() < 1024*8 {
log.Print("Invalid database found, fetching...")
if err != nil {
return "", err
}
return targetFile, nil
}
// Get only the headers of the external database
response, err := http.Head(url)
if err != nil {
return "", err
}
if response.StatusCode == 401 {
return "", errors.New("invalid MaxMind license key")
} else if response.StatusCode != 200 {
status := strconv.Itoa(response.StatusCode)
return "", errors.New("http request failed with status code " + status)
}
// Extract the build time of the most recent database
build, err := time.Parse(time.RFC1123, response.Header["Last-Modified"][0])
if err != nil {
return "", err
}
// Get new database if it is outdated
if build.After(info.ModTime()) {
log.Print("Cached geo database is outdated, fetching...")
if err != nil {
return "", err
}
return targetFile, nil
}
// If already existed and not out-of-date, do nothing
log.Print("Cached geo database is up-to-date...")
return targetFile, nil
}