forked from casdoor/casdoor
200 lines
4.3 KiB
Go
200 lines
4.3 KiB
Go
// Copyright 2022 The casbin Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ip
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net"
|
|
)
|
|
|
|
const Null = "N/A"
|
|
|
|
var (
|
|
ErrInvalidIp = errors.New("invalid ip format")
|
|
std *Locator
|
|
)
|
|
|
|
// Init default locator with dataFile
|
|
func Init(dataFile string) (err error) {
|
|
if std != nil {
|
|
return
|
|
}
|
|
std, err = NewLocator(dataFile)
|
|
return
|
|
}
|
|
|
|
// Init default locator with data
|
|
func InitWithData(data []byte) {
|
|
if std != nil {
|
|
return
|
|
}
|
|
std = NewLocatorWithData(data)
|
|
return
|
|
}
|
|
|
|
// Find locationInfo by ip string
|
|
// It will return err when ipstr is not a valid format
|
|
func Find(ipstr string) (*LocationInfo, error) {
|
|
return std.Find(ipstr)
|
|
}
|
|
|
|
// Find locationInfo by uint32
|
|
func FindByUint(ip uint32) *LocationInfo {
|
|
return std.FindByUint(ip)
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// New locator with dataFile
|
|
func NewLocator(dataFile string) (loc *Locator, err error) {
|
|
data, err := ioutil.ReadFile(dataFile)
|
|
if err != nil {
|
|
return
|
|
}
|
|
loc = NewLocatorWithData(data)
|
|
return
|
|
}
|
|
|
|
// New locator with data
|
|
func NewLocatorWithData(data []byte) (loc *Locator) {
|
|
loc = new(Locator)
|
|
loc.init(data)
|
|
return
|
|
}
|
|
|
|
type Locator struct {
|
|
textData []byte
|
|
indexData1 []uint32
|
|
indexData2 []int
|
|
indexData3 []int
|
|
index []int
|
|
}
|
|
|
|
type LocationInfo struct {
|
|
Country string
|
|
Region string
|
|
City string
|
|
Isp string
|
|
}
|
|
|
|
// Find locationInfo by ip string
|
|
// It will return err when ipstr is not a valid format
|
|
func (loc *Locator) Find(ipstr string) (info *LocationInfo, err error) {
|
|
ip := net.ParseIP(ipstr).To4()
|
|
if ip == nil || ip.To4() == nil {
|
|
err = ErrInvalidIp
|
|
return
|
|
}
|
|
info = loc.FindByUint(binary.BigEndian.Uint32([]byte(ip)))
|
|
return
|
|
}
|
|
|
|
// Find locationInfo by uint32
|
|
func (loc *Locator) FindByUint(ip uint32) (info *LocationInfo) {
|
|
end := len(loc.indexData1) - 1
|
|
if ip>>24 != 0xff {
|
|
end = loc.index[(ip>>24)+1]
|
|
}
|
|
idx := loc.findIndexOffset(ip, loc.index[ip>>24], end)
|
|
off := loc.indexData2[idx]
|
|
return newLocationInfo(loc.textData[off : off+loc.indexData3[idx]])
|
|
}
|
|
|
|
// binary search
|
|
func (loc *Locator) findIndexOffset(ip uint32, start, end int) int {
|
|
for start < end {
|
|
mid := (start + end) / 2
|
|
if ip > loc.indexData1[mid] {
|
|
start = mid + 1
|
|
} else {
|
|
end = mid
|
|
}
|
|
}
|
|
|
|
if loc.indexData1[end] >= ip {
|
|
return end
|
|
}
|
|
|
|
return start
|
|
}
|
|
|
|
func (loc *Locator) init(data []byte) {
|
|
textoff := int(binary.BigEndian.Uint32(data[:4]))
|
|
|
|
loc.textData = data[textoff-1024:]
|
|
|
|
loc.index = make([]int, 256)
|
|
for i := 0; i < 256; i++ {
|
|
off := 4 + i*4
|
|
loc.index[i] = int(binary.LittleEndian.Uint32(data[off : off+4]))
|
|
}
|
|
|
|
nidx := (textoff - 4 - 1024 - 1024) / 8
|
|
|
|
loc.indexData1 = make([]uint32, nidx)
|
|
loc.indexData2 = make([]int, nidx)
|
|
loc.indexData3 = make([]int, nidx)
|
|
|
|
for i := 0; i < nidx; i++ {
|
|
off := 4 + 1024 + i*8
|
|
loc.indexData1[i] = binary.BigEndian.Uint32(data[off : off+4])
|
|
loc.indexData2[i] = int(uint32(data[off+4]) | uint32(data[off+5])<<8 | uint32(data[off+6])<<16)
|
|
loc.indexData3[i] = int(data[off+7])
|
|
}
|
|
return
|
|
}
|
|
|
|
func newLocationInfo(str []byte) *LocationInfo {
|
|
var info *LocationInfo
|
|
|
|
fields := bytes.Split(str, []byte("\t"))
|
|
switch len(fields) {
|
|
case 4:
|
|
// free version
|
|
info = &LocationInfo{
|
|
Country: string(fields[0]),
|
|
Region: string(fields[1]),
|
|
City: string(fields[2]),
|
|
}
|
|
case 5:
|
|
// pay version
|
|
info = &LocationInfo{
|
|
Country: string(fields[0]),
|
|
Region: string(fields[1]),
|
|
City: string(fields[2]),
|
|
Isp: string(fields[4]),
|
|
}
|
|
default:
|
|
panic("unexpected ip info:" + string(str))
|
|
}
|
|
|
|
if len(info.Country) == 0 {
|
|
info.Country = Null
|
|
}
|
|
if len(info.Region) == 0 {
|
|
info.Region = Null
|
|
}
|
|
if len(info.City) == 0 {
|
|
info.City = Null
|
|
}
|
|
if len(info.Isp) == 0 {
|
|
info.Isp = Null
|
|
}
|
|
return info
|
|
}
|