Basic API to fetch data from maxmind geoip databases over http

This commit is contained in:
2018-07-12 23:54:37 -07:00
parent dc4018e8f4
commit f5f0ea4931
253 changed files with 167757 additions and 0 deletions

15
vendor/github.com/oschwald/geoip2-golang/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2015, Gregory J. Oschwald <oschwald@gmail.com>
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.

327
vendor/github.com/oschwald/geoip2-golang/reader.go generated vendored Normal file
View File

@@ -0,0 +1,327 @@
// Package geoip2 provides an easy-to-use API for the MaxMind GeoIP2 and
// GeoLite2 databases; this package does not support GeoIP Legacy databases.
//
// The structs provided by this package match the internal structure of
// the data in the MaxMind databases.
//
// See github.com/oschwald/maxminddb-golang for more advanced used cases.
package geoip2
import (
"fmt"
"net"
"github.com/oschwald/maxminddb-golang"
)
// The City struct corresponds to the data in the GeoIP2/GeoLite2 City
// databases.
type City struct {
City struct {
GeoNameID uint `maxminddb:"geoname_id"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"city"`
Continent struct {
Code string `maxminddb:"code"`
GeoNameID uint `maxminddb:"geoname_id"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"continent"`
Country struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"country"`
Location struct {
AccuracyRadius uint16 `maxminddb:"accuracy_radius"`
Latitude float64 `maxminddb:"latitude"`
Longitude float64 `maxminddb:"longitude"`
MetroCode uint `maxminddb:"metro_code"`
TimeZone string `maxminddb:"time_zone"`
} `maxminddb:"location"`
Postal struct {
Code string `maxminddb:"code"`
} `maxminddb:"postal"`
RegisteredCountry struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"registered_country"`
RepresentedCountry struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
Type string `maxminddb:"type"`
} `maxminddb:"represented_country"`
Subdivisions []struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"subdivisions"`
Traits struct {
IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"`
IsSatelliteProvider bool `maxminddb:"is_satellite_provider"`
} `maxminddb:"traits"`
}
// The Country struct corresponds to the data in the GeoIP2/GeoLite2
// Country databases.
type Country struct {
Continent struct {
Code string `maxminddb:"code"`
GeoNameID uint `maxminddb:"geoname_id"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"continent"`
Country struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"country"`
RegisteredCountry struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"registered_country"`
RepresentedCountry struct {
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
IsoCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
Type string `maxminddb:"type"`
} `maxminddb:"represented_country"`
Traits struct {
IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"`
IsSatelliteProvider bool `maxminddb:"is_satellite_provider"`
} `maxminddb:"traits"`
}
// The AnonymousIP struct corresponds to the data in the GeoIP2
// Anonymous IP database.
type AnonymousIP struct {
IsAnonymous bool `maxminddb:"is_anonymous"`
IsAnonymousVPN bool `maxminddb:"is_anonymous_vpn"`
IsHostingProvider bool `maxminddb:"is_hosting_provider"`
IsPublicProxy bool `maxminddb:"is_public_proxy"`
IsTorExitNode bool `maxminddb:"is_tor_exit_node"`
}
// The ASN struct corresponds to the data in the GeoLite2 ASN database.
type ASN struct {
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
}
// The ConnectionType struct corresponds to the data in the GeoIP2
// Connection-Type database.
type ConnectionType struct {
ConnectionType string `maxminddb:"connection_type"`
}
// The Domain struct corresponds to the data in the GeoIP2 Domain database.
type Domain struct {
Domain string `maxminddb:"domain"`
}
// The ISP struct corresponds to the data in the GeoIP2 ISP database.
type ISP struct {
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
ISP string `maxminddb:"isp"`
Organization string `maxminddb:"organization"`
}
type databaseType int
const (
isAnonymousIP = 1 << iota
isASN
isCity
isConnectionType
isCountry
isDomain
isEnterprise
isISP
)
// Reader holds the maxminddb.Reader struct. It can be created using the
// Open and FromBytes functions.
type Reader struct {
mmdbReader *maxminddb.Reader
databaseType databaseType
}
// InvalidMethodError is returned when a lookup method is called on a
// database that it does not support. For instance, calling the ISP method
// on a City database.
type InvalidMethodError struct {
Method string
DatabaseType string
}
func (e InvalidMethodError) Error() string {
return fmt.Sprintf(`geoip2: the %s method does not support the %s database`,
e.Method, e.DatabaseType)
}
// UnknownDatabaseTypeError is returned when an unknown database type is
// opened.
type UnknownDatabaseTypeError struct {
DatabaseType string
}
func (e UnknownDatabaseTypeError) Error() string {
return fmt.Sprintf(`geoip2: reader does not support the "%s" database type`,
e.DatabaseType)
}
// Open takes a string path to a file and returns a Reader struct or an error.
// The database file is opened using a memory map. Use the Close method on the
// Reader object to return the resources to the system.
func Open(file string) (*Reader, error) {
reader, err := maxminddb.Open(file)
if err != nil {
return nil, err
}
dbType, err := getDBType(reader)
return &Reader{reader, dbType}, err
}
// FromBytes takes a byte slice corresponding to a GeoIP2/GeoLite2 database
// file and returns a Reader struct or an error. Note that the byte slice is
// use directly; any modification of it after opening the database will result
// in errors while reading from the database.
func FromBytes(bytes []byte) (*Reader, error) {
reader, err := maxminddb.FromBytes(bytes)
if err != nil {
return nil, err
}
dbType, err := getDBType(reader)
return &Reader{reader, dbType}, err
}
func getDBType(reader *maxminddb.Reader) (databaseType, error) {
switch reader.Metadata.DatabaseType {
case "GeoIP2-Anonymous-IP":
return isAnonymousIP, nil
case "GeoLite2-ASN":
return isASN, nil
// We allow City lookups on Country for back compat
case "GeoLite2-City",
"GeoIP2-City",
"GeoIP2-City-Africa",
"GeoIP2-City-Asia-Pacific",
"GeoIP2-City-Europe",
"GeoIP2-City-North-America",
"GeoIP2-City-South-America",
"GeoIP2-Precision-City",
"GeoLite2-Country",
"GeoIP2-Country":
return isCity | isCountry, nil
case "GeoIP2-Connection-Type":
return isConnectionType, nil
case "GeoIP2-Domain":
return isDomain, nil
case "GeoIP2-Enterprise":
return isEnterprise | isCity | isCountry, nil
case "GeoIP2-ISP", "GeoIP2-Precision-ISP":
return isISP, nil
default:
return 0, UnknownDatabaseTypeError{reader.Metadata.DatabaseType}
}
}
// City takes an IP address as a net.IP struct and returns a City struct
// and/or an error. Although this can be used with other databases, this
// method generally should be used with the GeoIP2 or GeoLite2 City databases.
func (r *Reader) City(ipAddress net.IP) (*City, error) {
if isCity&r.databaseType == 0 {
return nil, InvalidMethodError{"City", r.Metadata().DatabaseType}
}
var city City
err := r.mmdbReader.Lookup(ipAddress, &city)
return &city, err
}
// Country takes an IP address as a net.IP struct and returns a Country struct
// and/or an error. Although this can be used with other databases, this
// method generally should be used with the GeoIP2 or GeoLite2 Country
// databases.
func (r *Reader) Country(ipAddress net.IP) (*Country, error) {
if isCountry&r.databaseType == 0 {
return nil, InvalidMethodError{"Country", r.Metadata().DatabaseType}
}
var country Country
err := r.mmdbReader.Lookup(ipAddress, &country)
return &country, err
}
// AnonymousIP takes an IP address as a net.IP struct and returns a
// AnonymousIP struct and/or an error.
func (r *Reader) AnonymousIP(ipAddress net.IP) (*AnonymousIP, error) {
if isAnonymousIP&r.databaseType == 0 {
return nil, InvalidMethodError{"AnonymousIP", r.Metadata().DatabaseType}
}
var anonIP AnonymousIP
err := r.mmdbReader.Lookup(ipAddress, &anonIP)
return &anonIP, err
}
// ASN takes an IP address as a net.IP struct and returns a ASN struct and/or
// an error
func (r *Reader) ASN(ipAddress net.IP) (*ASN, error) {
if isASN&r.databaseType == 0 {
return nil, InvalidMethodError{"ASN", r.Metadata().DatabaseType}
}
var val ASN
err := r.mmdbReader.Lookup(ipAddress, &val)
return &val, err
}
// ConnectionType takes an IP address as a net.IP struct and returns a
// ConnectionType struct and/or an error
func (r *Reader) ConnectionType(ipAddress net.IP) (*ConnectionType, error) {
if isConnectionType&r.databaseType == 0 {
return nil, InvalidMethodError{"ConnectionType", r.Metadata().DatabaseType}
}
var val ConnectionType
err := r.mmdbReader.Lookup(ipAddress, &val)
return &val, err
}
// Domain takes an IP address as a net.IP struct and returns a
// Domain struct and/or an error
func (r *Reader) Domain(ipAddress net.IP) (*Domain, error) {
if isDomain&r.databaseType == 0 {
return nil, InvalidMethodError{"Domain", r.Metadata().DatabaseType}
}
var val Domain
err := r.mmdbReader.Lookup(ipAddress, &val)
return &val, err
}
// ISP takes an IP address as a net.IP struct and returns a ISP struct and/or
// an error
func (r *Reader) ISP(ipAddress net.IP) (*ISP, error) {
if isISP&r.databaseType == 0 {
return nil, InvalidMethodError{"ISP", r.Metadata().DatabaseType}
}
var val ISP
err := r.mmdbReader.Lookup(ipAddress, &val)
return &val, err
}
// Metadata takes no arguments and returns a struct containing metadata about
// the MaxMind database in use by the Reader.
func (r *Reader) Metadata() maxminddb.Metadata {
return r.mmdbReader.Metadata
}
// Close unmaps the database file from virtual memory and returns the
// resources to the system.
func (r *Reader) Close() error {
return r.mmdbReader.Close()
}

15
vendor/github.com/oschwald/maxminddb-golang/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2015, Gregory J. Oschwald <oschwald@gmail.com>
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.

721
vendor/github.com/oschwald/maxminddb-golang/decoder.go generated vendored Normal file
View File

@@ -0,0 +1,721 @@
package maxminddb
import (
"encoding/binary"
"math"
"math/big"
"reflect"
"sync"
)
type decoder struct {
buffer []byte
}
type dataType int
const (
_Extended dataType = iota
_Pointer
_String
_Float64
_Bytes
_Uint16
_Uint32
_Map
_Int32
_Uint64
_Uint128
_Slice
_Container
_Marker
_Bool
_Float32
)
const (
// This is the value used in libmaxminddb
maximumDataStructureDepth = 512
)
func (d *decoder) decode(offset uint, result reflect.Value, depth int) (uint, error) {
if depth > maximumDataStructureDepth {
return 0, newInvalidDatabaseError("exceeded maximum data structure depth; database is likely corrupt")
}
typeNum, size, newOffset, err := d.decodeCtrlData(offset)
if err != nil {
return 0, err
}
if typeNum != _Pointer && result.Kind() == reflect.Uintptr {
result.Set(reflect.ValueOf(uintptr(offset)))
return d.nextValueOffset(offset, 1)
}
return d.decodeFromType(typeNum, size, newOffset, result, depth+1)
}
func (d *decoder) decodeCtrlData(offset uint) (dataType, uint, uint, error) {
newOffset := offset + 1
if offset >= uint(len(d.buffer)) {
return 0, 0, 0, newOffsetError()
}
ctrlByte := d.buffer[offset]
typeNum := dataType(ctrlByte >> 5)
if typeNum == _Extended {
if newOffset >= uint(len(d.buffer)) {
return 0, 0, 0, newOffsetError()
}
typeNum = dataType(d.buffer[newOffset] + 7)
newOffset++
}
var size uint
size, newOffset, err := d.sizeFromCtrlByte(ctrlByte, newOffset, typeNum)
return typeNum, size, newOffset, err
}
func (d *decoder) sizeFromCtrlByte(ctrlByte byte, offset uint, typeNum dataType) (uint, uint, error) {
size := uint(ctrlByte & 0x1f)
if typeNum == _Extended {
return size, offset, nil
}
var bytesToRead uint
if size < 29 {
return size, offset, nil
}
bytesToRead = size - 28
newOffset := offset + bytesToRead
if newOffset > uint(len(d.buffer)) {
return 0, 0, newOffsetError()
}
if size == 29 {
return 29 + uint(d.buffer[offset]), offset + 1, nil
}
sizeBytes := d.buffer[offset:newOffset]
switch {
case size == 30:
size = 285 + uintFromBytes(0, sizeBytes)
case size > 30:
size = uintFromBytes(0, sizeBytes) + 65821
}
return size, newOffset, nil
}
func (d *decoder) decodeFromType(
dtype dataType,
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
result = d.indirect(result)
// For these types, size has a special meaning
switch dtype {
case _Bool:
return d.unmarshalBool(size, offset, result)
case _Map:
return d.unmarshalMap(size, offset, result, depth)
case _Pointer:
return d.unmarshalPointer(size, offset, result, depth)
case _Slice:
return d.unmarshalSlice(size, offset, result, depth)
}
// For the remaining types, size is the byte size
if offset+size > uint(len(d.buffer)) {
return 0, newOffsetError()
}
switch dtype {
case _Bytes:
return d.unmarshalBytes(size, offset, result)
case _Float32:
return d.unmarshalFloat32(size, offset, result)
case _Float64:
return d.unmarshalFloat64(size, offset, result)
case _Int32:
return d.unmarshalInt32(size, offset, result)
case _String:
return d.unmarshalString(size, offset, result)
case _Uint16:
return d.unmarshalUint(size, offset, result, 16)
case _Uint32:
return d.unmarshalUint(size, offset, result, 32)
case _Uint64:
return d.unmarshalUint(size, offset, result, 64)
case _Uint128:
return d.unmarshalUint128(size, offset, result)
default:
return 0, newInvalidDatabaseError("unknown type: %d", dtype)
}
}
func (d *decoder) unmarshalBool(size uint, offset uint, result reflect.Value) (uint, error) {
if size > 1 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (bool size of %v)", size)
}
value, newOffset, err := d.decodeBool(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Bool:
result.SetBool(value)
return newOffset, nil
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
// indirect follows pointers and create values as necessary. This is
// heavily based on encoding/json as my original version had a subtle
// bug. This method should be considered to be licensed under
// https://golang.org/LICENSE
func (d *decoder) indirect(result reflect.Value) reflect.Value {
for {
// Load value from interface, but only if the result will be
// usefully addressable.
if result.Kind() == reflect.Interface && !result.IsNil() {
e := result.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() {
result = e
continue
}
}
if result.Kind() != reflect.Ptr {
break
}
if result.IsNil() {
result.Set(reflect.New(result.Type().Elem()))
}
result = result.Elem()
}
return result
}
var sliceType = reflect.TypeOf([]byte{})
func (d *decoder) unmarshalBytes(size uint, offset uint, result reflect.Value) (uint, error) {
value, newOffset, err := d.decodeBytes(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Slice:
if result.Type() == sliceType {
result.SetBytes(value)
return newOffset, nil
}
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) unmarshalFloat32(size uint, offset uint, result reflect.Value) (uint, error) {
if size != 4 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (float32 size of %v)", size)
}
value, newOffset, err := d.decodeFloat32(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Float32, reflect.Float64:
result.SetFloat(float64(value))
return newOffset, nil
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) unmarshalFloat64(size uint, offset uint, result reflect.Value) (uint, error) {
if size != 8 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (float 64 size of %v)", size)
}
value, newOffset, err := d.decodeFloat64(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Float32, reflect.Float64:
if result.OverflowFloat(value) {
return 0, newUnmarshalTypeError(value, result.Type())
}
result.SetFloat(value)
return newOffset, nil
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) unmarshalInt32(size uint, offset uint, result reflect.Value) (uint, error) {
if size > 4 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (int32 size of %v)", size)
}
value, newOffset, err := d.decodeInt(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n := int64(value)
if !result.OverflowInt(n) {
result.SetInt(n)
return newOffset, nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n := uint64(value)
if !result.OverflowUint(n) {
result.SetUint(n)
return newOffset, nil
}
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) unmarshalMap(
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
result = d.indirect(result)
switch result.Kind() {
default:
return 0, newUnmarshalTypeError("map", result.Type())
case reflect.Struct:
return d.decodeStruct(size, offset, result, depth)
case reflect.Map:
return d.decodeMap(size, offset, result, depth)
case reflect.Interface:
if result.NumMethod() == 0 {
rv := reflect.ValueOf(make(map[string]interface{}, size))
newOffset, err := d.decodeMap(size, offset, rv, depth)
result.Set(rv)
return newOffset, err
}
return 0, newUnmarshalTypeError("map", result.Type())
}
}
func (d *decoder) unmarshalPointer(size uint, offset uint, result reflect.Value, depth int) (uint, error) {
pointer, newOffset, err := d.decodePointer(size, offset)
if err != nil {
return 0, err
}
_, err = d.decode(pointer, result, depth)
return newOffset, err
}
func (d *decoder) unmarshalSlice(
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
switch result.Kind() {
case reflect.Slice:
return d.decodeSlice(size, offset, result, depth)
case reflect.Interface:
if result.NumMethod() == 0 {
a := []interface{}{}
rv := reflect.ValueOf(&a).Elem()
newOffset, err := d.decodeSlice(size, offset, rv, depth)
result.Set(rv)
return newOffset, err
}
}
return 0, newUnmarshalTypeError("array", result.Type())
}
func (d *decoder) unmarshalString(size uint, offset uint, result reflect.Value) (uint, error) {
value, newOffset, err := d.decodeString(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.String:
result.SetString(value)
return newOffset, nil
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) unmarshalUint(size uint, offset uint, result reflect.Value, uintType uint) (uint, error) {
if size > uintType/8 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (uint%v size of %v)", uintType, size)
}
value, newOffset, err := d.decodeUint(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n := int64(value)
if !result.OverflowInt(n) {
result.SetInt(n)
return newOffset, nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if !result.OverflowUint(value) {
result.SetUint(value)
return newOffset, nil
}
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
var bigIntType = reflect.TypeOf(big.Int{})
func (d *decoder) unmarshalUint128(size uint, offset uint, result reflect.Value) (uint, error) {
if size > 16 {
return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (uint128 size of %v)", size)
}
value, newOffset, err := d.decodeUint128(size, offset)
if err != nil {
return 0, err
}
switch result.Kind() {
case reflect.Struct:
if result.Type() == bigIntType {
result.Set(reflect.ValueOf(*value))
return newOffset, nil
}
case reflect.Interface:
if result.NumMethod() == 0 {
result.Set(reflect.ValueOf(value))
return newOffset, nil
}
}
return newOffset, newUnmarshalTypeError(value, result.Type())
}
func (d *decoder) decodeBool(size uint, offset uint) (bool, uint, error) {
return size != 0, offset, nil
}
func (d *decoder) decodeBytes(size uint, offset uint) ([]byte, uint, error) {
newOffset := offset + size
bytes := make([]byte, size)
copy(bytes, d.buffer[offset:newOffset])
return bytes, newOffset, nil
}
func (d *decoder) decodeFloat64(size uint, offset uint) (float64, uint, error) {
newOffset := offset + size
bits := binary.BigEndian.Uint64(d.buffer[offset:newOffset])
return math.Float64frombits(bits), newOffset, nil
}
func (d *decoder) decodeFloat32(size uint, offset uint) (float32, uint, error) {
newOffset := offset + size
bits := binary.BigEndian.Uint32(d.buffer[offset:newOffset])
return math.Float32frombits(bits), newOffset, nil
}
func (d *decoder) decodeInt(size uint, offset uint) (int, uint, error) {
newOffset := offset + size
var val int32
for _, b := range d.buffer[offset:newOffset] {
val = (val << 8) | int32(b)
}
return int(val), newOffset, nil
}
func (d *decoder) decodeMap(
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
if result.IsNil() {
result.Set(reflect.MakeMap(result.Type()))
}
for i := uint(0); i < size; i++ {
var key []byte
var err error
key, offset, err = d.decodeKey(offset)
if err != nil {
return 0, err
}
value := reflect.New(result.Type().Elem())
offset, err = d.decode(offset, value, depth)
if err != nil {
return 0, err
}
result.SetMapIndex(reflect.ValueOf(string(key)), value.Elem())
}
return offset, nil
}
func (d *decoder) decodePointer(
size uint,
offset uint,
) (uint, uint, error) {
pointerSize := ((size >> 3) & 0x3) + 1
newOffset := offset + pointerSize
if newOffset > uint(len(d.buffer)) {
return 0, 0, newOffsetError()
}
pointerBytes := d.buffer[offset:newOffset]
var prefix uint
if pointerSize == 4 {
prefix = 0
} else {
prefix = uint(size & 0x7)
}
unpacked := uintFromBytes(prefix, pointerBytes)
var pointerValueOffset uint
switch pointerSize {
case 1:
pointerValueOffset = 0
case 2:
pointerValueOffset = 2048
case 3:
pointerValueOffset = 526336
case 4:
pointerValueOffset = 0
}
pointer := unpacked + pointerValueOffset
return pointer, newOffset, nil
}
func (d *decoder) decodeSlice(
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
result.Set(reflect.MakeSlice(result.Type(), int(size), int(size)))
for i := 0; i < int(size); i++ {
var err error
offset, err = d.decode(offset, result.Index(i), depth)
if err != nil {
return 0, err
}
}
return offset, nil
}
func (d *decoder) decodeString(size uint, offset uint) (string, uint, error) {
newOffset := offset + size
return string(d.buffer[offset:newOffset]), newOffset, nil
}
type fieldsType struct {
namedFields map[string]int
anonymousFields []int
}
var (
fieldMap = map[reflect.Type]*fieldsType{}
fieldMapMu sync.RWMutex
)
func (d *decoder) decodeStruct(
size uint,
offset uint,
result reflect.Value,
depth int,
) (uint, error) {
resultType := result.Type()
fieldMapMu.RLock()
fields, ok := fieldMap[resultType]
fieldMapMu.RUnlock()
if !ok {
numFields := resultType.NumField()
namedFields := make(map[string]int, numFields)
var anonymous []int
for i := 0; i < numFields; i++ {
field := resultType.Field(i)
fieldName := field.Name
if tag := field.Tag.Get("maxminddb"); tag != "" {
if tag == "-" {
continue
}
fieldName = tag
}
if field.Anonymous {
anonymous = append(anonymous, i)
continue
}
namedFields[fieldName] = i
}
fieldMapMu.Lock()
fields = &fieldsType{namedFields, anonymous}
fieldMap[resultType] = fields
fieldMapMu.Unlock()
}
// This fills in embedded structs
for _, i := range fields.anonymousFields {
_, err := d.unmarshalMap(size, offset, result.Field(i), depth)
if err != nil {
return 0, err
}
}
// This handles named fields
for i := uint(0); i < size; i++ {
var (
err error
key []byte
)
key, offset, err = d.decodeKey(offset)
if err != nil {
return 0, err
}
// The string() does not create a copy due to this compiler
// optimization: https://github.com/golang/go/issues/3512
j, ok := fields.namedFields[string(key)]
if !ok {
offset, err = d.nextValueOffset(offset, 1)
if err != nil {
return 0, err
}
continue
}
offset, err = d.decode(offset, result.Field(j), depth)
if err != nil {
return 0, err
}
}
return offset, nil
}
func (d *decoder) decodeUint(size uint, offset uint) (uint64, uint, error) {
newOffset := offset + size
bytes := d.buffer[offset:newOffset]
var val uint64
for _, b := range bytes {
val = (val << 8) | uint64(b)
}
return val, newOffset, nil
}
func (d *decoder) decodeUint128(size uint, offset uint) (*big.Int, uint, error) {
newOffset := offset + size
val := new(big.Int)
val.SetBytes(d.buffer[offset:newOffset])
return val, newOffset, nil
}
func uintFromBytes(prefix uint, uintBytes []byte) uint {
val := prefix
for _, b := range uintBytes {
val = (val << 8) | uint(b)
}
return val
}
// decodeKey decodes a map key into []byte slice. We use a []byte so that we
// can take advantage of https://github.com/golang/go/issues/3512 to avoid
// copying the bytes when decoding a struct. Previously, we achieved this by
// using unsafe.
func (d *decoder) decodeKey(offset uint) ([]byte, uint, error) {
typeNum, size, dataOffset, err := d.decodeCtrlData(offset)
if err != nil {
return nil, 0, err
}
if typeNum == _Pointer {
pointer, ptrOffset, err := d.decodePointer(size, dataOffset)
if err != nil {
return nil, 0, err
}
key, _, err := d.decodeKey(pointer)
return key, ptrOffset, err
}
if typeNum != _String {
return nil, 0, newInvalidDatabaseError("unexpected type when decoding string: %v", typeNum)
}
newOffset := dataOffset + size
if newOffset > uint(len(d.buffer)) {
return nil, 0, newOffsetError()
}
return d.buffer[dataOffset:newOffset], newOffset, nil
}
// This function is used to skip ahead to the next value without decoding
// the one at the offset passed in. The size bits have different meanings for
// different data types
func (d *decoder) nextValueOffset(offset uint, numberToSkip uint) (uint, error) {
if numberToSkip == 0 {
return offset, nil
}
typeNum, size, offset, err := d.decodeCtrlData(offset)
if err != nil {
return 0, err
}
switch typeNum {
case _Pointer:
_, offset, err = d.decodePointer(size, offset)
if err != nil {
return 0, err
}
case _Map:
numberToSkip += 2 * size
case _Slice:
numberToSkip += size
case _Bool:
default:
offset += size
}
return d.nextValueOffset(offset, numberToSkip-1)
}

42
vendor/github.com/oschwald/maxminddb-golang/errors.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package maxminddb
import (
"fmt"
"reflect"
)
// InvalidDatabaseError is returned when the database contains invalid data
// and cannot be parsed.
type InvalidDatabaseError struct {
message string
}
func newOffsetError() InvalidDatabaseError {
return InvalidDatabaseError{"unexpected end of database"}
}
func newInvalidDatabaseError(format string, args ...interface{}) InvalidDatabaseError {
return InvalidDatabaseError{fmt.Sprintf(format, args...)}
}
func (e InvalidDatabaseError) Error() string {
return e.message
}
// UnmarshalTypeError is returned when the value in the database cannot be
// assigned to the specified data type.
type UnmarshalTypeError struct {
Value string // stringified copy of the database value that caused the error
Type reflect.Type // type of the value that could not be assign to
}
func newUnmarshalTypeError(value interface{}, rType reflect.Type) UnmarshalTypeError {
return UnmarshalTypeError{
Value: fmt.Sprintf("%v", value),
Type: rType,
}
}
func (e UnmarshalTypeError) Error() string {
return fmt.Sprintf("maxminddb: cannot unmarshal %s into type %s", e.Value, e.Type.String())
}

View File

@@ -0,0 +1,15 @@
// +build !windows,!appengine
package maxminddb
import (
"golang.org/x/sys/unix"
)
func mmap(fd int, length int) (data []byte, err error) {
return unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED)
}
func munmap(b []byte) (err error) {
return unix.Munmap(b)
}

View File

@@ -0,0 +1,85 @@
// +build windows,!appengine
package maxminddb
// Windows support largely borrowed from mmap-go.
//
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import (
"errors"
"os"
"reflect"
"sync"
"unsafe"
"golang.org/x/sys/windows"
)
type memoryMap []byte
// Windows
var handleLock sync.Mutex
var handleMap = map[uintptr]windows.Handle{}
func mmap(fd int, length int) (data []byte, err error) {
h, errno := windows.CreateFileMapping(windows.Handle(fd), nil,
uint32(windows.PAGE_READONLY), 0, uint32(length), nil)
if h == 0 {
return nil, os.NewSyscallError("CreateFileMapping", errno)
}
addr, errno := windows.MapViewOfFile(h, uint32(windows.FILE_MAP_READ), 0,
0, uintptr(length))
if addr == 0 {
return nil, os.NewSyscallError("MapViewOfFile", errno)
}
handleLock.Lock()
handleMap[addr] = h
handleLock.Unlock()
m := memoryMap{}
dh := m.header()
dh.Data = addr
dh.Len = length
dh.Cap = dh.Len
return m, nil
}
func (m *memoryMap) header() *reflect.SliceHeader {
return (*reflect.SliceHeader)(unsafe.Pointer(m))
}
func flush(addr, len uintptr) error {
errno := windows.FlushViewOfFile(addr, len)
return os.NewSyscallError("FlushViewOfFile", errno)
}
func munmap(b []byte) (err error) {
m := memoryMap(b)
dh := m.header()
addr := dh.Data
length := uintptr(dh.Len)
flush(addr, length)
err = windows.UnmapViewOfFile(addr)
if err != nil {
return err
}
handleLock.Lock()
defer handleLock.Unlock()
handle, ok := handleMap[addr]
if !ok {
// should be impossible; we would've errored above
return errors.New("unknown base address")
}
delete(handleMap, addr)
e := windows.CloseHandle(windows.Handle(handle))
return os.NewSyscallError("CloseHandle", e)
}

259
vendor/github.com/oschwald/maxminddb-golang/reader.go generated vendored Normal file
View File

@@ -0,0 +1,259 @@
package maxminddb
import (
"bytes"
"errors"
"fmt"
"net"
"reflect"
)
const (
// NotFound is returned by LookupOffset when a matched root record offset
// cannot be found.
NotFound = ^uintptr(0)
dataSectionSeparatorSize = 16
)
var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com")
// Reader holds the data corresponding to the MaxMind DB file. Its only public
// field is Metadata, which contains the metadata from the MaxMind DB file.
type Reader struct {
hasMappedFile bool
buffer []byte
decoder decoder
Metadata Metadata
ipv4Start uint
}
// Metadata holds the metadata decoded from the MaxMind DB file. In particular
// in has the format version, the build time as Unix epoch time, the database
// type and description, the IP version supported, and a slice of the natural
// languages included.
type Metadata struct {
BinaryFormatMajorVersion uint `maxminddb:"binary_format_major_version"`
BinaryFormatMinorVersion uint `maxminddb:"binary_format_minor_version"`
BuildEpoch uint `maxminddb:"build_epoch"`
DatabaseType string `maxminddb:"database_type"`
Description map[string]string `maxminddb:"description"`
IPVersion uint `maxminddb:"ip_version"`
Languages []string `maxminddb:"languages"`
NodeCount uint `maxminddb:"node_count"`
RecordSize uint `maxminddb:"record_size"`
}
// FromBytes takes a byte slice corresponding to a MaxMind DB file and returns
// a Reader structure or an error.
func FromBytes(buffer []byte) (*Reader, error) {
metadataStart := bytes.LastIndex(buffer, metadataStartMarker)
if metadataStart == -1 {
return nil, newInvalidDatabaseError("error opening database: invalid MaxMind DB file")
}
metadataStart += len(metadataStartMarker)
metadataDecoder := decoder{buffer[metadataStart:]}
var metadata Metadata
rvMetdata := reflect.ValueOf(&metadata)
_, err := metadataDecoder.decode(0, rvMetdata, 0)
if err != nil {
return nil, err
}
searchTreeSize := metadata.NodeCount * metadata.RecordSize / 4
dataSectionStart := searchTreeSize + dataSectionSeparatorSize
dataSectionEnd := uint(metadataStart - len(metadataStartMarker))
if dataSectionStart > dataSectionEnd {
return nil, newInvalidDatabaseError("the MaxMind DB contains invalid metadata")
}
d := decoder{
buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart-len(metadataStartMarker)],
}
reader := &Reader{
buffer: buffer,
decoder: d,
Metadata: metadata,
ipv4Start: 0,
}
reader.ipv4Start, err = reader.startNode()
return reader, err
}
func (r *Reader) startNode() (uint, error) {
if r.Metadata.IPVersion != 6 {
return 0, nil
}
nodeCount := r.Metadata.NodeCount
node := uint(0)
var err error
for i := 0; i < 96 && node < nodeCount; i++ {
node, err = r.readNode(node, 0)
if err != nil {
return 0, err
}
}
return node, err
}
// Lookup takes an IP address as a net.IP structure and a pointer to the
// result value to Decode into.
func (r *Reader) Lookup(ipAddress net.IP, result interface{}) error {
if r.buffer == nil {
return errors.New("cannot call Lookup on a closed database")
}
pointer, err := r.lookupPointer(ipAddress)
if pointer == 0 || err != nil {
return err
}
return r.retrieveData(pointer, result)
}
// LookupOffset maps an argument net.IP to a corresponding record offset in the
// database. NotFound is returned if no such record is found, and a record may
// otherwise be extracted by passing the returned offset to Decode. LookupOffset
// is an advanced API, which exists to provide clients with a means to cache
// previously-decoded records.
func (r *Reader) LookupOffset(ipAddress net.IP) (uintptr, error) {
if r.buffer == nil {
return 0, errors.New("cannot call LookupOffset on a closed database")
}
pointer, err := r.lookupPointer(ipAddress)
if pointer == 0 || err != nil {
return NotFound, err
}
return r.resolveDataPointer(pointer)
}
// Decode the record at |offset| into |result|. The result value pointed to
// must be a data value that corresponds to a record in the database. This may
// include a struct representation of the data, a map capable of holding the
// data or an empty interface{} value.
//
// If result is a pointer to a struct, the struct need not include a field
// for every value that may be in the database. If a field is not present in
// the structure, the decoder will not decode that field, reducing the time
// required to decode the record.
//
// As a special case, a struct field of type uintptr will be used to capture
// the offset of the value. Decode may later be used to extract the stored
// value from the offset. MaxMind DBs are highly normalized: for example in
// the City database, all records of the same country will reference a
// single representative record for that country. This uintptr behavior allows
// clients to leverage this normalization in their own sub-record caching.
func (r *Reader) Decode(offset uintptr, result interface{}) error {
if r.buffer == nil {
return errors.New("cannot call Decode on a closed database")
}
return r.decode(offset, result)
}
func (r *Reader) decode(offset uintptr, result interface{}) error {
rv := reflect.ValueOf(result)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("result param must be a pointer")
}
_, err := r.decoder.decode(uint(offset), reflect.ValueOf(result), 0)
return err
}
func (r *Reader) lookupPointer(ipAddress net.IP) (uint, error) {
if ipAddress == nil {
return 0, errors.New("ipAddress passed to Lookup cannot be nil")
}
ipV4Address := ipAddress.To4()
if ipV4Address != nil {
ipAddress = ipV4Address
}
if len(ipAddress) == 16 && r.Metadata.IPVersion == 4 {
return 0, fmt.Errorf("error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database", ipAddress.String())
}
return r.findAddressInTree(ipAddress)
}
func (r *Reader) findAddressInTree(ipAddress net.IP) (uint, error) {
bitCount := uint(len(ipAddress) * 8)
var node uint
if bitCount == 32 {
node = r.ipv4Start
}
nodeCount := r.Metadata.NodeCount
for i := uint(0); i < bitCount && node < nodeCount; i++ {
bit := uint(1) & (uint(ipAddress[i>>3]) >> (7 - (i % 8)))
var err error
node, err = r.readNode(node, bit)
if err != nil {
return 0, err
}
}
if node == nodeCount {
// Record is empty
return 0, nil
} else if node > nodeCount {
return node, nil
}
return 0, newInvalidDatabaseError("invalid node in search tree")
}
func (r *Reader) readNode(nodeNumber uint, index uint) (uint, error) {
RecordSize := r.Metadata.RecordSize
baseOffset := nodeNumber * RecordSize / 4
var nodeBytes []byte
var prefix uint
switch RecordSize {
case 24:
offset := baseOffset + index*3
nodeBytes = r.buffer[offset : offset+3]
case 28:
prefix = uint(r.buffer[baseOffset+3])
if index != 0 {
prefix &= 0x0F
} else {
prefix = (0xF0 & prefix) >> 4
}
offset := baseOffset + index*4
nodeBytes = r.buffer[offset : offset+3]
case 32:
offset := baseOffset + index*4
nodeBytes = r.buffer[offset : offset+4]
default:
return 0, newInvalidDatabaseError("unknown record size: %d", RecordSize)
}
return uintFromBytes(prefix, nodeBytes), nil
}
func (r *Reader) retrieveData(pointer uint, result interface{}) error {
offset, err := r.resolveDataPointer(pointer)
if err != nil {
return err
}
return r.decode(offset, result)
}
func (r *Reader) resolveDataPointer(pointer uint) (uintptr, error) {
var resolved = uintptr(pointer - r.Metadata.NodeCount - dataSectionSeparatorSize)
if resolved > uintptr(len(r.buffer)) {
return 0, newInvalidDatabaseError("the MaxMind DB file's search tree is corrupt")
}
return resolved, nil
}

View File

@@ -0,0 +1,28 @@
// +build appengine
package maxminddb
import "io/ioutil"
// Open takes a string path to a MaxMind DB file and returns a Reader
// structure or an error. The database file is opened using a memory map,
// except on Google App Engine where mmap is not supported; there the database
// is loaded into memory. Use the Close method on the Reader object to return
// the resources to the system.
func Open(file string) (*Reader, error) {
bytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
return FromBytes(bytes)
}
// Close unmaps the database file from virtual memory and returns the
// resources to the system. If called on a Reader opened using FromBytes
// or Open on Google App Engine, this method sets the underlying buffer
// to nil, returning the resources to the system.
func (r *Reader) Close() error {
r.buffer = nil
return nil
}

View File

@@ -0,0 +1,63 @@
// +build !appengine
package maxminddb
import (
"os"
"runtime"
)
// Open takes a string path to a MaxMind DB file and returns a Reader
// structure or an error. The database file is opened using a memory map,
// except on Google App Engine where mmap is not supported; there the database
// is loaded into memory. Use the Close method on the Reader object to return
// the resources to the system.
func Open(file string) (*Reader, error) {
mapFile, err := os.Open(file)
if err != nil {
return nil, err
}
defer func() {
if rerr := mapFile.Close(); rerr != nil {
err = rerr
}
}()
stats, err := mapFile.Stat()
if err != nil {
return nil, err
}
fileSize := int(stats.Size())
mmap, err := mmap(int(mapFile.Fd()), fileSize)
if err != nil {
return nil, err
}
reader, err := FromBytes(mmap)
if err != nil {
if err2 := munmap(mmap); err2 != nil {
// failing to unmap the file is probably the more severe error
return nil, err2
}
return nil, err
}
reader.hasMappedFile = true
runtime.SetFinalizer(reader, (*Reader).Close)
return reader, err
}
// Close unmaps the database file from virtual memory and returns the
// resources to the system. If called on a Reader opened using FromBytes
// or Open on Google App Engine, this method does nothing.
func (r *Reader) Close() error {
var err error
if r.hasMappedFile {
runtime.SetFinalizer(r, nil)
r.hasMappedFile = false
err = munmap(r.buffer)
}
r.buffer = nil
return err
}

108
vendor/github.com/oschwald/maxminddb-golang/traverse.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
package maxminddb
import "net"
// Internal structure used to keep track of nodes we still need to visit.
type netNode struct {
ip net.IP
bit uint
pointer uint
}
// Networks represents a set of subnets that we are iterating over.
type Networks struct {
reader *Reader
nodes []netNode // Nodes we still have to visit.
lastNode netNode
err error
}
// Networks returns an iterator that can be used to traverse all networks in
// the database.
//
// Please note that a MaxMind DB may map IPv4 networks into several locations
// in in an IPv6 database. This iterator will iterate over all of these
// locations separately.
func (r *Reader) Networks() *Networks {
s := 4
if r.Metadata.IPVersion == 6 {
s = 16
}
return &Networks{
reader: r,
nodes: []netNode{
{
ip: make(net.IP, s),
},
},
}
}
// Next prepares the next network for reading with the Network method. It
// returns true if there is another network to be processed and false if there
// are no more networks or if there is an error.
func (n *Networks) Next() bool {
for len(n.nodes) > 0 {
node := n.nodes[len(n.nodes)-1]
n.nodes = n.nodes[:len(n.nodes)-1]
for {
if node.pointer < n.reader.Metadata.NodeCount {
ipRight := make(net.IP, len(node.ip))
copy(ipRight, node.ip)
if len(ipRight) <= int(node.bit>>3) {
n.err = newInvalidDatabaseError(
"invalid search tree at %v/%v", ipRight, node.bit)
return false
}
ipRight[node.bit>>3] |= 1 << (7 - (node.bit % 8))
rightPointer, err := n.reader.readNode(node.pointer, 1)
if err != nil {
n.err = err
return false
}
node.bit++
n.nodes = append(n.nodes, netNode{
pointer: rightPointer,
ip: ipRight,
bit: node.bit,
})
node.pointer, err = n.reader.readNode(node.pointer, 0)
if err != nil {
n.err = err
return false
}
} else if node.pointer > n.reader.Metadata.NodeCount {
n.lastNode = node
return true
} else {
break
}
}
}
return false
}
// Network returns the current network or an error if there is a problem
// decoding the data for the network. It takes a pointer to a result value to
// decode the network's data into.
func (n *Networks) Network(result interface{}) (*net.IPNet, error) {
if err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil {
return nil, err
}
return &net.IPNet{
IP: n.lastNode.ip,
Mask: net.CIDRMask(int(n.lastNode.bit), len(n.lastNode.ip)*8),
}, nil
}
// Err returns an error, if any, that was encountered during iteration.
func (n *Networks) Err() error {
return n.err
}

185
vendor/github.com/oschwald/maxminddb-golang/verifier.go generated vendored Normal file
View File

@@ -0,0 +1,185 @@
package maxminddb
import "reflect"
type verifier struct {
reader *Reader
}
// Verify checks that the database is valid. It validates the search tree,
// the data section, and the metadata section. This verifier is stricter than
// the specification and may return errors on databases that are readable.
func (r *Reader) Verify() error {
v := verifier{r}
if err := v.verifyMetadata(); err != nil {
return err
}
return v.verifyDatabase()
}
func (v *verifier) verifyMetadata() error {
metadata := v.reader.Metadata
if metadata.BinaryFormatMajorVersion != 2 {
return testError(
"binary_format_major_version",
2,
metadata.BinaryFormatMajorVersion,
)
}
if metadata.BinaryFormatMinorVersion != 0 {
return testError(
"binary_format_minor_version",
0,
metadata.BinaryFormatMinorVersion,
)
}
if metadata.DatabaseType == "" {
return testError(
"database_type",
"non-empty string",
metadata.DatabaseType,
)
}
if len(metadata.Description) == 0 {
return testError(
"description",
"non-empty slice",
metadata.Description,
)
}
if metadata.IPVersion != 4 && metadata.IPVersion != 6 {
return testError(
"ip_version",
"4 or 6",
metadata.IPVersion,
)
}
if metadata.RecordSize != 24 &&
metadata.RecordSize != 28 &&
metadata.RecordSize != 32 {
return testError(
"record_size",
"24, 28, or 32",
metadata.RecordSize,
)
}
if metadata.NodeCount == 0 {
return testError(
"node_count",
"positive integer",
metadata.NodeCount,
)
}
return nil
}
func (v *verifier) verifyDatabase() error {
offsets, err := v.verifySearchTree()
if err != nil {
return err
}
if err := v.verifyDataSectionSeparator(); err != nil {
return err
}
return v.verifyDataSection(offsets)
}
func (v *verifier) verifySearchTree() (map[uint]bool, error) {
offsets := make(map[uint]bool)
it := v.reader.Networks()
for it.Next() {
offset, err := v.reader.resolveDataPointer(it.lastNode.pointer)
if err != nil {
return nil, err
}
offsets[uint(offset)] = true
}
if err := it.Err(); err != nil {
return nil, err
}
return offsets, nil
}
func (v *verifier) verifyDataSectionSeparator() error {
separatorStart := v.reader.Metadata.NodeCount * v.reader.Metadata.RecordSize / 4
separator := v.reader.buffer[separatorStart : separatorStart+dataSectionSeparatorSize]
for _, b := range separator {
if b != 0 {
return newInvalidDatabaseError("unexpected byte in data separator: %v", separator)
}
}
return nil
}
func (v *verifier) verifyDataSection(offsets map[uint]bool) error {
pointerCount := len(offsets)
decoder := v.reader.decoder
var offset uint
bufferLen := uint(len(decoder.buffer))
for offset < bufferLen {
var data interface{}
rv := reflect.ValueOf(&data)
newOffset, err := decoder.decode(offset, rv, 0)
if err != nil {
return newInvalidDatabaseError("received decoding error (%v) at offset of %v", err, offset)
}
if newOffset <= offset {
return newInvalidDatabaseError("data section offset unexpectedly went from %v to %v", offset, newOffset)
}
pointer := offset
if _, ok := offsets[pointer]; ok {
delete(offsets, pointer)
} else {
return newInvalidDatabaseError("found data (%v) at %v that the search tree does not point to", data, pointer)
}
offset = newOffset
}
if offset != bufferLen {
return newInvalidDatabaseError(
"unexpected data at the end of the data section (last offset: %v, end: %v)",
offset,
bufferLen,
)
}
if len(offsets) != 0 {
return newInvalidDatabaseError(
"found %v pointers (of %v) in the search tree that we did not see in the data section",
len(offsets),
pointerCount,
)
}
return nil
}
func testError(
field string,
expected interface{},
actual interface{},
) error {
return newInvalidDatabaseError(
"%v - Expected: %v Actual: %v",
field,
expected,
actual,
)
}