mirror of
https://github.com/xiaoqidun/qqwry.git
synced 2023-06-27 14:38:22 +08:00
162 lines
3.3 KiB
Go
162 lines
3.3 KiB
Go
package qqwry
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
|
"golang.org/x/text/transform"
|
|
"io/ioutil"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
data []byte
|
|
dataLen uint32
|
|
ipCache = &sync.Map{}
|
|
)
|
|
|
|
const (
|
|
indexLen = 7
|
|
redirectMode1 = 0x01
|
|
redirectMode2 = 0x02
|
|
)
|
|
|
|
type cache struct {
|
|
City string
|
|
Area string
|
|
}
|
|
|
|
func byte3ToUInt32(data []byte) uint32 {
|
|
i := uint32(data[0]) & 0xff
|
|
i |= (uint32(data[1]) << 8) & 0xff00
|
|
i |= (uint32(data[2]) << 16) & 0xff0000
|
|
return i
|
|
}
|
|
|
|
func gb18030Decode(src []byte) string {
|
|
in := bytes.NewReader(src)
|
|
out := transform.NewReader(in, simplifiedchinese.GB18030.NewDecoder())
|
|
d, _ := ioutil.ReadAll(out)
|
|
return string(d)
|
|
}
|
|
|
|
// QueryIP 从内存或缓存查询IP
|
|
func QueryIP(queryIp string) (city string, area string, err error) {
|
|
if v, ok := ipCache.Load(queryIp); ok {
|
|
city = v.(cache).City
|
|
area = v.(cache).Area
|
|
return
|
|
}
|
|
ip := net.ParseIP(queryIp).To4()
|
|
if ip == nil {
|
|
err = errors.New("ip is not ipv4")
|
|
return
|
|
}
|
|
ip32 := binary.BigEndian.Uint32(ip)
|
|
posA := binary.LittleEndian.Uint32(data[:4])
|
|
posZ := binary.LittleEndian.Uint32(data[4:8])
|
|
var offset uint32 = 0
|
|
for {
|
|
mid := posA + (((posZ-posA)/indexLen)>>1)*indexLen
|
|
buf := data[mid : mid+indexLen]
|
|
_ip := binary.LittleEndian.Uint32(buf[:4])
|
|
if posZ-posA == indexLen {
|
|
offset = byte3ToUInt32(buf[4:])
|
|
buf = data[mid+indexLen : mid+indexLen+indexLen]
|
|
if ip32 < binary.LittleEndian.Uint32(buf[:4]) {
|
|
break
|
|
} else {
|
|
offset = 0
|
|
break
|
|
}
|
|
}
|
|
if _ip > ip32 {
|
|
posZ = mid
|
|
} else if _ip < ip32 {
|
|
posA = mid
|
|
} else if _ip == ip32 {
|
|
offset = byte3ToUInt32(buf[4:])
|
|
break
|
|
}
|
|
}
|
|
if offset <= 0 {
|
|
err = errors.New("ip not found")
|
|
return
|
|
}
|
|
posM := offset + 4
|
|
mode := data[posM]
|
|
var areaPos uint32
|
|
switch mode {
|
|
case redirectMode1:
|
|
posC := byte3ToUInt32(data[posM+1 : posM+4])
|
|
mode = data[posC]
|
|
posCA := posC
|
|
if mode == redirectMode2 {
|
|
posCA = byte3ToUInt32(data[posC+1 : posC+4])
|
|
posC += 4
|
|
}
|
|
for i := posCA; i < dataLen; i++ {
|
|
if data[i] == 0 {
|
|
city = string(data[posCA:i])
|
|
break
|
|
}
|
|
}
|
|
if mode != redirectMode2 {
|
|
posC += uint32(len(city) + 1)
|
|
}
|
|
areaPos = posC
|
|
case redirectMode2:
|
|
posCA := byte3ToUInt32(data[posM+1 : posM+4])
|
|
for i := posCA; i < dataLen; i++ {
|
|
if data[i] == 0 {
|
|
city = string(data[posCA:i])
|
|
break
|
|
}
|
|
}
|
|
areaPos = offset + 8
|
|
default:
|
|
posCA := offset + 4
|
|
for i := posCA; i < dataLen; i++ {
|
|
if data[i] == 0 {
|
|
city = string(data[posCA:i])
|
|
break
|
|
}
|
|
}
|
|
areaPos = offset + uint32(5+len(city))
|
|
}
|
|
areaMode := data[areaPos]
|
|
if areaMode == redirectMode1 || areaMode == redirectMode2 {
|
|
areaPos = byte3ToUInt32(data[areaPos+1 : areaPos+4])
|
|
}
|
|
if areaPos > 0 {
|
|
for i := areaPos; i < dataLen; i++ {
|
|
if data[i] == 0 {
|
|
area = string(data[areaPos:i])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
city = gb18030Decode([]byte(city))
|
|
area = gb18030Decode([]byte(area))
|
|
ipCache.Store(queryIp, cache{City: city, Area: area})
|
|
return
|
|
}
|
|
|
|
// LoadData 从内存加载IP数据库
|
|
func LoadData(database []byte) {
|
|
data = database
|
|
dataLen = uint32(len(data))
|
|
}
|
|
|
|
// LoadFile 从文件加载IP数据库
|
|
func LoadFile(filepath string) (err error) {
|
|
data, err = ioutil.ReadFile(filepath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
dataLen = uint32(len(data))
|
|
return
|
|
}
|