parent
96906f189e
commit
e92e32634c
@ -1,3 +1,3 @@
|
|||||||
package goip
|
package goip
|
||||||
|
|
||||||
const Version = "1.0.30"
|
const Version = "1.0.31"
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 67 MiB |
Binary file not shown.
@ -0,0 +1,46 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
accountId int64 // 账号id
|
||||||
|
licenseKey string // 许可证密钥
|
||||||
|
asnDb *geoip2.Reader
|
||||||
|
cityDb *geoip2.Reader
|
||||||
|
countryDb *geoip2.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(licenseKey string) (*Client, error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c := &Client{}
|
||||||
|
|
||||||
|
c.licenseKey = licenseKey
|
||||||
|
|
||||||
|
c.asnDb, err = geoip2.FromBytes(asnBuff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cityDb, err = geoip2.FromBytes(cityBuff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.countryDb, err = geoip2.FromBytes(countryBuff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() {
|
||||||
|
|
||||||
|
c.asnDb.Close()
|
||||||
|
c.cityDb.Close()
|
||||||
|
c.countryDb.Close()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OnlineDownload(downloadUrl string, downloadName string) {
|
||||||
|
resp, err := http.Get(downloadUrl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile("./"+downloadName, body, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.Printf("已下载最新 ip2region.xdb 数据库 %s ", "./"+downloadName)
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.dtapp.net/gostring"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) GetGeoLite2AsnDownloadUrl() string {
|
||||||
|
if c.licenseKey == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=YOUR_LICENSE_KEY&suffix=tar.gz", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (c *Client) GetGeoLite2AsnCsvDownloadUrl() string {
|
||||||
|
// if c.licenseKey == "" {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN-CSV&license_key=YOUR_LICENSE_KEY&suffix=zip", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (c *Client) GetGeoLite2CityDownloadUrl() string {
|
||||||
|
if c.licenseKey == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=YOUR_LICENSE_KEY&suffix=tar.gz", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (c *Client) GetGeoLite2CityCsvDownloadUrl() string {
|
||||||
|
// if c.licenseKey == "" {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&license_key=YOUR_LICENSE_KEY&suffix=zip", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (c *Client) GetGeoLite2CountryDownloadUrl() string {
|
||||||
|
if c.licenseKey == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (c *Client) GetGeoLite2CountryCsvDownloadUrl() string {
|
||||||
|
// if c.licenseKey == "" {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// return gostring.Replace("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country-CSV&license_key=YOUR_LICENSE_KEY&suffix=zip", "YOUR_LICENSE_KEY", c.licenseKey)
|
||||||
|
//}
|
@ -0,0 +1,75 @@
|
|||||||
|
package geoip
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed GeoLite2-ASN.mmdb
|
||||||
|
var asnBuff []byte
|
||||||
|
|
||||||
|
//go:embed GeoLite2-City.mmdb
|
||||||
|
var cityBuff []byte
|
||||||
|
|
||||||
|
//go:embed GeoLite2-Country.mmdb
|
||||||
|
var countryBuff []byte
|
||||||
|
|
||||||
|
// QueryCityResult 返回
|
||||||
|
type QueryCityResult struct {
|
||||||
|
Ip string `json:"ip,omitempty"` // ip
|
||||||
|
Continent struct {
|
||||||
|
Code string `json:"code,omitempty"` // 大陆代码
|
||||||
|
Name string `json:"name,omitempty"` // 大陆名称
|
||||||
|
} `json:"continent,omitempty"`
|
||||||
|
Country struct {
|
||||||
|
Code string `json:"code,omitempty"` // 国家代码
|
||||||
|
Name string `json:"name,omitempty"` // 国家名称
|
||||||
|
} `json:"country,omitempty"`
|
||||||
|
Province struct {
|
||||||
|
Code string `json:"code,omitempty"` // 省份代码
|
||||||
|
Name string `json:"name,omitempty"` // 省份名称
|
||||||
|
} `json:"province,omitempty"`
|
||||||
|
City struct {
|
||||||
|
Name string `json:"name,omitempty"` // 城市名称
|
||||||
|
} `json:"city,omitempty"`
|
||||||
|
Location struct {
|
||||||
|
TimeZone string `json:"time_zone,omitempty"` // 位置时区
|
||||||
|
Latitude float64 `json:"latitude,omitempty"` // 坐标纬度
|
||||||
|
Longitude float64 `json:"longitude,omitempty"` // 坐标经度
|
||||||
|
} `json:"location,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) QueryCity(ipAddress net.IP) (result QueryCityResult, err error) {
|
||||||
|
|
||||||
|
record, err := c.cityDb.City(ipAddress)
|
||||||
|
if err != nil {
|
||||||
|
return QueryCityResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ip
|
||||||
|
result.Ip = ipAddress.String()
|
||||||
|
|
||||||
|
// 大陆
|
||||||
|
result.Continent.Code = record.Continent.Code
|
||||||
|
result.Continent.Name = record.Continent.Names["zh-CN"]
|
||||||
|
|
||||||
|
// 国家
|
||||||
|
result.Country.Code = record.Country.IsoCode
|
||||||
|
result.Country.Name = record.Country.Names["zh-CN"]
|
||||||
|
|
||||||
|
// 省份
|
||||||
|
if len(record.Subdivisions) > 0 {
|
||||||
|
result.Province.Code = record.Subdivisions[0].IsoCode
|
||||||
|
result.Province.Name = record.Subdivisions[0].Names["zh-CN"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 城市
|
||||||
|
result.City.Name = record.City.Names["zh-CN"]
|
||||||
|
|
||||||
|
// 位置
|
||||||
|
result.Location.TimeZone = record.Location.TimeZone
|
||||||
|
result.Location.Latitude = record.Location.Latitude
|
||||||
|
result.Location.Longitude = record.Location.Longitude
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package ip2region_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OnlineDownload() {
|
||||||
|
resp, err := http.Get("https://ghproxy.com/?q=https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb?raw=true")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile("./ip2region.xdb", body, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.Printf("已下载最新 ip2region.xdb 数据库 %s ", "./ip2region.xdb")
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,72 @@
|
|||||||
|
package ip2region_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"go.dtapp.net/gostring"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed ip2region.xdb
|
||||||
|
var cBuff []byte
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
db *Searcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*Client, error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c := &Client{}
|
||||||
|
|
||||||
|
// 1、从 dbPath 加载整个 xdb 到内存
|
||||||
|
|
||||||
|
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
|
||||||
|
c.db, err = NewWithBuffer(cBuff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result 返回
|
||||||
|
type Result struct {
|
||||||
|
Ip string `json:"ip,omitempty"` // 查询的ip地址
|
||||||
|
Country string `json:"country,omitempty"` // 国家
|
||||||
|
Province string `json:"province,omitempty"` // 省份
|
||||||
|
City string `json:"city,omitempty"` // 城市
|
||||||
|
Operator string `json:"operator,omitempty"` // 运营商
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Query(ipAddress net.IP) (result Result, err error) {
|
||||||
|
|
||||||
|
// 备注:并发使用,用整个 xdb 缓存创建的 searcher 对象可以安全用于并发。
|
||||||
|
|
||||||
|
str, err := c.db.SearchByStr(ipAddress.String())
|
||||||
|
if err != nil {
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
split := gostring.Split(str, "|")
|
||||||
|
if len(split) <= 0 {
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Ip = ipAddress.String()
|
||||||
|
|
||||||
|
result.Country = split[0]
|
||||||
|
result.Province = split[2]
|
||||||
|
if result.Province == "0" {
|
||||||
|
result.Province = ""
|
||||||
|
}
|
||||||
|
result.City = split[3]
|
||||||
|
if result.City == "0" {
|
||||||
|
result.City = ""
|
||||||
|
}
|
||||||
|
result.Operator = split[4]
|
||||||
|
if result.Operator == "0" {
|
||||||
|
result.Operator = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
@ -0,0 +1,240 @@
|
|||||||
|
package ip2region_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderInfoLength = 256
|
||||||
|
VectorIndexRows = 256
|
||||||
|
VectorIndexCols = 256
|
||||||
|
VectorIndexSize = 8
|
||||||
|
SegmentIndexBlockSize = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Index policy define
|
||||||
|
|
||||||
|
type IndexPolicy int
|
||||||
|
|
||||||
|
const (
|
||||||
|
VectorIndexPolicy IndexPolicy = 1
|
||||||
|
BTreeIndexPolicy IndexPolicy = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i IndexPolicy) String() string {
|
||||||
|
switch i {
|
||||||
|
case VectorIndexPolicy:
|
||||||
|
return "VectorIndex"
|
||||||
|
case BTreeIndexPolicy:
|
||||||
|
return "BtreeIndex"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Header define
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
// data []byte
|
||||||
|
Version uint16
|
||||||
|
IndexPolicy IndexPolicy
|
||||||
|
CreatedAt uint32
|
||||||
|
StartIndexPtr uint32
|
||||||
|
EndIndexPtr uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHeader(input []byte) (*Header, error) {
|
||||||
|
if len(input) < 16 {
|
||||||
|
return nil, fmt.Errorf("invalid input buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Header{
|
||||||
|
Version: binary.LittleEndian.Uint16(input),
|
||||||
|
IndexPolicy: IndexPolicy(binary.LittleEndian.Uint16(input[2:])),
|
||||||
|
CreatedAt: binary.LittleEndian.Uint32(input[4:]),
|
||||||
|
StartIndexPtr: binary.LittleEndian.Uint32(input[8:]),
|
||||||
|
EndIndexPtr: binary.LittleEndian.Uint32(input[12:]),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- searcher implementation
|
||||||
|
|
||||||
|
type Searcher struct {
|
||||||
|
handle *os.File
|
||||||
|
|
||||||
|
// header info
|
||||||
|
header *Header
|
||||||
|
ioCount int
|
||||||
|
|
||||||
|
// use it only when this feature enabled.
|
||||||
|
// Preload the vector index will reduce the number of IO operations
|
||||||
|
// thus speedup the search process
|
||||||
|
vectorIndex []byte
|
||||||
|
|
||||||
|
// content buffer.
|
||||||
|
// running with the whole xdb file cached
|
||||||
|
contentBuff []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseNew(dbFile string, vIndex []byte, cBuff []byte) (*Searcher, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// content buff first
|
||||||
|
if cBuff != nil {
|
||||||
|
return &Searcher{
|
||||||
|
vectorIndex: nil,
|
||||||
|
contentBuff: cBuff,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the xdb binary file
|
||||||
|
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Searcher{
|
||||||
|
handle: handle,
|
||||||
|
vectorIndex: vIndex,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithFileOnly(dbFile string) (*Searcher, error) {
|
||||||
|
return baseNew(dbFile, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithVectorIndex(dbFile string, vIndex []byte) (*Searcher, error) {
|
||||||
|
return baseNew(dbFile, vIndex, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithBuffer(cBuff []byte) (*Searcher, error) {
|
||||||
|
return baseNew("", nil, cBuff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Searcher) Close() {
|
||||||
|
if s.handle != nil {
|
||||||
|
err := s.handle.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIOCount return the global io count for the last search
|
||||||
|
func (s *Searcher) GetIOCount() int {
|
||||||
|
return s.ioCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchByStr find the region for the specified ip string
|
||||||
|
func (s *Searcher) SearchByStr(str string) (string, error) {
|
||||||
|
ip, err := CheckIP(str)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Search(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search find the region for the specified long ip
|
||||||
|
func (s *Searcher) Search(ip uint32) (string, error) {
|
||||||
|
// reset the global ioCount
|
||||||
|
s.ioCount = 0
|
||||||
|
|
||||||
|
// locate the segment index block based on the vector index
|
||||||
|
var il0 = (ip >> 24) & 0xFF
|
||||||
|
var il1 = (ip >> 16) & 0xFF
|
||||||
|
var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize
|
||||||
|
var sPtr, ePtr = uint32(0), uint32(0)
|
||||||
|
if s.vectorIndex != nil {
|
||||||
|
sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:])
|
||||||
|
ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:])
|
||||||
|
} else if s.contentBuff != nil {
|
||||||
|
sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:])
|
||||||
|
ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:])
|
||||||
|
} else {
|
||||||
|
// read the vector index block
|
||||||
|
var buff = make([]byte, VectorIndexSize)
|
||||||
|
err := s.read(int64(HeaderInfoLength+idx), buff)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sPtr = binary.LittleEndian.Uint32(buff)
|
||||||
|
ePtr = binary.LittleEndian.Uint32(buff[4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("sPtr=%d, ePtr=%d", sPtr, ePtr)
|
||||||
|
|
||||||
|
// binary search the segment index to get the region
|
||||||
|
var dataLen, dataPtr = 0, uint32(0)
|
||||||
|
var buff = make([]byte, SegmentIndexBlockSize)
|
||||||
|
var l, h = 0, int((ePtr - sPtr) / SegmentIndexBlockSize)
|
||||||
|
for l <= h {
|
||||||
|
m := (l + h) >> 1
|
||||||
|
p := sPtr + uint32(m*SegmentIndexBlockSize)
|
||||||
|
err := s.read(int64(p), buff)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("read segment index at %d: %w", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the data step by step to reduce the unnecessary operations
|
||||||
|
sip := binary.LittleEndian.Uint32(buff)
|
||||||
|
if ip < sip {
|
||||||
|
h = m - 1
|
||||||
|
} else {
|
||||||
|
eip := binary.LittleEndian.Uint32(buff[4:])
|
||||||
|
if ip > eip {
|
||||||
|
l = m + 1
|
||||||
|
} else {
|
||||||
|
dataLen = int(binary.LittleEndian.Uint16(buff[8:]))
|
||||||
|
dataPtr = binary.LittleEndian.Uint32(buff[10:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("dataLen: %d, dataPtr: %d", dataLen, dataPtr)
|
||||||
|
if dataLen == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// load and return the region data
|
||||||
|
var regionBuff = make([]byte, dataLen)
|
||||||
|
err := s.read(int64(dataPtr), regionBuff)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("read region at %d: %w", dataPtr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(regionBuff), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the data read operation based on the setting.
|
||||||
|
// content buffer first or will read from the file.
|
||||||
|
// this operation will invoke the Seek for file based read.
|
||||||
|
func (s *Searcher) read(offset int64, buff []byte) error {
|
||||||
|
if s.contentBuff != nil {
|
||||||
|
cLen := copy(buff, s.contentBuff[offset:])
|
||||||
|
if cLen != len(buff) {
|
||||||
|
return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := s.handle.Seek(offset, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("seek to %d: %w", offset, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ioCount++
|
||||||
|
rLen, err := s.handle.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("handle read: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rLen != len(buff) {
|
||||||
|
return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
package ip2region_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shiftIndex = []int{24, 16, 8, 0}
|
||||||
|
|
||||||
|
func CheckIP(ip string) (uint32, error) {
|
||||||
|
var ps = strings.Split(ip, ".")
|
||||||
|
if len(ps) != 4 {
|
||||||
|
return 0, fmt.Errorf("invalid ip address `%s`", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = uint32(0)
|
||||||
|
for i, s := range ps {
|
||||||
|
d, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("the %dth part `%s` is not an integer", i, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d < 0 || d > 255 {
|
||||||
|
return 0, fmt.Errorf("the %dth part `%s` should be an integer bettween 0 and 255", i, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
val |= uint32(d) << shiftIndex[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the ip to integer
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Long2IP(ip uint32) string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d.%d", (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, ip&0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MidIP(sip uint32, eip uint32) uint32 {
|
||||||
|
return uint32((uint64(sip) + uint64(eip)) >> 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHeader load the header info from the specified handle
|
||||||
|
func LoadHeader(handle *os.File) (*Header, error) {
|
||||||
|
_, err := handle.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("seek to the header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buff = make([]byte, HeaderInfoLength)
|
||||||
|
rLen, err := handle.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rLen != len(buff) {
|
||||||
|
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewHeader(buff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHeaderFromFile load header info from the specified db file path
|
||||||
|
func LoadHeaderFromFile(dbFile string) (*Header, error) {
|
||||||
|
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := LoadHeader(handle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = handle.Close()
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHeaderFromBuff wrap the header info from the content buffer
|
||||||
|
func LoadHeaderFromBuff(cBuff []byte) (*Header, error) {
|
||||||
|
return NewHeader(cBuff[0:256])
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadVectorIndex util function to load the vector index from the specified file handle
|
||||||
|
func LoadVectorIndex(handle *os.File) ([]byte, error) {
|
||||||
|
// load all the vector index block
|
||||||
|
_, err := handle.Seek(HeaderInfoLength, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("seek to vector index: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buff = make([]byte, VectorIndexRows*VectorIndexCols*VectorIndexSize)
|
||||||
|
rLen, err := handle.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rLen != len(buff) {
|
||||||
|
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadVectorIndexFromFile load vector index from a specified file path
|
||||||
|
func LoadVectorIndexFromFile(dbFile string) ([]byte, error) {
|
||||||
|
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vIndex, err := LoadVectorIndex(handle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = handle.Close()
|
||||||
|
return vIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadContent load the whole xdb content from the specified file handle
|
||||||
|
func LoadContent(handle *os.File) ([]byte, error) {
|
||||||
|
// get file size
|
||||||
|
fi, err := handle.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("stat: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := fi.Size()
|
||||||
|
|
||||||
|
// seek to the head of the file
|
||||||
|
_, err = handle.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("seek to get xdb file length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buff = make([]byte, size)
|
||||||
|
rLen, err := handle.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rLen != len(buff) {
|
||||||
|
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadContentFromFile load the whole xdb content from the specified db file path
|
||||||
|
func LoadContentFromFile(dbFile string) ([]byte, error) {
|
||||||
|
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cBuff, err := LoadContent(handle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = handle.Close()
|
||||||
|
return cBuff, nil
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package goip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go.dtapp.net/goip/geoip"
|
||||||
|
"go.dtapp.net/goip/ip2region_v2"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
QueryIncorrect = errors.New("ip地址不正确")
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryQqWryResult 返回
|
||||||
|
type QueryQqWryResult struct {
|
||||||
|
Ip string `json:"ip,omitempty"` // 查询的ip地址
|
||||||
|
Country string `json:"country,omitempty"` // 国家或地区
|
||||||
|
Area string `json:"area,omitempty"` // 区域
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryQqWry 纯真IP库
|
||||||
|
// https://www.cz88.net/
|
||||||
|
func (c *Client) QueryQqWry(ipAddress net.IP) (result QueryQqWryResult, err error) {
|
||||||
|
if ipAddress.To4() == nil {
|
||||||
|
return result, QueryIncorrect
|
||||||
|
}
|
||||||
|
resp := c.V4db.Query(ipAddress)
|
||||||
|
return QueryQqWryResult{
|
||||||
|
Ip: resp.IP,
|
||||||
|
Country: resp.Country,
|
||||||
|
Area: resp.Area,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIp2Region ip2region
|
||||||
|
// https://github.com/lionsoul2014/ip2region
|
||||||
|
func (c *Client) QueryIp2Region(ipAddress net.IP) (result QueryQqWryResult, err error) {
|
||||||
|
if ipAddress.To4() == nil {
|
||||||
|
return result, QueryIncorrect
|
||||||
|
}
|
||||||
|
resp := c.V4db.Query(ipAddress)
|
||||||
|
return QueryQqWryResult{
|
||||||
|
Ip: resp.IP,
|
||||||
|
Country: resp.Country,
|
||||||
|
Area: resp.Area,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIp2RegionV2 ip2region
|
||||||
|
// https://github.com/lionsoul2014/ip2region
|
||||||
|
func (c *Client) QueryIp2RegionV2(ipAddress net.IP) (result ip2region_v2.Result, err error) {
|
||||||
|
if ipAddress.To4() == nil {
|
||||||
|
return result, QueryIncorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := c.ip2regionV2Client.Query(ipAddress)
|
||||||
|
if err != nil {
|
||||||
|
return ip2region_v2.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryGeoIp ip2region
|
||||||
|
// https://www.maxmind.com/
|
||||||
|
func (c *Client) QueryGeoIp(ipAddress net.IP) (result geoip.QueryCityResult, err error) {
|
||||||
|
if ipAddress.String() == "<nil>" {
|
||||||
|
return result, QueryIncorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := c.geoIpClient.QueryCity(ipAddress)
|
||||||
|
if err != nil {
|
||||||
|
return geoip.QueryCityResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
.vscode
|
||||||
|
*.out
|
||||||
|
*.test
|
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "test-data"]
|
||||||
|
path = test-data
|
||||||
|
url = https://github.com/maxmind/MaxMind-DB.git
|
@ -0,0 +1,472 @@
|
|||||||
|
[run]
|
||||||
|
deadline = "10m"
|
||||||
|
|
||||||
|
tests = true
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
disable-all = true
|
||||||
|
enable = [
|
||||||
|
"asciicheck",
|
||||||
|
"bidichk",
|
||||||
|
"bodyclose",
|
||||||
|
"containedctx",
|
||||||
|
"contextcheck",
|
||||||
|
"deadcode",
|
||||||
|
"depguard",
|
||||||
|
"durationcheck",
|
||||||
|
"errcheck",
|
||||||
|
"errchkjson",
|
||||||
|
"errname",
|
||||||
|
"errorlint",
|
||||||
|
"exportloopref",
|
||||||
|
"forbidigo",
|
||||||
|
#"forcetypeassert",
|
||||||
|
"goconst",
|
||||||
|
"gocyclo",
|
||||||
|
"gocritic",
|
||||||
|
"godot",
|
||||||
|
"gofumpt",
|
||||||
|
"gomodguard",
|
||||||
|
"gosec",
|
||||||
|
"gosimple",
|
||||||
|
"govet",
|
||||||
|
"grouper",
|
||||||
|
"ineffassign",
|
||||||
|
"lll",
|
||||||
|
"makezero",
|
||||||
|
"maintidx",
|
||||||
|
"misspell",
|
||||||
|
"nakedret",
|
||||||
|
"nilerr",
|
||||||
|
"noctx",
|
||||||
|
"nolintlint",
|
||||||
|
"nosprintfhostport",
|
||||||
|
"predeclared",
|
||||||
|
"revive",
|
||||||
|
"rowserrcheck",
|
||||||
|
"sqlclosecheck",
|
||||||
|
"staticcheck",
|
||||||
|
"structcheck",
|
||||||
|
"stylecheck",
|
||||||
|
"tenv",
|
||||||
|
"tparallel",
|
||||||
|
"typecheck",
|
||||||
|
"unconvert",
|
||||||
|
"unparam",
|
||||||
|
"unused",
|
||||||
|
"varcheck",
|
||||||
|
"vetshadow",
|
||||||
|
"wastedassign",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Please note that we only use depguard for stdlib as gomodguard only
|
||||||
|
# supports modules currently. See https://github.com/ryancurrah/gomodguard/issues/12
|
||||||
|
[linters-settings.depguard]
|
||||||
|
list-type = "blacklist"
|
||||||
|
include-go-root = true
|
||||||
|
packages = [
|
||||||
|
# ioutil is deprecated. The functions have been moved elsewhere:
|
||||||
|
# https://golang.org/doc/go1.16#ioutil
|
||||||
|
"io/ioutil",
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.errcheck]
|
||||||
|
# Don't allow setting of error to the blank identifier. If there is a legtimate
|
||||||
|
# reason, there should be a nolint with an explanation.
|
||||||
|
check-blank = true
|
||||||
|
|
||||||
|
exclude-functions = [
|
||||||
|
# If we are rolling back a transaction, we are often already in an error
|
||||||
|
# state.
|
||||||
|
'(*database/sql.Tx).Rollback',
|
||||||
|
|
||||||
|
# It is reasonable to ignore errors if Cleanup fails in most cases.
|
||||||
|
'(*github.com/google/renameio/v2.PendingFile).Cleanup',
|
||||||
|
|
||||||
|
# We often don't care if removing a file failed (e.g., it doesn't exist)
|
||||||
|
'os.Remove',
|
||||||
|
'os.RemoveAll',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ignoring Close so that we don't have to have a bunch of
|
||||||
|
# `defer func() { _ = r.Close() }()` constructs when we
|
||||||
|
# don't actually care about the error.
|
||||||
|
ignore = "Close,fmt:.*"
|
||||||
|
|
||||||
|
[linters-settings.errorlint]
|
||||||
|
errorf = true
|
||||||
|
asserts = true
|
||||||
|
comparison = true
|
||||||
|
|
||||||
|
[linters-settings.exhaustive]
|
||||||
|
default-signifies-exhaustive = true
|
||||||
|
|
||||||
|
[linters-settings.forbidigo]
|
||||||
|
# Forbid the following identifiers
|
||||||
|
forbid = [
|
||||||
|
"^minFraud*",
|
||||||
|
"^maxMind*",
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.gocritic]
|
||||||
|
enabled-checks = [
|
||||||
|
"appendAssign",
|
||||||
|
"appendCombine",
|
||||||
|
"argOrder",
|
||||||
|
"assignOp",
|
||||||
|
"badCall",
|
||||||
|
"badCond",
|
||||||
|
"badLock",
|
||||||
|
"badRegexp",
|
||||||
|
"badSorting",
|
||||||
|
"boolExprSimplify",
|
||||||
|
"builtinShadow",
|
||||||
|
"builtinShadowDecl",
|
||||||
|
"captLocal",
|
||||||
|
"caseOrder",
|
||||||
|
"codegenComment",
|
||||||
|
"commentedOutCode",
|
||||||
|
"commentedOutImport",
|
||||||
|
"commentFormatting",
|
||||||
|
"defaultCaseOrder",
|
||||||
|
# Revive's defer rule already captures this. This caught no extra cases.
|
||||||
|
# "deferInLoop",
|
||||||
|
"deferUnlambda",
|
||||||
|
"deprecatedComment",
|
||||||
|
"docStub",
|
||||||
|
"dupArg",
|
||||||
|
"dupBranchBody",
|
||||||
|
"dupCase",
|
||||||
|
"dupImport",
|
||||||
|
"dupSubExpr",
|
||||||
|
"dynamicFmtString",
|
||||||
|
"elseif",
|
||||||
|
"emptyDecl",
|
||||||
|
"emptyFallthrough",
|
||||||
|
"emptyStringTest",
|
||||||
|
"equalFold",
|
||||||
|
"evalOrder",
|
||||||
|
"exitAfterDefer",
|
||||||
|
"exposedSyncMutex",
|
||||||
|
"externalErrorReassign",
|
||||||
|
# Given that all of our code runs on Linux and the / separate should
|
||||||
|
# work fine, this seems less important.
|
||||||
|
# "filepathJoin",
|
||||||
|
"flagDeref",
|
||||||
|
"flagName",
|
||||||
|
"hexLiteral",
|
||||||
|
"ifElseChain",
|
||||||
|
"importShadow",
|
||||||
|
"indexAlloc",
|
||||||
|
"initClause",
|
||||||
|
"ioutilDeprecated",
|
||||||
|
"mapKey",
|
||||||
|
"methodExprCall",
|
||||||
|
"nestingReduce",
|
||||||
|
"newDeref",
|
||||||
|
"nilValReturn",
|
||||||
|
"octalLiteral",
|
||||||
|
"offBy1",
|
||||||
|
"paramTypeCombine",
|
||||||
|
"preferDecodeRune",
|
||||||
|
"preferFilepathJoin",
|
||||||
|
"preferFprint",
|
||||||
|
"preferStringWriter",
|
||||||
|
"preferWriteByte",
|
||||||
|
"ptrToRefParam",
|
||||||
|
"rangeExprCopy",
|
||||||
|
"rangeValCopy",
|
||||||
|
"redundantSprint",
|
||||||
|
"regexpMust",
|
||||||
|
"regexpPattern",
|
||||||
|
# This might be good, but I don't think we want to encourage
|
||||||
|
# significant changes to regexes as we port stuff from Perl.
|
||||||
|
# "regexpSimplify",
|
||||||
|
"ruleguard",
|
||||||
|
"singleCaseSwitch",
|
||||||
|
"sliceClear",
|
||||||
|
"sloppyLen",
|
||||||
|
# This seems like it might also be good, but a lot of existing code
|
||||||
|
# fails.
|
||||||
|
# "sloppyReassign",
|
||||||
|
"returnAfterHttpError",
|
||||||
|
"sloppyTypeAssert",
|
||||||
|
"sortSlice",
|
||||||
|
"sprintfQuotedString",
|
||||||
|
"sqlQuery",
|
||||||
|
"stringsCompare",
|
||||||
|
"stringXbytes",
|
||||||
|
"switchTrue",
|
||||||
|
"syncMapLoadAndDelete",
|
||||||
|
"timeExprSimplify",
|
||||||
|
"todoCommentWithoutDetail",
|
||||||
|
"tooManyResultsChecker",
|
||||||
|
"truncateCmp",
|
||||||
|
"typeAssertChain",
|
||||||
|
"typeDefFirst",
|
||||||
|
"typeSwitchVar",
|
||||||
|
"typeUnparen",
|
||||||
|
"underef",
|
||||||
|
"unlabelStmt",
|
||||||
|
"unlambda",
|
||||||
|
# I am not sure we would want this linter and a lot of existing
|
||||||
|
# code fails.
|
||||||
|
# "unnamedResult",
|
||||||
|
"unnecessaryBlock",
|
||||||
|
"unnecessaryDefer",
|
||||||
|
"unslice",
|
||||||
|
"valSwap",
|
||||||
|
"weakCond",
|
||||||
|
"wrapperFunc",
|
||||||
|
"yodaStyleExpr",
|
||||||
|
# This requires explanations for "nolint" directives. This would be
|
||||||
|
# nice for gosec ones, but I am not sure we want it generally unless
|
||||||
|
# we can get the false positive rate lower.
|
||||||
|
# "whyNoLint"
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.gofumpt]
|
||||||
|
extra-rules = true
|
||||||
|
lang-version = "1.18"
|
||||||
|
|
||||||
|
[linters-settings.govet]
|
||||||
|
"enable-all" = true
|
||||||
|
|
||||||
|
[linters-settings.lll]
|
||||||
|
line-length = 120
|
||||||
|
tab-width = 4
|
||||||
|
|
||||||
|
[linters-settings.nolintlint]
|
||||||
|
allow-leading-space = false
|
||||||
|
allow-unused = false
|
||||||
|
allow-no-explanation = ["lll", "misspell"]
|
||||||
|
require-explanation = true
|
||||||
|
require-specific = true
|
||||||
|
|
||||||
|
[linters-settings.revive]
|
||||||
|
ignore-generated-header = true
|
||||||
|
severity = "warning"
|
||||||
|
|
||||||
|
# This might be nice but it is so common that it is hard
|
||||||
|
# to enable.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "add-constant"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "argument-limit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "atomic"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "bare-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "blank-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "bool-literal-in-expr"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "call-to-gc"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "cognitive-complexity"
|
||||||
|
|
||||||
|
# Probably a good rule, but we have a lot of names that
|
||||||
|
# only have case differences.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "confusing-naming"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "confusing-results"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "constant-logical-expr"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "context-as-argument"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "context-keys-type"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "cyclomatic"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "deep-exit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "defer"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "dot-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "duplicated-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "early-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "empty-block"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "empty-lines"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "errorf"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-strings"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "exported"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "file-header"
|
||||||
|
|
||||||
|
# We have a lot of flag parameters. This linter probably makes
|
||||||
|
# a good point, but we would need some cleanup or a lot of nolints.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "flag-parameter"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "function-result-limit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "get-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "identical-branches"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "if-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "imports-blacklist"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "import-shadowing"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "increment-decrement"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "indent-error-flow"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "line-length-limit"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "max-public-structs"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "modifies-parameter"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "modifies-value-receiver"
|
||||||
|
|
||||||
|
# We frequently use nested structs, particularly in tests.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "nested-structs"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "optimize-operands-order"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "package-comments"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range-val-address"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range-val-in-closure"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "receiver-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "redefines-builtin-id"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "string-of-int"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "struct-tag"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "superfluous-else"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "time-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unconditional-recursion"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unexported-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unexported-return"
|
||||||
|
|
||||||
|
# This is covered elsewhere and we want to ignore some
|
||||||
|
# functions such as fmt.Fprintf.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "unhandled-error"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unnecessary-stmt"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unreachable-code"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unused-parameter"
|
||||||
|
|
||||||
|
# We generally have unused receivers in tests for meeting the
|
||||||
|
# requirements of an interface.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "unused-receiver"
|
||||||
|
|
||||||
|
# This probably makes sense after we upgrade to 1.18
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "use-any"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "useless-break"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "var-declaration"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "var-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "waitgroup-by-value"
|
||||||
|
|
||||||
|
[linters-settings.unparam]
|
||||||
|
check-exported = true
|
||||||
|
|
||||||
|
[[issues.exclude-rules]]
|
||||||
|
linters = [
|
||||||
|
"govet"
|
||||||
|
]
|
||||||
|
# we want to enable almost all govet rules. It is easier to just filter out
|
||||||
|
# the ones we don't want:
|
||||||
|
#
|
||||||
|
# * fieldalignment - way too noisy. Although it is very useful in particular
|
||||||
|
# cases where we are trying to use as little memory as possible, having
|
||||||
|
# it go off on every struct isn't helpful.
|
||||||
|
# * shadow - although often useful, it complains about _many_ err
|
||||||
|
# shadowing assignments and some others where shadowing is clear.
|
||||||
|
text = "^(fieldalignment|shadow)"
|
@ -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.
|
@ -0,0 +1,93 @@
|
|||||||
|
# GeoIP2 Reader for Go #
|
||||||
|
|
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/github.com/oschwald/geoip2-golang)](https://pkg.go.dev/github.com/oschwald/geoip2-golang)
|
||||||
|
|
||||||
|
This library reads MaxMind [GeoLite2](http://dev.maxmind.com/geoip/geoip2/geolite2/)
|
||||||
|
and [GeoIP2](http://www.maxmind.com/en/geolocation_landing) databases.
|
||||||
|
|
||||||
|
This library is built using
|
||||||
|
[the Go maxminddb reader](https://github.com/oschwald/maxminddb-golang).
|
||||||
|
All data for the database record is decoded using this library. If you only
|
||||||
|
need several fields, you may get superior performance by using maxminddb's
|
||||||
|
`Lookup` directly with a result struct that only contains the required fields.
|
||||||
|
(See [example_test.go](https://github.com/oschwald/maxminddb-golang/blob/main/example_test.go)
|
||||||
|
in the maxminddb repository for an example of this.)
|
||||||
|
|
||||||
|
## Installation ##
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/oschwald/geoip2-golang
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage ##
|
||||||
|
|
||||||
|
[See GoDoc](http://godoc.org/github.com/oschwald/geoip2-golang) for
|
||||||
|
documentation and examples.
|
||||||
|
|
||||||
|
## Example ##
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := geoip2.Open("GeoIP2-City.mmdb")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
// If you are using strings that may be invalid, check that ip is not nil
|
||||||
|
ip := net.ParseIP("81.2.69.142")
|
||||||
|
record, err := db.City(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names["pt-BR"])
|
||||||
|
if len(record.Subdivisions) > 0 {
|
||||||
|
fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names["en"])
|
||||||
|
}
|
||||||
|
fmt.Printf("Russian country name: %v\n", record.Country.Names["ru"])
|
||||||
|
fmt.Printf("ISO country code: %v\n", record.Country.IsoCode)
|
||||||
|
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
|
||||||
|
fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
|
||||||
|
// Output:
|
||||||
|
// Portuguese (BR) city name: Londres
|
||||||
|
// English subdivision name: England
|
||||||
|
// Russian country name: Великобритания
|
||||||
|
// ISO country code: GB
|
||||||
|
// Time zone: Europe/London
|
||||||
|
// Coordinates: 51.5142, -0.0931
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing ##
|
||||||
|
|
||||||
|
Make sure you checked out test data submodule:
|
||||||
|
|
||||||
|
```
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute test suite:
|
||||||
|
|
||||||
|
```
|
||||||
|
go test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing ##
|
||||||
|
|
||||||
|
Contributions welcome! Please fork the repository and open a pull request
|
||||||
|
with your changes.
|
||||||
|
|
||||||
|
## License ##
|
||||||
|
|
||||||
|
This is free software, licensed under the ISC license.
|
@ -0,0 +1,418 @@
|
|||||||
|
// 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 Enterprise struct corresponds to the data in the GeoIP2 Enterprise
|
||||||
|
// database.
|
||||||
|
type Enterprise struct {
|
||||||
|
City struct {
|
||||||
|
Confidence uint8 `maxminddb:"confidence"`
|
||||||
|
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"`
|
||||||
|
IsoCode string `maxminddb:"iso_code"`
|
||||||
|
Names map[string]string `maxminddb:"names"`
|
||||||
|
Confidence uint8 `maxminddb:"confidence"`
|
||||||
|
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
|
||||||
|
} `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"`
|
||||||
|
Confidence uint8 `maxminddb:"confidence"`
|
||||||
|
} `maxminddb:"postal"`
|
||||||
|
RegisteredCountry struct {
|
||||||
|
GeoNameID uint `maxminddb:"geoname_id"`
|
||||||
|
IsoCode string `maxminddb:"iso_code"`
|
||||||
|
Names map[string]string `maxminddb:"names"`
|
||||||
|
Confidence uint8 `maxminddb:"confidence"`
|
||||||
|
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
|
||||||
|
} `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 {
|
||||||
|
Confidence uint8 `maxminddb:"confidence"`
|
||||||
|
GeoNameID uint `maxminddb:"geoname_id"`
|
||||||
|
IsoCode string `maxminddb:"iso_code"`
|
||||||
|
Names map[string]string `maxminddb:"names"`
|
||||||
|
} `maxminddb:"subdivisions"`
|
||||||
|
Traits struct {
|
||||||
|
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
|
||||||
|
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
|
||||||
|
ConnectionType string `maxminddb:"connection_type"`
|
||||||
|
Domain string `maxminddb:"domain"`
|
||||||
|
IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"`
|
||||||
|
IsLegitimateProxy bool `maxminddb:"is_legitimate_proxy"`
|
||||||
|
IsSatelliteProvider bool `maxminddb:"is_satellite_provider"`
|
||||||
|
ISP string `maxminddb:"isp"`
|
||||||
|
MobileCountryCode string `maxminddb:"mobile_country_code"`
|
||||||
|
MobileNetworkCode string `maxminddb:"mobile_network_code"`
|
||||||
|
Organization string `maxminddb:"organization"`
|
||||||
|
StaticIPScore float64 `maxminddb:"static_ip_score"`
|
||||||
|
UserType string `maxminddb:"user_type"`
|
||||||
|
} `maxminddb:"traits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
IsResidentialProxy bool `maxminddb:"is_residential_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"`
|
||||||
|
MobileCountryCode string `maxminddb:"mobile_country_code"`
|
||||||
|
MobileNetworkCode string `maxminddb:"mobile_network_code"`
|
||||||
|
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 %q 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
|
||||||
|
// used 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 "DBIP-ASN-Lite (compat=GeoLite2-ASN)",
|
||||||
|
"GeoLite2-ASN":
|
||||||
|
return isASN, nil
|
||||||
|
// We allow City lookups on Country for back compat
|
||||||
|
case "DBIP-City-Lite",
|
||||||
|
"DBIP-Country-Lite",
|
||||||
|
"DBIP-Country",
|
||||||
|
"DBIP-Location (compat=City)",
|
||||||
|
"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 "DBIP-ISP (compat=Enterprise)",
|
||||||
|
"DBIP-Location-ISP (compat=Enterprise)",
|
||||||
|
"GeoIP2-Enterprise":
|
||||||
|
return isEnterprise | isCity | isCountry, nil
|
||||||
|
case "GeoIP2-ISP",
|
||||||
|
"GeoIP2-Precision-ISP":
|
||||||
|
return isISP | isASN, nil
|
||||||
|
default:
|
||||||
|
return 0, UnknownDatabaseTypeError{reader.Metadata.DatabaseType}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enterprise takes an IP address as a net.IP struct and returns an Enterprise
|
||||||
|
// struct and/or an error. This is intended to be used with the GeoIP2
|
||||||
|
// Enterprise database.
|
||||||
|
func (r *Reader) Enterprise(ipAddress net.IP) (*Enterprise, error) {
|
||||||
|
if isEnterprise&r.databaseType == 0 {
|
||||||
|
return nil, InvalidMethodError{"Enterprise", r.Metadata().DatabaseType}
|
||||||
|
}
|
||||||
|
var enterprise Enterprise
|
||||||
|
err := r.mmdbReader.Lookup(ipAddress, &enterprise)
|
||||||
|
return &enterprise, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
.vscode
|
||||||
|
*.out
|
||||||
|
*.sw?
|
||||||
|
*.test
|
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "test-data"]
|
||||||
|
path = test-data
|
||||||
|
url = https://github.com/maxmind/MaxMind-DB.git
|
@ -0,0 +1,472 @@
|
|||||||
|
[run]
|
||||||
|
deadline = "10m"
|
||||||
|
|
||||||
|
tests = true
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
disable-all = true
|
||||||
|
enable = [
|
||||||
|
"asciicheck",
|
||||||
|
"bidichk",
|
||||||
|
"bodyclose",
|
||||||
|
"containedctx",
|
||||||
|
"contextcheck",
|
||||||
|
"deadcode",
|
||||||
|
"depguard",
|
||||||
|
"durationcheck",
|
||||||
|
"errcheck",
|
||||||
|
"errchkjson",
|
||||||
|
"errname",
|
||||||
|
"errorlint",
|
||||||
|
"exportloopref",
|
||||||
|
"forbidigo",
|
||||||
|
#"forcetypeassert",
|
||||||
|
"goconst",
|
||||||
|
"gocyclo",
|
||||||
|
"gocritic",
|
||||||
|
"godot",
|
||||||
|
"gofumpt",
|
||||||
|
"gomodguard",
|
||||||
|
"gosec",
|
||||||
|
"gosimple",
|
||||||
|
"govet",
|
||||||
|
"grouper",
|
||||||
|
"ineffassign",
|
||||||
|
"lll",
|
||||||
|
"makezero",
|
||||||
|
"maintidx",
|
||||||
|
"misspell",
|
||||||
|
"nakedret",
|
||||||
|
"nilerr",
|
||||||
|
"noctx",
|
||||||
|
"nolintlint",
|
||||||
|
"nosprintfhostport",
|
||||||
|
"predeclared",
|
||||||
|
"revive",
|
||||||
|
"rowserrcheck",
|
||||||
|
"sqlclosecheck",
|
||||||
|
"staticcheck",
|
||||||
|
"structcheck",
|
||||||
|
"stylecheck",
|
||||||
|
"tenv",
|
||||||
|
"tparallel",
|
||||||
|
"typecheck",
|
||||||
|
"unconvert",
|
||||||
|
"unparam",
|
||||||
|
"unused",
|
||||||
|
"varcheck",
|
||||||
|
"vetshadow",
|
||||||
|
"wastedassign",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Please note that we only use depguard for stdlib as gomodguard only
|
||||||
|
# supports modules currently. See https://github.com/ryancurrah/gomodguard/issues/12
|
||||||
|
[linters-settings.depguard]
|
||||||
|
list-type = "blacklist"
|
||||||
|
include-go-root = true
|
||||||
|
packages = [
|
||||||
|
# ioutil is deprecated. The functions have been moved elsewhere:
|
||||||
|
# https://golang.org/doc/go1.16#ioutil
|
||||||
|
"io/ioutil",
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.errcheck]
|
||||||
|
# Don't allow setting of error to the blank identifier. If there is a legtimate
|
||||||
|
# reason, there should be a nolint with an explanation.
|
||||||
|
check-blank = true
|
||||||
|
|
||||||
|
exclude-functions = [
|
||||||
|
# If we are rolling back a transaction, we are often already in an error
|
||||||
|
# state.
|
||||||
|
'(*database/sql.Tx).Rollback',
|
||||||
|
|
||||||
|
# It is reasonable to ignore errors if Cleanup fails in most cases.
|
||||||
|
'(*github.com/google/renameio/v2.PendingFile).Cleanup',
|
||||||
|
|
||||||
|
# We often don't care if removing a file failed (e.g., it doesn't exist)
|
||||||
|
'os.Remove',
|
||||||
|
'os.RemoveAll',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ignoring Close so that we don't have to have a bunch of
|
||||||
|
# `defer func() { _ = r.Close() }()` constructs when we
|
||||||
|
# don't actually care about the error.
|
||||||
|
ignore = "Close,fmt:.*"
|
||||||
|
|
||||||
|
[linters-settings.errorlint]
|
||||||
|
errorf = true
|
||||||
|
asserts = true
|
||||||
|
comparison = true
|
||||||
|
|
||||||
|
[linters-settings.exhaustive]
|
||||||
|
default-signifies-exhaustive = true
|
||||||
|
|
||||||
|
[linters-settings.forbidigo]
|
||||||
|
# Forbid the following identifiers
|
||||||
|
forbid = [
|
||||||
|
"^minFraud*",
|
||||||
|
"^maxMind*",
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.gocritic]
|
||||||
|
enabled-checks = [
|
||||||
|
"appendAssign",
|
||||||
|
"appendCombine",
|
||||||
|
"argOrder",
|
||||||
|
"assignOp",
|
||||||
|
"badCall",
|
||||||
|
"badCond",
|
||||||
|
"badLock",
|
||||||
|
"badRegexp",
|
||||||
|
"badSorting",
|
||||||
|
"boolExprSimplify",
|
||||||
|
"builtinShadow",
|
||||||
|
"builtinShadowDecl",
|
||||||
|
"captLocal",
|
||||||
|
"caseOrder",
|
||||||
|
"codegenComment",
|
||||||
|
"commentedOutCode",
|
||||||
|
"commentedOutImport",
|
||||||
|
"commentFormatting",
|
||||||
|
"defaultCaseOrder",
|
||||||
|
# Revive's defer rule already captures this. This caught no extra cases.
|
||||||
|
# "deferInLoop",
|
||||||
|
"deferUnlambda",
|
||||||
|
"deprecatedComment",
|
||||||
|
"docStub",
|
||||||
|
"dupArg",
|
||||||
|
"dupBranchBody",
|
||||||
|
"dupCase",
|
||||||
|
"dupImport",
|
||||||
|
"dupSubExpr",
|
||||||
|
"dynamicFmtString",
|
||||||
|
"elseif",
|
||||||
|
"emptyDecl",
|
||||||
|
"emptyFallthrough",
|
||||||
|
"emptyStringTest",
|
||||||
|
"equalFold",
|
||||||
|
"evalOrder",
|
||||||
|
"exitAfterDefer",
|
||||||
|
"exposedSyncMutex",
|
||||||
|
"externalErrorReassign",
|
||||||
|
# Given that all of our code runs on Linux and the / separate should
|
||||||
|
# work fine, this seems less important.
|
||||||
|
# "filepathJoin",
|
||||||
|
"flagDeref",
|
||||||
|
"flagName",
|
||||||
|
"hexLiteral",
|
||||||
|
"ifElseChain",
|
||||||
|
"importShadow",
|
||||||
|
"indexAlloc",
|
||||||
|
"initClause",
|
||||||
|
"ioutilDeprecated",
|
||||||
|
"mapKey",
|
||||||
|
"methodExprCall",
|
||||||
|
"nestingReduce",
|
||||||
|
"newDeref",
|
||||||
|
"nilValReturn",
|
||||||
|
"octalLiteral",
|
||||||
|
"offBy1",
|
||||||
|
"paramTypeCombine",
|
||||||
|
"preferDecodeRune",
|
||||||
|
"preferFilepathJoin",
|
||||||
|
"preferFprint",
|
||||||
|
"preferStringWriter",
|
||||||
|
"preferWriteByte",
|
||||||
|
"ptrToRefParam",
|
||||||
|
"rangeExprCopy",
|
||||||
|
"rangeValCopy",
|
||||||
|
"redundantSprint",
|
||||||
|
"regexpMust",
|
||||||
|
"regexpPattern",
|
||||||
|
# This might be good, but I don't think we want to encourage
|
||||||
|
# significant changes to regexes as we port stuff from Perl.
|
||||||
|
# "regexpSimplify",
|
||||||
|
"ruleguard",
|
||||||
|
"singleCaseSwitch",
|
||||||
|
"sliceClear",
|
||||||
|
"sloppyLen",
|
||||||
|
# This seems like it might also be good, but a lot of existing code
|
||||||
|
# fails.
|
||||||
|
# "sloppyReassign",
|
||||||
|
"returnAfterHttpError",
|
||||||
|
"sloppyTypeAssert",
|
||||||
|
"sortSlice",
|
||||||
|
"sprintfQuotedString",
|
||||||
|
"sqlQuery",
|
||||||
|
"stringsCompare",
|
||||||
|
"stringXbytes",
|
||||||
|
"switchTrue",
|
||||||
|
"syncMapLoadAndDelete",
|
||||||
|
"timeExprSimplify",
|
||||||
|
"todoCommentWithoutDetail",
|
||||||
|
"tooManyResultsChecker",
|
||||||
|
"truncateCmp",
|
||||||
|
"typeAssertChain",
|
||||||
|
"typeDefFirst",
|
||||||
|
"typeSwitchVar",
|
||||||
|
"typeUnparen",
|
||||||
|
"underef",
|
||||||
|
"unlabelStmt",
|
||||||
|
"unlambda",
|
||||||
|
# I am not sure we would want this linter and a lot of existing
|
||||||
|
# code fails.
|
||||||
|
# "unnamedResult",
|
||||||
|
"unnecessaryBlock",
|
||||||
|
"unnecessaryDefer",
|
||||||
|
"unslice",
|
||||||
|
"valSwap",
|
||||||
|
"weakCond",
|
||||||
|
"wrapperFunc",
|
||||||
|
"yodaStyleExpr",
|
||||||
|
# This requires explanations for "nolint" directives. This would be
|
||||||
|
# nice for gosec ones, but I am not sure we want it generally unless
|
||||||
|
# we can get the false positive rate lower.
|
||||||
|
# "whyNoLint"
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters-settings.gofumpt]
|
||||||
|
extra-rules = true
|
||||||
|
lang-version = "1.18"
|
||||||
|
|
||||||
|
[linters-settings.govet]
|
||||||
|
"enable-all" = true
|
||||||
|
|
||||||
|
[linters-settings.lll]
|
||||||
|
line-length = 120
|
||||||
|
tab-width = 4
|
||||||
|
|
||||||
|
[linters-settings.nolintlint]
|
||||||
|
allow-leading-space = false
|
||||||
|
allow-unused = false
|
||||||
|
allow-no-explanation = ["lll", "misspell"]
|
||||||
|
require-explanation = true
|
||||||
|
require-specific = true
|
||||||
|
|
||||||
|
[linters-settings.revive]
|
||||||
|
ignore-generated-header = true
|
||||||
|
severity = "warning"
|
||||||
|
|
||||||
|
# This might be nice but it is so common that it is hard
|
||||||
|
# to enable.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "add-constant"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "argument-limit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "atomic"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "bare-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "blank-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "bool-literal-in-expr"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "call-to-gc"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "cognitive-complexity"
|
||||||
|
|
||||||
|
# Probably a good rule, but we have a lot of names that
|
||||||
|
# only have case differences.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "confusing-naming"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "confusing-results"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "constant-logical-expr"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "context-as-argument"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "context-keys-type"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "cyclomatic"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "deep-exit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "defer"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "dot-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "duplicated-imports"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "early-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "empty-block"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "empty-lines"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "errorf"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "error-strings"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "exported"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "file-header"
|
||||||
|
|
||||||
|
# We have a lot of flag parameters. This linter probably makes
|
||||||
|
# a good point, but we would need some cleanup or a lot of nolints.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "flag-parameter"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "function-result-limit"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "get-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "identical-branches"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "if-return"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "imports-blacklist"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "import-shadowing"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "increment-decrement"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "indent-error-flow"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "line-length-limit"
|
||||||
|
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "max-public-structs"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "modifies-parameter"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "modifies-value-receiver"
|
||||||
|
|
||||||
|
# We frequently use nested structs, particularly in tests.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "nested-structs"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "optimize-operands-order"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "package-comments"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range-val-address"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "range-val-in-closure"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "receiver-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "redefines-builtin-id"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "string-of-int"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "struct-tag"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "superfluous-else"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "time-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unconditional-recursion"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unexported-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unexported-return"
|
||||||
|
|
||||||
|
# This is covered elsewhere and we want to ignore some
|
||||||
|
# functions such as fmt.Fprintf.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "unhandled-error"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unnecessary-stmt"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unreachable-code"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "unused-parameter"
|
||||||
|
|
||||||
|
# We generally have unused receivers in tests for meeting the
|
||||||
|
# requirements of an interface.
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "unused-receiver"
|
||||||
|
|
||||||
|
# This probably makes sense after we upgrade to 1.18
|
||||||
|
# [[linters-settings.revive.rules]]
|
||||||
|
# name = "use-any"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "useless-break"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "var-declaration"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "var-naming"
|
||||||
|
|
||||||
|
[[linters-settings.revive.rules]]
|
||||||
|
name = "waitgroup-by-value"
|
||||||
|
|
||||||
|
[linters-settings.unparam]
|
||||||
|
check-exported = true
|
||||||
|
|
||||||
|
[[issues.exclude-rules]]
|
||||||
|
linters = [
|
||||||
|
"govet"
|
||||||
|
]
|
||||||
|
# we want to enable almost all govet rules. It is easier to just filter out
|
||||||
|
# the ones we don't want:
|
||||||
|
#
|
||||||
|
# * fieldalignment - way too noisy. Although it is very useful in particular
|
||||||
|
# cases where we are trying to use as little memory as possible, having
|
||||||
|
# it go off on every struct isn't helpful.
|
||||||
|
# * shadow - although often useful, it complains about _many_ err
|
||||||
|
# shadowing assignments and some others where shadowing is clear.
|
||||||
|
text = "^(fieldalignment|shadow)"
|
@ -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.
|
@ -0,0 +1,36 @@
|
|||||||
|
# MaxMind DB Reader for Go #
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/oschwald/maxminddb-golang?status.svg)](https://godoc.org/github.com/oschwald/maxminddb-golang)
|
||||||
|
|
||||||
|
This is a Go reader for the MaxMind DB format. Although this can be used to
|
||||||
|
read [GeoLite2](http://dev.maxmind.com/geoip/geoip2/geolite2/) and
|
||||||
|
[GeoIP2](https://www.maxmind.com/en/geoip2-databases) databases,
|
||||||
|
[geoip2](https://github.com/oschwald/geoip2-golang) provides a higher-level
|
||||||
|
API for doing so.
|
||||||
|
|
||||||
|
This is not an official MaxMind API.
|
||||||
|
|
||||||
|
## Installation ##
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/oschwald/maxminddb-golang
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage ##
|
||||||
|
|
||||||
|
[See GoDoc](http://godoc.org/github.com/oschwald/maxminddb-golang) for
|
||||||
|
documentation and examples.
|
||||||
|
|
||||||
|
## Examples ##
|
||||||
|
|
||||||
|
See [GoDoc](http://godoc.org/github.com/oschwald/maxminddb-golang) or
|
||||||
|
`example_test.go` for examples.
|
||||||
|
|
||||||
|
## Contributing ##
|
||||||
|
|
||||||
|
Contributions welcome! Please fork the repository and open a pull request
|
||||||
|
with your changes.
|
||||||
|
|
||||||
|
## License ##
|
||||||
|
|
||||||
|
This is free software, licensed under the ISC License.
|
@ -0,0 +1,897 @@
|
|||||||
|
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
|
||||||
|
// We don't use the next two. They are placeholders. See the spec
|
||||||
|
// for more details.
|
||||||
|
_Container //nolint: deadcode, varcheck // above
|
||||||
|
_Marker //nolint: deadcode, varcheck // above
|
||||||
|
_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) decodeToDeserializer(
|
||||||
|
offset uint,
|
||||||
|
dser deserializer,
|
||||||
|
depth int,
|
||||||
|
getNext bool,
|
||||||
|
) (uint, error) {
|
||||||
|
if depth > maximumDataStructureDepth {
|
||||||
|
return 0, newInvalidDatabaseError(
|
||||||
|
"exceeded maximum data structure depth; database is likely corrupt",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
skip, err := dser.ShouldSkip(uintptr(offset))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
if getNext {
|
||||||
|
return d.nextValueOffset(offset, 1)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
typeNum, size, newOffset, err := d.decodeCtrlData(offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.decodeFromTypeToDeserializer(typeNum, size, newOffset, dser, 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) decodeFromTypeToDeserializer(
|
||||||
|
dtype dataType,
|
||||||
|
size uint,
|
||||||
|
offset uint,
|
||||||
|
dser deserializer,
|
||||||
|
depth int,
|
||||||
|
) (uint, error) {
|
||||||
|
// For these types, size has a special meaning
|
||||||
|
switch dtype {
|
||||||
|
case _Bool:
|
||||||
|
v, offset := d.decodeBool(size, offset)
|
||||||
|
return offset, dser.Bool(v)
|
||||||
|
case _Map:
|
||||||
|
return d.decodeMapToDeserializer(size, offset, dser, depth)
|
||||||
|
case _Pointer:
|
||||||
|
pointer, newOffset, err := d.decodePointer(size, offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = d.decodeToDeserializer(pointer, dser, depth, false)
|
||||||
|
return newOffset, err
|
||||||
|
case _Slice:
|
||||||
|
return d.decodeSliceToDeserializer(size, offset, dser, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the remaining types, size is the byte size
|
||||||
|
if offset+size > uint(len(d.buffer)) {
|
||||||
|
return 0, newOffsetError()
|
||||||
|
}
|
||||||
|
switch dtype {
|
||||||
|
case _Bytes:
|
||||||
|
v, offset := d.decodeBytes(size, offset)
|
||||||
|
return offset, dser.Bytes(v)
|
||||||
|
case _Float32:
|
||||||
|
v, offset := d.decodeFloat32(size, offset)
|
||||||
|
return offset, dser.Float32(v)
|
||||||
|
case _Float64:
|
||||||
|
v, offset := d.decodeFloat64(size, offset)
|
||||||
|
return offset, dser.Float64(v)
|
||||||
|
case _Int32:
|
||||||
|
v, offset := d.decodeInt(size, offset)
|
||||||
|
return offset, dser.Int32(int32(v))
|
||||||
|
case _String:
|
||||||
|
v, offset := d.decodeString(size, offset)
|
||||||
|
return offset, dser.String(v)
|
||||||
|
case _Uint16:
|
||||||
|
v, offset := d.decodeUint(size, offset)
|
||||||
|
return offset, dser.Uint16(uint16(v))
|
||||||
|
case _Uint32:
|
||||||
|
v, offset := d.decodeUint(size, offset)
|
||||||
|
return offset, dser.Uint32(uint32(v))
|
||||||
|
case _Uint64:
|
||||||
|
v, offset := d.decodeUint(size, offset)
|
||||||
|
return offset, dser.Uint64(v)
|
||||||
|
case _Uint128:
|
||||||
|
v, offset := d.decodeUint128(size, offset)
|
||||||
|
return offset, dser.Uint128(v)
|
||||||
|
default:
|
||||||
|
return 0, newInvalidDatabaseError("unknown type: %d", dtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) unmarshalBool(size, 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 := d.decodeBool(size, offset)
|
||||||
|
|
||||||
|
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, offset uint, result reflect.Value) (uint, error) {
|
||||||
|
value, newOffset := d.decodeBytes(size, offset)
|
||||||
|
|
||||||
|
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, 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 := d.decodeFloat32(size, offset)
|
||||||
|
|
||||||
|
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, 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 := d.decodeFloat64(size, offset)
|
||||||
|
|
||||||
|
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, 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 := d.decodeInt(size, offset)
|
||||||
|
|
||||||
|
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, 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, offset uint, result reflect.Value) (uint, error) {
|
||||||
|
value, newOffset := d.decodeString(size, offset)
|
||||||
|
|
||||||
|
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, 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 := d.decodeUint(size, offset)
|
||||||
|
|
||||||
|
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, 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 := d.decodeUint128(size, offset)
|
||||||
|
|
||||||
|
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, offset uint) (bool, uint) {
|
||||||
|
return size != 0, offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeBytes(size, offset uint) ([]byte, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
bytes := make([]byte, size)
|
||||||
|
copy(bytes, d.buffer[offset:newOffset])
|
||||||
|
return bytes, newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeFloat64(size, offset uint) (float64, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
bits := binary.BigEndian.Uint64(d.buffer[offset:newOffset])
|
||||||
|
return math.Float64frombits(bits), newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeFloat32(size, offset uint) (float32, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
bits := binary.BigEndian.Uint32(d.buffer[offset:newOffset])
|
||||||
|
return math.Float32frombits(bits), newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeInt(size, offset uint) (int, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
var val int32
|
||||||
|
for _, b := range d.buffer[offset:newOffset] {
|
||||||
|
val = (val << 8) | int32(b)
|
||||||
|
}
|
||||||
|
return int(val), newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeMap(
|
||||||
|
size uint,
|
||||||
|
offset uint,
|
||||||
|
result reflect.Value,
|
||||||
|
depth int,
|
||||||
|
) (uint, error) {
|
||||||
|
if result.IsNil() {
|
||||||
|
result.Set(reflect.MakeMapWithSize(result.Type(), int(size)))
|
||||||
|
}
|
||||||
|
|
||||||
|
mapType := result.Type()
|
||||||
|
keyValue := reflect.New(mapType.Key()).Elem()
|
||||||
|
elemType := mapType.Elem()
|
||||||
|
elemKind := elemType.Kind()
|
||||||
|
var elemValue reflect.Value
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if !elemValue.IsValid() || elemKind == reflect.Interface {
|
||||||
|
elemValue = reflect.New(elemType).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err = d.decode(offset, elemValue, depth)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValue.SetString(string(key))
|
||||||
|
result.SetMapIndex(keyValue, elemValue)
|
||||||
|
}
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeMapToDeserializer(
|
||||||
|
size uint,
|
||||||
|
offset uint,
|
||||||
|
dser deserializer,
|
||||||
|
depth int,
|
||||||
|
) (uint, error) {
|
||||||
|
err := dser.StartMap(size)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := uint(0); i < size; i++ {
|
||||||
|
// TODO - implement key/value skipping?
|
||||||
|
offset, err = d.decodeToDeserializer(offset, dser, depth, true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err = d.decodeToDeserializer(offset, dser, depth, true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dser.End()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
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 = 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) decodeSliceToDeserializer(
|
||||||
|
size uint,
|
||||||
|
offset uint,
|
||||||
|
dser deserializer,
|
||||||
|
depth int,
|
||||||
|
) (uint, error) {
|
||||||
|
err := dser.StartSlice(size)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := uint(0); i < size; i++ {
|
||||||
|
offset, err = d.decodeToDeserializer(offset, dser, depth, true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dser.End()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeString(size, offset uint) (string, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
return string(d.buffer[offset:newOffset]), newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeStruct(
|
||||||
|
size uint,
|
||||||
|
offset uint,
|
||||||
|
result reflect.Value,
|
||||||
|
depth int,
|
||||||
|
) (uint, error) {
|
||||||
|
fields := cachedFields(result)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldsType struct {
|
||||||
|
namedFields map[string]int
|
||||||
|
anonymousFields []int
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldsMap sync.Map
|
||||||
|
|
||||||
|
func cachedFields(result reflect.Value) *fieldsType {
|
||||||
|
resultType := result.Type()
|
||||||
|
|
||||||
|
if fields, ok := fieldsMap.Load(resultType); ok {
|
||||||
|
return fields.(*fieldsType)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
fields := &fieldsType{namedFields, anonymous}
|
||||||
|
fieldsMap.Store(resultType, fields)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeUint(size, offset uint) (uint64, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
bytes := d.buffer[offset:newOffset]
|
||||||
|
|
||||||
|
var val uint64
|
||||||
|
for _, b := range bytes {
|
||||||
|
val = (val << 8) | uint64(b)
|
||||||
|
}
|
||||||
|
return val, newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeUint128(size, offset uint) (*big.Int, uint) {
|
||||||
|
newOffset := offset + size
|
||||||
|
val := new(big.Int)
|
||||||
|
val.SetBytes(d.buffer[offset:newOffset])
|
||||||
|
|
||||||
|
return val, newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package maxminddb
|
||||||
|
|
||||||
|
import "math/big"
|
||||||
|
|
||||||
|
// deserializer is an interface for a type that deserializes an MaxMind DB
|
||||||
|
// data record to some other type. This exists as an alternative to the
|
||||||
|
// standard reflection API.
|
||||||
|
//
|
||||||
|
// This is fundamentally different than the Unmarshaler interface that
|
||||||
|
// several packages provide. A Deserializer will generally create the
|
||||||
|
// final struct or value rather than unmarshaling to itself.
|
||||||
|
//
|
||||||
|
// This interface and the associated unmarshaling code is EXPERIMENTAL!
|
||||||
|
// It is not currently covered by any Semantic Versioning guarantees.
|
||||||
|
// Use at your own risk.
|
||||||
|
type deserializer interface {
|
||||||
|
ShouldSkip(offset uintptr) (bool, error)
|
||||||
|
StartSlice(size uint) error
|
||||||
|
StartMap(size uint) error
|
||||||
|
End() error
|
||||||
|
String(string) error
|
||||||
|
Float64(float64) error
|
||||||
|
Bytes([]byte) error
|
||||||
|
Uint16(uint16) error
|
||||||
|
Uint32(uint32) error
|
||||||
|
Int32(int32) error
|
||||||
|
Uint64(uint64) error
|
||||||
|
Uint128(*big.Int) error
|
||||||
|
Bool(bool) error
|
||||||
|
Float32(float32) error
|
||||||
|
}
|
@ -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())
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
//go:build !windows && !appengine && !plan9
|
||||||
|
// +build !windows,!appengine,!plan9
|
||||||
|
|
||||||
|
package maxminddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mmap(fd, 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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package maxminddb
|
||||||
|
|
||||||
|
type nodeReader interface {
|
||||||
|
readLeft(uint) uint
|
||||||
|
readRight(uint) uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeReader24 struct {
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader24) readLeft(nodeNumber uint) uint {
|
||||||
|
return (uint(n.buffer[nodeNumber]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+1]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader24) readRight(nodeNumber uint) uint {
|
||||||
|
return (uint(n.buffer[nodeNumber+3]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+4]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+5])
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeReader28 struct {
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader28) readLeft(nodeNumber uint) uint {
|
||||||
|
return ((uint(n.buffer[nodeNumber+3]) & 0xF0) << 20) |
|
||||||
|
(uint(n.buffer[nodeNumber]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+1]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader28) readRight(nodeNumber uint) uint {
|
||||||
|
return ((uint(n.buffer[nodeNumber+3]) & 0x0F) << 24) |
|
||||||
|
(uint(n.buffer[nodeNumber+4]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+5]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+6])
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeReader32 struct {
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader32) readLeft(nodeNumber uint) uint {
|
||||||
|
return (uint(n.buffer[nodeNumber]) << 24) |
|
||||||
|
(uint(n.buffer[nodeNumber+1]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+2]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+3])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nodeReader32) readRight(nodeNumber uint) uint {
|
||||||
|
return (uint(n.buffer[nodeNumber+4]) << 24) |
|
||||||
|
(uint(n.buffer[nodeNumber+5]) << 16) |
|
||||||
|
(uint(n.buffer[nodeNumber+6]) << 8) |
|
||||||
|
uint(n.buffer[nodeNumber+7])
|
||||||
|
}
|
@ -0,0 +1,310 @@
|
|||||||
|
// Package maxminddb provides a reader for the MaxMind DB file format.
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// All of the methods on Reader are thread-safe. The struct may be safely
|
||||||
|
// shared across goroutines.
|
||||||
|
type Reader struct {
|
||||||
|
hasMappedFile bool
|
||||||
|
buffer []byte
|
||||||
|
nodeReader nodeReader
|
||||||
|
decoder decoder
|
||||||
|
Metadata Metadata
|
||||||
|
ipv4Start uint
|
||||||
|
ipv4StartBitDepth int
|
||||||
|
nodeOffsetMult uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata holds the metadata decoded from the MaxMind DB file. In particular
|
||||||
|
// it 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)],
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeBuffer := buffer[:searchTreeSize]
|
||||||
|
var nodeReader nodeReader
|
||||||
|
switch metadata.RecordSize {
|
||||||
|
case 24:
|
||||||
|
nodeReader = nodeReader24{buffer: nodeBuffer}
|
||||||
|
case 28:
|
||||||
|
nodeReader = nodeReader28{buffer: nodeBuffer}
|
||||||
|
case 32:
|
||||||
|
nodeReader = nodeReader32{buffer: nodeBuffer}
|
||||||
|
default:
|
||||||
|
return nil, newInvalidDatabaseError("unknown record size: %d", metadata.RecordSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := &Reader{
|
||||||
|
buffer: buffer,
|
||||||
|
nodeReader: nodeReader,
|
||||||
|
decoder: d,
|
||||||
|
Metadata: metadata,
|
||||||
|
ipv4Start: 0,
|
||||||
|
nodeOffsetMult: metadata.RecordSize / 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setIPv4Start()
|
||||||
|
|
||||||
|
return reader, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) setIPv4Start() {
|
||||||
|
if r.Metadata.IPVersion != 6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeCount := r.Metadata.NodeCount
|
||||||
|
|
||||||
|
node := uint(0)
|
||||||
|
i := 0
|
||||||
|
for ; i < 96 && node < nodeCount; i++ {
|
||||||
|
node = r.nodeReader.readLeft(node * r.nodeOffsetMult)
|
||||||
|
}
|
||||||
|
r.ipv4Start = node
|
||||||
|
r.ipv4StartBitDepth = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup retrieves the database record for ip and stores it in the value
|
||||||
|
// pointed to by result. If result is nil or not a pointer, an error is
|
||||||
|
// returned. If the data in the database record cannot be stored in result
|
||||||
|
// because of type differences, an UnmarshalTypeError is returned. If the
|
||||||
|
// database is invalid or otherwise cannot be read, an InvalidDatabaseError
|
||||||
|
// is returned.
|
||||||
|
func (r *Reader) Lookup(ip net.IP, result interface{}) error {
|
||||||
|
if r.buffer == nil {
|
||||||
|
return errors.New("cannot call Lookup on a closed database")
|
||||||
|
}
|
||||||
|
pointer, _, _, err := r.lookupPointer(ip)
|
||||||
|
if pointer == 0 || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.retrieveData(pointer, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupNetwork retrieves the database record for ip and stores it in the
|
||||||
|
// value pointed to by result. The network returned is the network associated
|
||||||
|
// with the data record in the database. The ok return value indicates whether
|
||||||
|
// the database contained a record for the ip.
|
||||||
|
//
|
||||||
|
// If result is nil or not a pointer, an error is returned. If the data in the
|
||||||
|
// database record cannot be stored in result because of type differences, an
|
||||||
|
// UnmarshalTypeError is returned. If the database is invalid or otherwise
|
||||||
|
// cannot be read, an InvalidDatabaseError is returned.
|
||||||
|
func (r *Reader) LookupNetwork(
|
||||||
|
ip net.IP,
|
||||||
|
result interface{},
|
||||||
|
) (network *net.IPNet, ok bool, err error) {
|
||||||
|
if r.buffer == nil {
|
||||||
|
return nil, false, errors.New("cannot call Lookup on a closed database")
|
||||||
|
}
|
||||||
|
pointer, prefixLength, ip, err := r.lookupPointer(ip)
|
||||||
|
|
||||||
|
network = r.cidr(ip, prefixLength)
|
||||||
|
if pointer == 0 || err != nil {
|
||||||
|
return network, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return network, true, 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(ip net.IP) (uintptr, error) {
|
||||||
|
if r.buffer == nil {
|
||||||
|
return 0, errors.New("cannot call LookupOffset on a closed database")
|
||||||
|
}
|
||||||
|
pointer, _, _, err := r.lookupPointer(ip)
|
||||||
|
if pointer == 0 || err != nil {
|
||||||
|
return NotFound, err
|
||||||
|
}
|
||||||
|
return r.resolveDataPointer(pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) cidr(ip net.IP, prefixLength int) *net.IPNet {
|
||||||
|
// This is necessary as the node that the IPv4 start is at may
|
||||||
|
// be at a bit depth that is less that 96, i.e., ipv4Start points
|
||||||
|
// to a leaf node. For instance, if a record was inserted at ::/8,
|
||||||
|
// the ipv4Start would point directly at the leaf node for the
|
||||||
|
// record and would have a bit depth of 8. This would not happen
|
||||||
|
// with databases currently distributed by MaxMind as all of them
|
||||||
|
// have an IPv4 subtree that is greater than a single node.
|
||||||
|
if r.Metadata.IPVersion == 6 &&
|
||||||
|
len(ip) == net.IPv4len &&
|
||||||
|
r.ipv4StartBitDepth != 96 {
|
||||||
|
return &net.IPNet{IP: net.ParseIP("::"), Mask: net.CIDRMask(r.ipv4StartBitDepth, 128)}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := net.CIDRMask(prefixLength, len(ip)*8)
|
||||||
|
return &net.IPNet{IP: ip.Mask(mask), Mask: mask}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dser, ok := result.(deserializer); ok {
|
||||||
|
_, err := r.decoder.decodeToDeserializer(uint(offset), dser, 0, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := r.decoder.decode(uint(offset), rv, 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) lookupPointer(ip net.IP) (uint, int, net.IP, error) {
|
||||||
|
if ip == nil {
|
||||||
|
return 0, 0, nil, errors.New("IP passed to Lookup cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ipV4Address := ip.To4()
|
||||||
|
if ipV4Address != nil {
|
||||||
|
ip = ipV4Address
|
||||||
|
}
|
||||||
|
if len(ip) == 16 && r.Metadata.IPVersion == 4 {
|
||||||
|
return 0, 0, ip, fmt.Errorf(
|
||||||
|
"error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database",
|
||||||
|
ip.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bitCount := uint(len(ip) * 8)
|
||||||
|
|
||||||
|
var node uint
|
||||||
|
if bitCount == 32 {
|
||||||
|
node = r.ipv4Start
|
||||||
|
}
|
||||||
|
node, prefixLength := r.traverseTree(ip, node, bitCount)
|
||||||
|
|
||||||
|
nodeCount := r.Metadata.NodeCount
|
||||||
|
if node == nodeCount {
|
||||||
|
// Record is empty
|
||||||
|
return 0, prefixLength, ip, nil
|
||||||
|
} else if node > nodeCount {
|
||||||
|
return node, prefixLength, ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, prefixLength, ip, newInvalidDatabaseError("invalid node in search tree")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) traverseTree(ip net.IP, node, bitCount uint) (uint, int) {
|
||||||
|
nodeCount := r.Metadata.NodeCount
|
||||||
|
|
||||||
|
i := uint(0)
|
||||||
|
for ; i < bitCount && node < nodeCount; i++ {
|
||||||
|
bit := uint(1) & (uint(ip[i>>3]) >> (7 - (i % 8)))
|
||||||
|
|
||||||
|
offset := node * r.nodeOffsetMult
|
||||||
|
if bit == 0 {
|
||||||
|
node = r.nodeReader.readLeft(offset)
|
||||||
|
} else {
|
||||||
|
node = r.nodeReader.readRight(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, int(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// +build appengine plan9
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
//go:build !appengine && !plan9
|
||||||
|
// +build !appengine,!plan9
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_ = mapFile.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := mapFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
_ = mapFile.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := int(stats.Size())
|
||||||
|
mmap, err := mmap(int(mapFile.Fd()), fileSize)
|
||||||
|
if err != nil {
|
||||||
|
_ = mapFile.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mapFile.Close(); err != nil {
|
||||||
|
//nolint:errcheck // we prefer to return the original error
|
||||||
|
munmap(mmap)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := FromBytes(mmap)
|
||||||
|
if err != nil {
|
||||||
|
//nolint:errcheck // we prefer to return the original error
|
||||||
|
munmap(mmap)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.hasMappedFile = true
|
||||||
|
runtime.SetFinalizer(reader, (*Reader).Close)
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -0,0 +1,205 @@
|
|||||||
|
package maxminddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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
|
||||||
|
|
||||||
|
skipAliasedNetworks bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allIPv4 = &net.IPNet{IP: make(net.IP, 4), Mask: net.CIDRMask(0, 32)}
|
||||||
|
allIPv6 = &net.IPNet{IP: make(net.IP, 16), Mask: net.CIDRMask(0, 128)}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworksOption are options for Networks and NetworksWithin.
|
||||||
|
type NetworksOption func(*Networks)
|
||||||
|
|
||||||
|
// SkipAliasedNetworks is an option for Networks and NetworksWithin that
|
||||||
|
// makes them not iterate over aliases of the IPv4 subtree in an IPv6
|
||||||
|
// database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16.
|
||||||
|
//
|
||||||
|
// You most likely want to set this. The only reason it isn't the default
|
||||||
|
// behavior is to provide backwards compatibility to existing users.
|
||||||
|
func SkipAliasedNetworks(networks *Networks) {
|
||||||
|
networks.skipAliasedNetworks = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 an IPv6 database. This iterator will iterate over all of these locations
|
||||||
|
// separately. To only iterate over the IPv4 networks once, use the
|
||||||
|
// SkipAliasedNetworks option.
|
||||||
|
func (r *Reader) Networks(options ...NetworksOption) *Networks {
|
||||||
|
var networks *Networks
|
||||||
|
if r.Metadata.IPVersion == 6 {
|
||||||
|
networks = r.NetworksWithin(allIPv6, options...)
|
||||||
|
} else {
|
||||||
|
networks = r.NetworksWithin(allIPv4, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworksWithin returns an iterator that can be used to traverse all networks
|
||||||
|
// in the database which are contained in a given network.
|
||||||
|
//
|
||||||
|
// Please note that a MaxMind DB may map IPv4 networks into several locations
|
||||||
|
// in an IPv6 database. This iterator will iterate over all of these locations
|
||||||
|
// separately. To only iterate over the IPv4 networks once, use the
|
||||||
|
// SkipAliasedNetworks option.
|
||||||
|
//
|
||||||
|
// If the provided network is contained within a network in the database, the
|
||||||
|
// iterator will iterate over exactly one network, the containing network.
|
||||||
|
func (r *Reader) NetworksWithin(network *net.IPNet, options ...NetworksOption) *Networks {
|
||||||
|
if r.Metadata.IPVersion == 4 && network.IP.To4() == nil {
|
||||||
|
return &Networks{
|
||||||
|
err: fmt.Errorf(
|
||||||
|
"error getting networks with '%s': you attempted to use an IPv6 network in an IPv4-only database",
|
||||||
|
network.String(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
networks := &Networks{reader: r}
|
||||||
|
for _, option := range options {
|
||||||
|
option(networks)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := network.IP
|
||||||
|
prefixLength, _ := network.Mask.Size()
|
||||||
|
|
||||||
|
if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len {
|
||||||
|
if networks.skipAliasedNetworks {
|
||||||
|
ip = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ip[0], ip[1], ip[2], ip[3]}
|
||||||
|
} else {
|
||||||
|
ip = ip.To16()
|
||||||
|
}
|
||||||
|
prefixLength += 96
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer, bit := r.traverseTree(ip, 0, uint(prefixLength))
|
||||||
|
networks.nodes = []netNode{
|
||||||
|
{
|
||||||
|
ip: ip,
|
||||||
|
bit: uint(bit),
|
||||||
|
pointer: pointer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if n.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for len(n.nodes) > 0 {
|
||||||
|
node := n.nodes[len(n.nodes)-1]
|
||||||
|
n.nodes = n.nodes[:len(n.nodes)-1]
|
||||||
|
|
||||||
|
for node.pointer != n.reader.Metadata.NodeCount {
|
||||||
|
// This skips IPv4 aliases without hardcoding the networks that the writer
|
||||||
|
// currently aliases.
|
||||||
|
if n.skipAliasedNetworks && n.reader.ipv4Start != 0 &&
|
||||||
|
node.pointer == n.reader.ipv4Start && !isInIPv4Subtree(node.ip) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.pointer > n.reader.Metadata.NodeCount {
|
||||||
|
n.lastNode = node
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
|
||||||
|
offset := node.pointer * n.reader.nodeOffsetMult
|
||||||
|
rightPointer := n.reader.nodeReader.readRight(offset)
|
||||||
|
|
||||||
|
node.bit++
|
||||||
|
n.nodes = append(n.nodes, netNode{
|
||||||
|
pointer: rightPointer,
|
||||||
|
ip: ipRight,
|
||||||
|
bit: node.bit,
|
||||||
|
})
|
||||||
|
|
||||||
|
node.pointer = n.reader.nodeReader.readLeft(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 n.err != nil {
|
||||||
|
return nil, n.err
|
||||||
|
}
|
||||||
|
if err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := n.lastNode.ip
|
||||||
|
prefixLength := int(n.lastNode.bit)
|
||||||
|
|
||||||
|
// We do this because uses of SkipAliasedNetworks expect the IPv4 networks
|
||||||
|
// to be returned as IPv4 networks. If we are not skipping aliased
|
||||||
|
// networks, then the user will get IPv4 networks from the ::FFFF:0:0/96
|
||||||
|
// network as Go automatically converts those.
|
||||||
|
if n.skipAliasedNetworks && isInIPv4Subtree(ip) {
|
||||||
|
ip = ip[12:]
|
||||||
|
prefixLength -= 96
|
||||||
|
}
|
||||||
|
|
||||||
|
return &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(prefixLength, len(ip)*8),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns an error, if any, that was encountered during iteration.
|
||||||
|
func (n *Networks) Err() error {
|
||||||
|
return n.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInIPv4Subtree returns true if the IP is an IPv6 address in the database's
|
||||||
|
// IPv4 subtree.
|
||||||
|
func isInIPv4Subtree(ip net.IP) bool {
|
||||||
|
if len(ip) != 16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
if ip[i] != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
package maxminddb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.verifyDatabase()
|
||||||
|
runtime.KeepAlive(v.reader)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return newInvalidDatabaseError(
|
||||||
|
"found data (%v) at %v that the search tree does not point to",
|
||||||
|
data,
|
||||||
|
pointer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
delete(offsets, 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,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package unsafeheader contains header declarations for the Go runtime's
|
||||||
|
// slice and string implementations.
|
||||||
|
//
|
||||||
|
// This package allows x/sys to use types equivalent to
|
||||||
|
// reflect.SliceHeader and reflect.StringHeader without introducing
|
||||||
|
// a dependency on the (relatively heavy) "reflect" package.
|
||||||
|
package unsafeheader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Slice is the runtime representation of a slice.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type Slice struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
Cap int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the runtime representation of a string.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type String struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build darwin && go1.12 && !go1.13
|
|
||||||
// +build darwin,go1.12,!go1.13
|
|
||||||
|
|
||||||
package unix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const _SYS_GETDIRENTRIES64 = 344
|
|
||||||
|
|
||||||
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
|
||||||
// To implement this using libSystem we'd need syscall_syscallPtr for
|
|
||||||
// fdopendir. However, syscallPtr was only added in Go 1.13, so we fall
|
|
||||||
// back to raw syscalls for this func on Go 1.12.
|
|
||||||
var p unsafe.Pointer
|
|
||||||
if len(buf) > 0 {
|
|
||||||
p = unsafe.Pointer(&buf[0])
|
|
||||||
} else {
|
|
||||||
p = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(_SYS_GETDIRENTRIES64, uintptr(fd), uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
return n, errnoErr(e1)
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
// Copyright 2019 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build darwin && go1.13
|
|
||||||
// +build darwin,go1.13
|
|
||||||
|
|
||||||
package unix
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
//sys closedir(dir uintptr) (err error)
|
|
||||||
//sys readdir_r(dir uintptr, entry *Dirent, result **Dirent) (res Errno)
|
|
||||||
|
|
||||||
func fdopendir(fd int) (dir uintptr, err error) {
|
|
||||||
r0, _, e1 := syscall_syscallPtr(libc_fdopendir_trampoline_addr, uintptr(fd), 0, 0)
|
|
||||||
dir = uintptr(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_fdopendir_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_fdopendir fdopendir "/usr/lib/libSystem.B.dylib"
|
|
||||||
|
|
||||||
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
|
||||||
// Simulate Getdirentries using fdopendir/readdir_r/closedir.
|
|
||||||
// We store the number of entries to skip in the seek
|
|
||||||
// offset of fd. See issue #31368.
|
|
||||||
// It's not the full required semantics, but should handle the case
|
|
||||||
// of calling Getdirentries or ReadDirent repeatedly.
|
|
||||||
// It won't handle assigning the results of lseek to *basep, or handle
|
|
||||||
// the directory being edited underfoot.
|
|
||||||
skip, err := Seek(fd, 0, 1 /* SEEK_CUR */)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to duplicate the incoming file descriptor
|
|
||||||
// because the caller expects to retain control of it, but
|
|
||||||
// fdopendir expects to take control of its argument.
|
|
||||||
// Just Dup'ing the file descriptor is not enough, as the
|
|
||||||
// result shares underlying state. Use Openat to make a really
|
|
||||||
// new file descriptor referring to the same directory.
|
|
||||||
fd2, err := Openat(fd, ".", O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
d, err := fdopendir(fd2)
|
|
||||||
if err != nil {
|
|
||||||
Close(fd2)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer closedir(d)
|
|
||||||
|
|
||||||
var cnt int64
|
|
||||||
for {
|
|
||||||
var entry Dirent
|
|
||||||
var entryp *Dirent
|
|
||||||
e := readdir_r(d, &entry, &entryp)
|
|
||||||
if e != 0 {
|
|
||||||
return n, errnoErr(e)
|
|
||||||
}
|
|
||||||
if entryp == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if skip > 0 {
|
|
||||||
skip--
|
|
||||||
cnt++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
reclen := int(entry.Reclen)
|
|
||||||
if reclen > len(buf) {
|
|
||||||
// Not enough room. Return for now.
|
|
||||||
// The counter will let us know where we should start up again.
|
|
||||||
// Note: this strategy for suspending in the middle and
|
|
||||||
// restarting is O(n^2) in the length of the directory. Oh well.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy entry into return buffer.
|
|
||||||
s := unsafe.Slice((*byte)(unsafe.Pointer(&entry)), reclen)
|
|
||||||
copy(buf, s)
|
|
||||||
|
|
||||||
buf = buf[reclen:]
|
|
||||||
n += reclen
|
|
||||||
cnt++
|
|
||||||
}
|
|
||||||
// Set the seek offset of the input fd to record
|
|
||||||
// how many files we've already returned.
|
|
||||||
_, err = Seek(fd, cnt, 0 /* SEEK_SET */)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
// go run mksyscall.go -tags darwin,amd64,go1.13 syscall_darwin.1_13.go
|
|
||||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:build darwin && amd64 && go1.13
|
|
||||||
// +build darwin,amd64,go1.13
|
|
||||||
|
|
||||||
package unix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ syscall.Errno
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func closedir(dir uintptr) (err error) {
|
|
||||||
_, _, e1 := syscall_syscall(libc_closedir_trampoline_addr, uintptr(dir), 0, 0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_closedir_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_closedir closedir "/usr/lib/libSystem.B.dylib"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (res Errno) {
|
|
||||||
r0, _, _ := syscall_syscall(libc_readdir_r_trampoline_addr, uintptr(dir), uintptr(unsafe.Pointer(entry)), uintptr(unsafe.Pointer(result)))
|
|
||||||
res = Errno(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_readdir_r_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_readdir_r readdir_r "/usr/lib/libSystem.B.dylib"
|
|
@ -1,25 +0,0 @@
|
|||||||
// go run mkasm.go darwin amd64
|
|
||||||
// Code generated by the command above; DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:build go1.13
|
|
||||||
// +build go1.13
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
TEXT libc_fdopendir_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_fdopendir(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_fdopendir_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_fdopendir_trampoline_addr(SB)/8, $libc_fdopendir_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_closedir_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_closedir(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_closedir_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_closedir_trampoline_addr(SB)/8, $libc_closedir_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_readdir_r_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_readdir_r(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_readdir_r_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_readdir_r_trampoline_addr(SB)/8, $libc_readdir_r_trampoline<>(SB)
|
|
@ -1,40 +0,0 @@
|
|||||||
// go run mksyscall.go -tags darwin,arm64,go1.13 syscall_darwin.1_13.go
|
|
||||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:build darwin && arm64 && go1.13
|
|
||||||
// +build darwin,arm64,go1.13
|
|
||||||
|
|
||||||
package unix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ syscall.Errno
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func closedir(dir uintptr) (err error) {
|
|
||||||
_, _, e1 := syscall_syscall(libc_closedir_trampoline_addr, uintptr(dir), 0, 0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_closedir_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_closedir closedir "/usr/lib/libSystem.B.dylib"
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (res Errno) {
|
|
||||||
r0, _, _ := syscall_syscall(libc_readdir_r_trampoline_addr, uintptr(dir), uintptr(unsafe.Pointer(entry)), uintptr(unsafe.Pointer(result)))
|
|
||||||
res = Errno(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_readdir_r_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_readdir_r readdir_r "/usr/lib/libSystem.B.dylib"
|
|
@ -1,25 +0,0 @@
|
|||||||
// go run mkasm.go darwin arm64
|
|
||||||
// Code generated by the command above; DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:build go1.13
|
|
||||||
// +build go1.13
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
TEXT libc_fdopendir_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_fdopendir(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_fdopendir_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_fdopendir_trampoline_addr(SB)/8, $libc_fdopendir_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_closedir_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_closedir(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_closedir_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_closedir_trampoline_addr(SB)/8, $libc_closedir_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_readdir_r_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_readdir_r(SB)
|
|
||||||
|
|
||||||
GLOBL ·libc_readdir_r_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_readdir_r_trampoline_addr(SB)/8, $libc_readdir_r_trampoline<>(SB)
|
|
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows && go1.9
|
||||||
|
// +build windows,go1.9
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
type Errno = syscall.Errno
|
||||||
|
type SysProcAttr = syscall.SysProcAttr
|
@ -0,0 +1,416 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We need to use LoadLibrary and GetProcAddress from the Go runtime, because
|
||||||
|
// the these symbols are loaded by the system linker and are required to
|
||||||
|
// dynamically load additional symbols. Note that in the Go runtime, these
|
||||||
|
// return syscall.Handle and syscall.Errno, but these are the same, in fact,
|
||||||
|
// as windows.Handle and windows.Errno, and we intend to keep these the same.
|
||||||
|
|
||||||
|
//go:linkname syscall_loadlibrary syscall.loadlibrary
|
||||||
|
func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno)
|
||||||
|
|
||||||
|
//go:linkname syscall_getprocaddress syscall.getprocaddress
|
||||||
|
func syscall_getprocaddress(handle Handle, procname *uint8) (proc uintptr, err Errno)
|
||||||
|
|
||||||
|
// DLLError describes reasons for DLL load failures.
|
||||||
|
type DLLError struct {
|
||||||
|
Err error
|
||||||
|
ObjName string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DLLError) Error() string { return e.Msg }
|
||||||
|
|
||||||
|
func (e *DLLError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// A DLL implements access to a single DLL.
|
||||||
|
type DLL struct {
|
||||||
|
Name string
|
||||||
|
Handle Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDLL loads DLL file into memory.
|
||||||
|
//
|
||||||
|
// Warning: using LoadDLL without an absolute path name is subject to
|
||||||
|
// DLL preloading attacks. To safely load a system DLL, use LazyDLL
|
||||||
|
// with System set to true, or use LoadLibraryEx directly.
|
||||||
|
func LoadDLL(name string) (dll *DLL, err error) {
|
||||||
|
namep, err := UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, e := syscall_loadlibrary(namep)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, &DLLError{
|
||||||
|
Err: e,
|
||||||
|
ObjName: name,
|
||||||
|
Msg: "Failed to load " + name + ": " + e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d := &DLL{
|
||||||
|
Name: name,
|
||||||
|
Handle: h,
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadDLL is like LoadDLL but panics if load operation failes.
|
||||||
|
func MustLoadDLL(name string) *DLL {
|
||||||
|
d, e := LoadDLL(name)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindProc searches DLL d for procedure named name and returns *Proc
|
||||||
|
// if found. It returns an error if search fails.
|
||||||
|
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
|
||||||
|
namep, err := BytePtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a, e := syscall_getprocaddress(d.Handle, namep)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, &DLLError{
|
||||||
|
Err: e,
|
||||||
|
ObjName: name,
|
||||||
|
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := &Proc{
|
||||||
|
Dll: d,
|
||||||
|
Name: name,
|
||||||
|
addr: a,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFindProc is like FindProc but panics if search fails.
|
||||||
|
func (d *DLL) MustFindProc(name string) *Proc {
|
||||||
|
p, e := d.FindProc(name)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc
|
||||||
|
// if found. It returns an error if search fails.
|
||||||
|
func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) {
|
||||||
|
a, e := GetProcAddressByOrdinal(d.Handle, ordinal)
|
||||||
|
name := "#" + itoa(int(ordinal))
|
||||||
|
if e != nil {
|
||||||
|
return nil, &DLLError{
|
||||||
|
Err: e,
|
||||||
|
ObjName: name,
|
||||||
|
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := &Proc{
|
||||||
|
Dll: d,
|
||||||
|
Name: name,
|
||||||
|
addr: a,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails.
|
||||||
|
func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc {
|
||||||
|
p, e := d.FindProcByOrdinal(ordinal)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release unloads DLL d from memory.
|
||||||
|
func (d *DLL) Release() (err error) {
|
||||||
|
return FreeLibrary(d.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Proc implements access to a procedure inside a DLL.
|
||||||
|
type Proc struct {
|
||||||
|
Dll *DLL
|
||||||
|
Name string
|
||||||
|
addr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the address of the procedure represented by p.
|
||||||
|
// The return value can be passed to Syscall to run the procedure.
|
||||||
|
func (p *Proc) Addr() uintptr {
|
||||||
|
return p.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:uintptrescapes
|
||||||
|
|
||||||
|
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||||
|
// are supplied.
|
||||||
|
//
|
||||||
|
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||||
|
// Callers must inspect the primary return value to decide whether an error occurred
|
||||||
|
// (according to the semantics of the specific function being called) before consulting
|
||||||
|
// the error. The error will be guaranteed to contain windows.Errno.
|
||||||
|
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||||
|
switch len(a) {
|
||||||
|
case 0:
|
||||||
|
return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
|
||||||
|
case 1:
|
||||||
|
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
|
||||||
|
case 2:
|
||||||
|
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
|
||||||
|
case 3:
|
||||||
|
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
|
||||||
|
case 4:
|
||||||
|
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
|
||||||
|
case 5:
|
||||||
|
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
|
||||||
|
case 6:
|
||||||
|
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
|
||||||
|
case 7:
|
||||||
|
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
|
||||||
|
case 8:
|
||||||
|
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
|
||||||
|
case 9:
|
||||||
|
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
|
||||||
|
case 10:
|
||||||
|
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
|
||||||
|
case 11:
|
||||||
|
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
|
||||||
|
case 12:
|
||||||
|
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
|
||||||
|
case 13:
|
||||||
|
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
|
||||||
|
case 14:
|
||||||
|
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
|
||||||
|
case 15:
|
||||||
|
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
|
||||||
|
default:
|
||||||
|
panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A LazyDLL implements access to a single DLL.
|
||||||
|
// It will delay the load of the DLL until the first
|
||||||
|
// call to its Handle method or to one of its
|
||||||
|
// LazyProc's Addr method.
|
||||||
|
type LazyDLL struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// System determines whether the DLL must be loaded from the
|
||||||
|
// Windows System directory, bypassing the normal DLL search
|
||||||
|
// path.
|
||||||
|
System bool
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
dll *DLL // non nil once DLL is loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads DLL file d.Name into memory. It returns an error if fails.
|
||||||
|
// Load will not try to load DLL, if it is already loaded into memory.
|
||||||
|
func (d *LazyDLL) Load() error {
|
||||||
|
// Non-racy version of:
|
||||||
|
// if d.dll != nil {
|
||||||
|
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
if d.dll != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// kernel32.dll is special, since it's where LoadLibraryEx comes from.
|
||||||
|
// The kernel already special-cases its name, so it's always
|
||||||
|
// loaded from system32.
|
||||||
|
var dll *DLL
|
||||||
|
var err error
|
||||||
|
if d.Name == "kernel32.dll" {
|
||||||
|
dll, err = LoadDLL(d.Name)
|
||||||
|
} else {
|
||||||
|
dll, err = loadLibraryEx(d.Name, d.System)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-racy version of:
|
||||||
|
// d.dll = dll
|
||||||
|
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustLoad is like Load but panics if search fails.
|
||||||
|
func (d *LazyDLL) mustLoad() {
|
||||||
|
e := d.Load()
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle returns d's module handle.
|
||||||
|
func (d *LazyDLL) Handle() uintptr {
|
||||||
|
d.mustLoad()
|
||||||
|
return uintptr(d.dll.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProc returns a LazyProc for accessing the named procedure in the DLL d.
|
||||||
|
func (d *LazyDLL) NewProc(name string) *LazyProc {
|
||||||
|
return &LazyProc{l: d, Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLazyDLL creates new LazyDLL associated with DLL file.
|
||||||
|
func NewLazyDLL(name string) *LazyDLL {
|
||||||
|
return &LazyDLL{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLazySystemDLL is like NewLazyDLL, but will only
|
||||||
|
// search Windows System directory for the DLL if name is
|
||||||
|
// a base name (like "advapi32.dll").
|
||||||
|
func NewLazySystemDLL(name string) *LazyDLL {
|
||||||
|
return &LazyDLL{Name: name, System: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A LazyProc implements access to a procedure inside a LazyDLL.
|
||||||
|
// It delays the lookup until the Addr method is called.
|
||||||
|
type LazyProc struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
l *LazyDLL
|
||||||
|
proc *Proc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find searches DLL for procedure named p.Name. It returns
|
||||||
|
// an error if search fails. Find will not search procedure,
|
||||||
|
// if it is already found and loaded into memory.
|
||||||
|
func (p *LazyProc) Find() error {
|
||||||
|
// Non-racy version of:
|
||||||
|
// if p.proc == nil {
|
||||||
|
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.proc == nil {
|
||||||
|
e := p.l.Load()
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
proc, e := p.l.dll.FindProc(p.Name)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
// Non-racy version of:
|
||||||
|
// p.proc = proc
|
||||||
|
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustFind is like Find but panics if search fails.
|
||||||
|
func (p *LazyProc) mustFind() {
|
||||||
|
e := p.Find()
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the address of the procedure represented by p.
|
||||||
|
// The return value can be passed to Syscall to run the procedure.
|
||||||
|
// It will panic if the procedure cannot be found.
|
||||||
|
func (p *LazyProc) Addr() uintptr {
|
||||||
|
p.mustFind()
|
||||||
|
return p.proc.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:uintptrescapes
|
||||||
|
|
||||||
|
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||||
|
// are supplied. It will also panic if the procedure cannot be found.
|
||||||
|
//
|
||||||
|
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||||
|
// Callers must inspect the primary return value to decide whether an error occurred
|
||||||
|
// (according to the semantics of the specific function being called) before consulting
|
||||||
|
// the error. The error will be guaranteed to contain windows.Errno.
|
||||||
|
func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||||
|
p.mustFind()
|
||||||
|
return p.proc.Call(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var canDoSearchSystem32Once struct {
|
||||||
|
sync.Once
|
||||||
|
v bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func initCanDoSearchSystem32() {
|
||||||
|
// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
|
||||||
|
// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
|
||||||
|
// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
|
||||||
|
// systems that have KB2533623 installed. To determine whether the
|
||||||
|
// flags are available, use GetProcAddress to get the address of the
|
||||||
|
// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
|
||||||
|
// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
|
||||||
|
// flags can be used with LoadLibraryEx."
|
||||||
|
canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func canDoSearchSystem32() bool {
|
||||||
|
canDoSearchSystem32Once.Do(initCanDoSearchSystem32)
|
||||||
|
return canDoSearchSystem32Once.v
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBaseName(name string) bool {
|
||||||
|
for _, c := range name {
|
||||||
|
if c == ':' || c == '/' || c == '\\' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadLibraryEx wraps the Windows LoadLibraryEx function.
|
||||||
|
//
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
|
||||||
|
//
|
||||||
|
// If name is not an absolute path, LoadLibraryEx searches for the DLL
|
||||||
|
// in a variety of automatic locations unless constrained by flags.
|
||||||
|
// See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx
|
||||||
|
func loadLibraryEx(name string, system bool) (*DLL, error) {
|
||||||
|
loadDLL := name
|
||||||
|
var flags uintptr
|
||||||
|
if system {
|
||||||
|
if canDoSearchSystem32() {
|
||||||
|
flags = LOAD_LIBRARY_SEARCH_SYSTEM32
|
||||||
|
} else if isBaseName(name) {
|
||||||
|
// WindowsXP or unpatched Windows machine
|
||||||
|
// trying to load "foo.dll" out of the system
|
||||||
|
// folder, but LoadLibraryEx doesn't support
|
||||||
|
// that yet on their system, so emulate it.
|
||||||
|
systemdir, err := GetSystemDirectory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
loadDLL = systemdir + "\\" + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := LoadLibraryEx(loadDLL, 0, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &DLL{Name: name, Handle: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type errString string
|
||||||
|
|
||||||
|
func (s errString) Error() string { return string(s) }
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.12
|
||||||
|
// +build !go1.12
|
||||||
|
|
||||||
|
// This file is here to allow bodyless functions with go:linkname for Go 1.11
|
||||||
|
// and earlier (see https://golang.org/issue/23311).
|
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Windows environment variables.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Getenv(key string) (value string, found bool) {
|
||||||
|
return syscall.Getenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setenv(key, value string) error {
|
||||||
|
return syscall.Setenv(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clearenv() {
|
||||||
|
syscall.Clearenv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Environ() []string {
|
||||||
|
return syscall.Environ()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a default environment associated with the token, rather than the current
|
||||||
|
// process. If inheritExisting is true, then this environment also inherits the
|
||||||
|
// environment of the current process.
|
||||||
|
func (token Token) Environ(inheritExisting bool) (env []string, err error) {
|
||||||
|
var block *uint16
|
||||||
|
err = CreateEnvironmentBlock(&block, token, inheritExisting)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer DestroyEnvironmentBlock(block)
|
||||||
|
blockp := uintptr(unsafe.Pointer(block))
|
||||||
|
for {
|
||||||
|
entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp)))
|
||||||
|
if len(entry) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
env = append(env, entry)
|
||||||
|
blockp += 2 * (uintptr(len(entry)) + 1)
|
||||||
|
}
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unsetenv(key string) error {
|
||||||
|
return syscall.Unsetenv(key)
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
const (
|
||||||
|
EVENTLOG_SUCCESS = 0
|
||||||
|
EVENTLOG_ERROR_TYPE = 1
|
||||||
|
EVENTLOG_WARNING_TYPE = 2
|
||||||
|
EVENTLOG_INFORMATION_TYPE = 4
|
||||||
|
EVENTLOG_AUDIT_SUCCESS = 8
|
||||||
|
EVENTLOG_AUDIT_FAILURE = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
|
||||||
|
//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource
|
||||||
|
//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW
|
@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Fork, exec, wait, etc.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
errorspkg "errors"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeArg rewrites command line argument s as prescribed
|
||||||
|
// in http://msdn.microsoft.com/en-us/library/ms880421.
|
||||||
|
// This function returns "" (2 double quotes) if s is empty.
|
||||||
|
// Alternatively, these transformations are done:
|
||||||
|
// - every back slash (\) is doubled, but only if immediately
|
||||||
|
// followed by double quote (");
|
||||||
|
// - every double quote (") is escaped by back slash (\);
|
||||||
|
// - finally, s is wrapped with double quotes (arg -> "arg"),
|
||||||
|
// but only if there is space or tab inside s.
|
||||||
|
func EscapeArg(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "\"\""
|
||||||
|
}
|
||||||
|
n := len(s)
|
||||||
|
hasSpace := false
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '"', '\\':
|
||||||
|
n++
|
||||||
|
case ' ', '\t':
|
||||||
|
hasSpace = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasSpace {
|
||||||
|
n += 2
|
||||||
|
}
|
||||||
|
if n == len(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
qs := make([]byte, n)
|
||||||
|
j := 0
|
||||||
|
if hasSpace {
|
||||||
|
qs[j] = '"'
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
slashes := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
default:
|
||||||
|
slashes = 0
|
||||||
|
qs[j] = s[i]
|
||||||
|
case '\\':
|
||||||
|
slashes++
|
||||||
|
qs[j] = s[i]
|
||||||
|
case '"':
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
qs[j] = '\\'
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
qs[j] = '\\'
|
||||||
|
j++
|
||||||
|
qs[j] = s[i]
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if hasSpace {
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
qs[j] = '\\'
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
qs[j] = '"'
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return string(qs[:j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
|
||||||
|
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
|
||||||
|
// or any program that uses CommandLineToArgv.
|
||||||
|
func ComposeCommandLine(args []string) string {
|
||||||
|
var commandLine string
|
||||||
|
for i := range args {
|
||||||
|
if i > 0 {
|
||||||
|
commandLine += " "
|
||||||
|
}
|
||||||
|
commandLine += EscapeArg(args[i])
|
||||||
|
}
|
||||||
|
return commandLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
||||||
|
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
||||||
|
// command lines are passed around.
|
||||||
|
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||||
|
if len(commandLine) == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
var argc int32
|
||||||
|
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer LocalFree(Handle(unsafe.Pointer(argv)))
|
||||||
|
var args []string
|
||||||
|
for _, v := range (*argv)[:argc] {
|
||||||
|
args = append(args, UTF16ToString((*v)[:]))
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseOnExec(fd Handle) {
|
||||||
|
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullPath retrieves the full path of the specified file.
|
||||||
|
func FullPath(name string) (path string, err error) {
|
||||||
|
p, err := UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
n := uint32(100)
|
||||||
|
for {
|
||||||
|
buf := make([]uint16, n)
|
||||||
|
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n <= uint32(len(buf)) {
|
||||||
|
return UTF16ToString(buf[:n]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
|
||||||
|
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
|
||||||
|
var size uintptr
|
||||||
|
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
|
||||||
|
if err != ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
if err == nil {
|
||||||
|
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
|
||||||
|
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
|
||||||
|
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return al, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
|
||||||
|
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
|
||||||
|
al.pointers = append(al.pointers, value)
|
||||||
|
return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete frees ProcThreadAttributeList's resources.
|
||||||
|
func (al *ProcThreadAttributeListContainer) Delete() {
|
||||||
|
deleteProcThreadAttributeList(al.data)
|
||||||
|
LocalFree(Handle(unsafe.Pointer(al.data)))
|
||||||
|
al.data = nil
|
||||||
|
al.pointers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
|
||||||
|
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
|
||||||
|
return al.data
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
const (
|
||||||
|
MEM_COMMIT = 0x00001000
|
||||||
|
MEM_RESERVE = 0x00002000
|
||||||
|
MEM_DECOMMIT = 0x00004000
|
||||||
|
MEM_RELEASE = 0x00008000
|
||||||
|
MEM_RESET = 0x00080000
|
||||||
|
MEM_TOP_DOWN = 0x00100000
|
||||||
|
MEM_WRITE_WATCH = 0x00200000
|
||||||
|
MEM_PHYSICAL = 0x00400000
|
||||||
|
MEM_RESET_UNDO = 0x01000000
|
||||||
|
MEM_LARGE_PAGES = 0x20000000
|
||||||
|
|
||||||
|
PAGE_NOACCESS = 0x00000001
|
||||||
|
PAGE_READONLY = 0x00000002
|
||||||
|
PAGE_READWRITE = 0x00000004
|
||||||
|
PAGE_WRITECOPY = 0x00000008
|
||||||
|
PAGE_EXECUTE = 0x00000010
|
||||||
|
PAGE_EXECUTE_READ = 0x00000020
|
||||||
|
PAGE_EXECUTE_READWRITE = 0x00000040
|
||||||
|
PAGE_EXECUTE_WRITECOPY = 0x00000080
|
||||||
|
PAGE_GUARD = 0x00000100
|
||||||
|
PAGE_NOCACHE = 0x00000200
|
||||||
|
PAGE_WRITECOMBINE = 0x00000400
|
||||||
|
PAGE_TARGETS_INVALID = 0x40000000
|
||||||
|
PAGE_TARGETS_NO_UPDATE = 0x40000000
|
||||||
|
|
||||||
|
QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x00000002
|
||||||
|
QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x00000001
|
||||||
|
QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008
|
||||||
|
QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemoryBasicInformation struct {
|
||||||
|
BaseAddress uintptr
|
||||||
|
AllocationBase uintptr
|
||||||
|
AllocationProtect uint32
|
||||||
|
PartitionId uint16
|
||||||
|
RegionSize uintptr
|
||||||
|
State uint32
|
||||||
|
Protect uint32
|
||||||
|
Type uint32
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
winerror="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/winerror.h | sort -Vr | head -n 1)"
|
||||||
|
[[ -n $winerror ]] || { echo "Unable to find winerror.h" >&2; exit 1; }
|
||||||
|
ntstatus="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/ntstatus.h | sort -Vr | head -n 1)"
|
||||||
|
[[ -n $ntstatus ]] || { echo "Unable to find ntstatus.h" >&2; exit 1; }
|
||||||
|
|
||||||
|
declare -A errors
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "// Code generated by 'mkerrors.bash'; DO NOT EDIT."
|
||||||
|
echo
|
||||||
|
echo "package windows"
|
||||||
|
echo "import \"syscall\""
|
||||||
|
echo "const ("
|
||||||
|
|
||||||
|
while read -r line; do
|
||||||
|
unset vtype
|
||||||
|
if [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?([A-Z][A-Z0-9_]+k?)\)? ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
value="${BASH_REMATCH[3]}"
|
||||||
|
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?((0x)?[0-9A-Fa-f]+)L?\)? ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
value="${BASH_REMATCH[3]}"
|
||||||
|
vtype="${BASH_REMATCH[2]}"
|
||||||
|
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +\(\(([A-Z]+)\)((0x)?[0-9A-Fa-f]+)L?\) ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
value="${BASH_REMATCH[3]}"
|
||||||
|
vtype="${BASH_REMATCH[2]}"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ -n $key && -n $value ]] || continue
|
||||||
|
[[ -z ${errors["$key"]} ]] || continue
|
||||||
|
errors["$key"]="$value"
|
||||||
|
if [[ -v vtype ]]; then
|
||||||
|
if [[ $key == FACILITY_* || $key == NO_ERROR ]]; then
|
||||||
|
vtype=""
|
||||||
|
elif [[ $vtype == *HANDLE* || $vtype == *HRESULT* ]]; then
|
||||||
|
vtype="Handle"
|
||||||
|
else
|
||||||
|
vtype="syscall.Errno"
|
||||||
|
fi
|
||||||
|
last_vtype="$vtype"
|
||||||
|
else
|
||||||
|
vtype=""
|
||||||
|
if [[ $last_vtype == Handle && $value == NO_ERROR ]]; then
|
||||||
|
value="S_OK"
|
||||||
|
elif [[ $last_vtype == syscall.Errno && $value == NO_ERROR ]]; then
|
||||||
|
value="ERROR_SUCCESS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$key $vtype = $value"
|
||||||
|
done < "$winerror"
|
||||||
|
|
||||||
|
while read -r line; do
|
||||||
|
[[ $line =~ ^#define\ (STATUS_[^\s]+)\ +\(\(NTSTATUS\)((0x)?[0-9a-fA-F]+)L?\) ]] || continue
|
||||||
|
echo "${BASH_REMATCH[1]} NTStatus = ${BASH_REMATCH[2]}"
|
||||||
|
done < "$ntstatus"
|
||||||
|
|
||||||
|
echo ")"
|
||||||
|
} | gofmt > "zerrors_windows.go"
|
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
knownfolders="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/um/KnownFolders.h | sort -Vr | head -n 1)"
|
||||||
|
[[ -n $knownfolders ]] || { echo "Unable to find KnownFolders.h" >&2; exit 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT."
|
||||||
|
echo
|
||||||
|
echo "package windows"
|
||||||
|
echo "type KNOWNFOLDERID GUID"
|
||||||
|
echo "var ("
|
||||||
|
while read -r line; do
|
||||||
|
[[ $line =~ DEFINE_KNOWN_FOLDER\((FOLDERID_[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+)\) ]] || continue
|
||||||
|
printf "%s = &KNOWNFOLDERID{0x%08x, 0x%04x, 0x%04x, [8]byte{0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x}}\n" \
|
||||||
|
"${BASH_REMATCH[1]}" $(( "${BASH_REMATCH[2]}" )) $(( "${BASH_REMATCH[3]}" )) $(( "${BASH_REMATCH[4]}" )) \
|
||||||
|
$(( "${BASH_REMATCH[5]}" )) $(( "${BASH_REMATCH[6]}" )) $(( "${BASH_REMATCH[7]}" )) $(( "${BASH_REMATCH[8]}" )) \
|
||||||
|
$(( "${BASH_REMATCH[9]}" )) $(( "${BASH_REMATCH[10]}" )) $(( "${BASH_REMATCH[11]}" )) $(( "${BASH_REMATCH[12]}" ))
|
||||||
|
done < "$knownfolders"
|
||||||
|
echo ")"
|
||||||
|
} | gofmt > "zknownfolderids_windows.go"
|
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build generate
|
||||||
|
// +build generate
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go
|
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows && race
|
||||||
|
// +build windows,race
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const raceenabled = true
|
||||||
|
|
||||||
|
func raceAcquire(addr unsafe.Pointer) {
|
||||||
|
runtime.RaceAcquire(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||||
|
runtime.RaceReleaseMerge(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||||
|
runtime.RaceReadRange(addr, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||||
|
runtime.RaceWriteRange(addr, len)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows && !race
|
||||||
|
// +build windows,!race
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const raceenabled = false
|
||||||
|
|
||||||
|
func raceAcquire(addr unsafe.Pointer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,247 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
const (
|
||||||
|
SC_MANAGER_CONNECT = 1
|
||||||
|
SC_MANAGER_CREATE_SERVICE = 2
|
||||||
|
SC_MANAGER_ENUMERATE_SERVICE = 4
|
||||||
|
SC_MANAGER_LOCK = 8
|
||||||
|
SC_MANAGER_QUERY_LOCK_STATUS = 16
|
||||||
|
SC_MANAGER_MODIFY_BOOT_CONFIG = 32
|
||||||
|
SC_MANAGER_ALL_ACCESS = 0xf003f
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SERVICE_KERNEL_DRIVER = 1
|
||||||
|
SERVICE_FILE_SYSTEM_DRIVER = 2
|
||||||
|
SERVICE_ADAPTER = 4
|
||||||
|
SERVICE_RECOGNIZER_DRIVER = 8
|
||||||
|
SERVICE_WIN32_OWN_PROCESS = 16
|
||||||
|
SERVICE_WIN32_SHARE_PROCESS = 32
|
||||||
|
SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
|
||||||
|
SERVICE_INTERACTIVE_PROCESS = 256
|
||||||
|
SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
|
||||||
|
SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
|
||||||
|
|
||||||
|
SERVICE_BOOT_START = 0
|
||||||
|
SERVICE_SYSTEM_START = 1
|
||||||
|
SERVICE_AUTO_START = 2
|
||||||
|
SERVICE_DEMAND_START = 3
|
||||||
|
SERVICE_DISABLED = 4
|
||||||
|
|
||||||
|
SERVICE_ERROR_IGNORE = 0
|
||||||
|
SERVICE_ERROR_NORMAL = 1
|
||||||
|
SERVICE_ERROR_SEVERE = 2
|
||||||
|
SERVICE_ERROR_CRITICAL = 3
|
||||||
|
|
||||||
|
SC_STATUS_PROCESS_INFO = 0
|
||||||
|
|
||||||
|
SC_ACTION_NONE = 0
|
||||||
|
SC_ACTION_RESTART = 1
|
||||||
|
SC_ACTION_REBOOT = 2
|
||||||
|
SC_ACTION_RUN_COMMAND = 3
|
||||||
|
|
||||||
|
SERVICE_STOPPED = 1
|
||||||
|
SERVICE_START_PENDING = 2
|
||||||
|
SERVICE_STOP_PENDING = 3
|
||||||
|
SERVICE_RUNNING = 4
|
||||||
|
SERVICE_CONTINUE_PENDING = 5
|
||||||
|
SERVICE_PAUSE_PENDING = 6
|
||||||
|
SERVICE_PAUSED = 7
|
||||||
|
SERVICE_NO_CHANGE = 0xffffffff
|
||||||
|
|
||||||
|
SERVICE_ACCEPT_STOP = 1
|
||||||
|
SERVICE_ACCEPT_PAUSE_CONTINUE = 2
|
||||||
|
SERVICE_ACCEPT_SHUTDOWN = 4
|
||||||
|
SERVICE_ACCEPT_PARAMCHANGE = 8
|
||||||
|
SERVICE_ACCEPT_NETBINDCHANGE = 16
|
||||||
|
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
|
||||||
|
SERVICE_ACCEPT_POWEREVENT = 64
|
||||||
|
SERVICE_ACCEPT_SESSIONCHANGE = 128
|
||||||
|
SERVICE_ACCEPT_PRESHUTDOWN = 256
|
||||||
|
|
||||||
|
SERVICE_CONTROL_STOP = 1
|
||||||
|
SERVICE_CONTROL_PAUSE = 2
|
||||||
|
SERVICE_CONTROL_CONTINUE = 3
|
||||||
|
SERVICE_CONTROL_INTERROGATE = 4
|
||||||
|
SERVICE_CONTROL_SHUTDOWN = 5
|
||||||
|
SERVICE_CONTROL_PARAMCHANGE = 6
|
||||||
|
SERVICE_CONTROL_NETBINDADD = 7
|
||||||
|
SERVICE_CONTROL_NETBINDREMOVE = 8
|
||||||
|
SERVICE_CONTROL_NETBINDENABLE = 9
|
||||||
|
SERVICE_CONTROL_NETBINDDISABLE = 10
|
||||||
|
SERVICE_CONTROL_DEVICEEVENT = 11
|
||||||
|
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
|
||||||
|
SERVICE_CONTROL_POWEREVENT = 13
|
||||||
|
SERVICE_CONTROL_SESSIONCHANGE = 14
|
||||||
|
SERVICE_CONTROL_PRESHUTDOWN = 15
|
||||||
|
|
||||||
|
SERVICE_ACTIVE = 1
|
||||||
|
SERVICE_INACTIVE = 2
|
||||||
|
SERVICE_STATE_ALL = 3
|
||||||
|
|
||||||
|
SERVICE_QUERY_CONFIG = 1
|
||||||
|
SERVICE_CHANGE_CONFIG = 2
|
||||||
|
SERVICE_QUERY_STATUS = 4
|
||||||
|
SERVICE_ENUMERATE_DEPENDENTS = 8
|
||||||
|
SERVICE_START = 16
|
||||||
|
SERVICE_STOP = 32
|
||||||
|
SERVICE_PAUSE_CONTINUE = 64
|
||||||
|
SERVICE_INTERROGATE = 128
|
||||||
|
SERVICE_USER_DEFINED_CONTROL = 256
|
||||||
|
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
|
||||||
|
|
||||||
|
SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
|
||||||
|
|
||||||
|
SERVICE_CONFIG_DESCRIPTION = 1
|
||||||
|
SERVICE_CONFIG_FAILURE_ACTIONS = 2
|
||||||
|
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
|
||||||
|
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
|
||||||
|
SERVICE_CONFIG_SERVICE_SID_INFO = 5
|
||||||
|
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
|
||||||
|
SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
|
||||||
|
SERVICE_CONFIG_TRIGGER_INFO = 8
|
||||||
|
SERVICE_CONFIG_PREFERRED_NODE = 9
|
||||||
|
SERVICE_CONFIG_LAUNCH_PROTECTED = 12
|
||||||
|
|
||||||
|
SERVICE_SID_TYPE_NONE = 0
|
||||||
|
SERVICE_SID_TYPE_UNRESTRICTED = 1
|
||||||
|
SERVICE_SID_TYPE_RESTRICTED = 2 | SERVICE_SID_TYPE_UNRESTRICTED
|
||||||
|
|
||||||
|
SC_ENUM_PROCESS_INFO = 0
|
||||||
|
|
||||||
|
SERVICE_NOTIFY_STATUS_CHANGE = 2
|
||||||
|
SERVICE_NOTIFY_STOPPED = 0x00000001
|
||||||
|
SERVICE_NOTIFY_START_PENDING = 0x00000002
|
||||||
|
SERVICE_NOTIFY_STOP_PENDING = 0x00000004
|
||||||
|
SERVICE_NOTIFY_RUNNING = 0x00000008
|
||||||
|
SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010
|
||||||
|
SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020
|
||||||
|
SERVICE_NOTIFY_PAUSED = 0x00000040
|
||||||
|
SERVICE_NOTIFY_CREATED = 0x00000080
|
||||||
|
SERVICE_NOTIFY_DELETED = 0x00000100
|
||||||
|
SERVICE_NOTIFY_DELETE_PENDING = 0x00000200
|
||||||
|
|
||||||
|
SC_EVENT_DATABASE_CHANGE = 0
|
||||||
|
SC_EVENT_PROPERTY_CHANGE = 1
|
||||||
|
SC_EVENT_STATUS_CHANGE = 2
|
||||||
|
|
||||||
|
SERVICE_START_REASON_DEMAND = 0x00000001
|
||||||
|
SERVICE_START_REASON_AUTO = 0x00000002
|
||||||
|
SERVICE_START_REASON_TRIGGER = 0x00000004
|
||||||
|
SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008
|
||||||
|
SERVICE_START_REASON_DELAYEDAUTO = 0x00000010
|
||||||
|
|
||||||
|
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type SERVICE_STATUS struct {
|
||||||
|
ServiceType uint32
|
||||||
|
CurrentState uint32
|
||||||
|
ControlsAccepted uint32
|
||||||
|
Win32ExitCode uint32
|
||||||
|
ServiceSpecificExitCode uint32
|
||||||
|
CheckPoint uint32
|
||||||
|
WaitHint uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_TABLE_ENTRY struct {
|
||||||
|
ServiceName *uint16
|
||||||
|
ServiceProc uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUERY_SERVICE_CONFIG struct {
|
||||||
|
ServiceType uint32
|
||||||
|
StartType uint32
|
||||||
|
ErrorControl uint32
|
||||||
|
BinaryPathName *uint16
|
||||||
|
LoadOrderGroup *uint16
|
||||||
|
TagId uint32
|
||||||
|
Dependencies *uint16
|
||||||
|
ServiceStartName *uint16
|
||||||
|
DisplayName *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_DESCRIPTION struct {
|
||||||
|
Description *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_DELAYED_AUTO_START_INFO struct {
|
||||||
|
IsDelayedAutoStartUp uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_STATUS_PROCESS struct {
|
||||||
|
ServiceType uint32
|
||||||
|
CurrentState uint32
|
||||||
|
ControlsAccepted uint32
|
||||||
|
Win32ExitCode uint32
|
||||||
|
ServiceSpecificExitCode uint32
|
||||||
|
CheckPoint uint32
|
||||||
|
WaitHint uint32
|
||||||
|
ProcessId uint32
|
||||||
|
ServiceFlags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ENUM_SERVICE_STATUS_PROCESS struct {
|
||||||
|
ServiceName *uint16
|
||||||
|
DisplayName *uint16
|
||||||
|
ServiceStatusProcess SERVICE_STATUS_PROCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_NOTIFY struct {
|
||||||
|
Version uint32
|
||||||
|
NotifyCallback uintptr
|
||||||
|
Context uintptr
|
||||||
|
NotificationStatus uint32
|
||||||
|
ServiceStatus SERVICE_STATUS_PROCESS
|
||||||
|
NotificationTriggered uint32
|
||||||
|
ServiceNames *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SERVICE_FAILURE_ACTIONS struct {
|
||||||
|
ResetPeriod uint32
|
||||||
|
RebootMsg *uint16
|
||||||
|
Command *uint16
|
||||||
|
ActionsCount uint32
|
||||||
|
Actions *SC_ACTION
|
||||||
|
}
|
||||||
|
|
||||||
|
type SC_ACTION struct {
|
||||||
|
Type uint32
|
||||||
|
Delay uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUERY_SERVICE_LOCK_STATUS struct {
|
||||||
|
IsLocked uint32
|
||||||
|
LockOwner *uint16
|
||||||
|
LockDuration uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
|
||||||
|
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
|
||||||
|
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
|
||||||
|
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
|
||||||
|
//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
|
||||||
|
//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW
|
||||||
|
//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus
|
||||||
|
//sys QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceLockStatusW
|
||||||
|
//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService
|
||||||
|
//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW
|
||||||
|
//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus
|
||||||
|
//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW
|
||||||
|
//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
|
||||||
|
//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
|
||||||
|
//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
|
||||||
|
//sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW
|
||||||
|
//sys QueryServiceStatusEx(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceStatusEx
|
||||||
|
//sys NotifyServiceStatusChange(service Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW
|
||||||
|
//sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications?
|
||||||
|
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
|
||||||
|
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
|
||||||
|
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||||
|
if val < 0 {
|
||||||
|
return "-" + itoa(-val)
|
||||||
|
}
|
||||||
|
var buf [32]byte // big enough for int64
|
||||||
|
i := len(buf) - 1
|
||||||
|
for val >= 10 {
|
||||||
|
buf[i] = byte(val%10 + '0')
|
||||||
|
i--
|
||||||
|
val /= 10
|
||||||
|
}
|
||||||
|
buf[i] = byte(val + '0')
|
||||||
|
return string(buf[i:])
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
// Package windows contains an interface to the low-level operating system
|
||||||
|
// primitives. OS details vary depending on the underlying system, and
|
||||||
|
// by default, godoc will display the OS-specific documentation for the current
|
||||||
|
// system. If you want godoc to display syscall documentation for another
|
||||||
|
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||||
|
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||||
|
// to freebsd and $GOARCH to arm.
|
||||||
|
//
|
||||||
|
// The primary use of this package is inside other packages that provide a more
|
||||||
|
// portable interface to the system, such as "os", "time" and "net". Use
|
||||||
|
// those packages rather than this one if you can.
|
||||||
|
//
|
||||||
|
// For details of the functions and data types in this package consult
|
||||||
|
// the manuals for the appropriate operating system.
|
||||||
|
//
|
||||||
|
// These calls return err == nil to indicate success; otherwise
|
||||||
|
// err represents an operating system error describing the failure and
|
||||||
|
// holds a value of type syscall.Errno.
|
||||||
|
package windows // import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||||
|
// containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, syscall.EINVAL).
|
||||||
|
func ByteSliceFromString(s string) ([]byte, error) {
|
||||||
|
if strings.IndexByte(s, 0) != -1 {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
a := make([]byte, len(s)+1)
|
||||||
|
copy(a, s)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||||
|
// bytes containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, syscall.EINVAL).
|
||||||
|
func BytePtrFromString(s string) (*byte, error) {
|
||||||
|
a, err := ByteSliceFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &a[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||||
|
// bytes after the NUL removed.
|
||||||
|
func ByteSliceToString(s []byte) string {
|
||||||
|
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||||
|
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||||
|
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||||
|
func BytePtrToString(p *byte) string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if *p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find NUL terminator.
|
||||||
|
n := 0
|
||||||
|
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||||
|
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(unsafe.Slice(p, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||||
|
// See mksyscall.pl.
|
||||||
|
var _zero uintptr
|
||||||
|
|
||||||
|
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||||
|
return int64(ts.Sec), int64(ts.Nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||||
|
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Timespec) Nano() int64 {
|
||||||
|
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *Timeval) Nano() int64 {
|
||||||
|
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
type WSAData struct {
|
||||||
|
Version uint16
|
||||||
|
HighVersion uint16
|
||||||
|
Description [WSADESCRIPTION_LEN + 1]byte
|
||||||
|
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||||
|
MaxSockets uint16
|
||||||
|
MaxUdpDg uint16
|
||||||
|
VendorInfo *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Servent struct {
|
||||||
|
Name *byte
|
||||||
|
Aliases **byte
|
||||||
|
Port uint16
|
||||||
|
Proto *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||||
|
PerProcessUserTimeLimit int64
|
||||||
|
PerJobUserTimeLimit int64
|
||||||
|
LimitFlags uint32
|
||||||
|
MinimumWorkingSetSize uintptr
|
||||||
|
MaximumWorkingSetSize uintptr
|
||||||
|
ActiveProcessLimit uint32
|
||||||
|
Affinity uintptr
|
||||||
|
PriorityClass uint32
|
||||||
|
SchedulingClass uint32
|
||||||
|
_ uint32 // pad to 8 byte boundary
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
type WSAData struct {
|
||||||
|
Version uint16
|
||||||
|
HighVersion uint16
|
||||||
|
MaxSockets uint16
|
||||||
|
MaxUdpDg uint16
|
||||||
|
VendorInfo *byte
|
||||||
|
Description [WSADESCRIPTION_LEN + 1]byte
|
||||||
|
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Servent struct {
|
||||||
|
Name *byte
|
||||||
|
Aliases **byte
|
||||||
|
Proto *byte
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||||
|
PerProcessUserTimeLimit int64
|
||||||
|
PerJobUserTimeLimit int64
|
||||||
|
LimitFlags uint32
|
||||||
|
MinimumWorkingSetSize uintptr
|
||||||
|
MaximumWorkingSetSize uintptr
|
||||||
|
ActiveProcessLimit uint32
|
||||||
|
Affinity uintptr
|
||||||
|
PriorityClass uint32
|
||||||
|
SchedulingClass uint32
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
type WSAData struct {
|
||||||
|
Version uint16
|
||||||
|
HighVersion uint16
|
||||||
|
Description [WSADESCRIPTION_LEN + 1]byte
|
||||||
|
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||||
|
MaxSockets uint16
|
||||||
|
MaxUdpDg uint16
|
||||||
|
VendorInfo *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Servent struct {
|
||||||
|
Name *byte
|
||||||
|
Aliases **byte
|
||||||
|
Port uint16
|
||||||
|
Proto *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||||
|
PerProcessUserTimeLimit int64
|
||||||
|
PerJobUserTimeLimit int64
|
||||||
|
LimitFlags uint32
|
||||||
|
MinimumWorkingSetSize uintptr
|
||||||
|
MaximumWorkingSetSize uintptr
|
||||||
|
ActiveProcessLimit uint32
|
||||||
|
Affinity uintptr
|
||||||
|
PriorityClass uint32
|
||||||
|
SchedulingClass uint32
|
||||||
|
_ uint32 // pad to 8 byte boundary
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
type WSAData struct {
|
||||||
|
Version uint16
|
||||||
|
HighVersion uint16
|
||||||
|
MaxSockets uint16
|
||||||
|
MaxUdpDg uint16
|
||||||
|
VendorInfo *byte
|
||||||
|
Description [WSADESCRIPTION_LEN + 1]byte
|
||||||
|
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Servent struct {
|
||||||
|
Name *byte
|
||||||
|
Aliases **byte
|
||||||
|
Proto *byte
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||||
|
PerProcessUserTimeLimit int64
|
||||||
|
PerJobUserTimeLimit int64
|
||||||
|
LimitFlags uint32
|
||||||
|
MinimumWorkingSetSize uintptr
|
||||||
|
MaximumWorkingSetSize uintptr
|
||||||
|
ActiveProcessLimit uint32
|
||||||
|
Affinity uintptr
|
||||||
|
PriorityClass uint32
|
||||||
|
SchedulingClass uint32
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,149 @@
|
|||||||
|
// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
type KNOWNFOLDERID GUID
|
||||||
|
|
||||||
|
var (
|
||||||
|
FOLDERID_NetworkFolder = &KNOWNFOLDERID{0xd20beec4, 0x5ca8, 0x4905, [8]byte{0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53}}
|
||||||
|
FOLDERID_ComputerFolder = &KNOWNFOLDERID{0x0ac0837c, 0xbbf8, 0x452a, [8]byte{0x85, 0x0d, 0x79, 0xd0, 0x8e, 0x66, 0x7c, 0xa7}}
|
||||||
|
FOLDERID_InternetFolder = &KNOWNFOLDERID{0x4d9f7874, 0x4e0c, 0x4904, [8]byte{0x96, 0x7b, 0x40, 0xb0, 0xd2, 0x0c, 0x3e, 0x4b}}
|
||||||
|
FOLDERID_ControlPanelFolder = &KNOWNFOLDERID{0x82a74aeb, 0xaeb4, 0x465c, [8]byte{0xa0, 0x14, 0xd0, 0x97, 0xee, 0x34, 0x6d, 0x63}}
|
||||||
|
FOLDERID_PrintersFolder = &KNOWNFOLDERID{0x76fc4e2d, 0xd6ad, 0x4519, [8]byte{0xa6, 0x63, 0x37, 0xbd, 0x56, 0x06, 0x81, 0x85}}
|
||||||
|
FOLDERID_SyncManagerFolder = &KNOWNFOLDERID{0x43668bf8, 0xc14e, 0x49b2, [8]byte{0x97, 0xc9, 0x74, 0x77, 0x84, 0xd7, 0x84, 0xb7}}
|
||||||
|
FOLDERID_SyncSetupFolder = &KNOWNFOLDERID{0x0f214138, 0xb1d3, 0x4a90, [8]byte{0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a}}
|
||||||
|
FOLDERID_ConflictFolder = &KNOWNFOLDERID{0x4bfefb45, 0x347d, 0x4006, [8]byte{0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92}}
|
||||||
|
FOLDERID_SyncResultsFolder = &KNOWNFOLDERID{0x289a9a43, 0xbe44, 0x4057, [8]byte{0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9}}
|
||||||
|
FOLDERID_RecycleBinFolder = &KNOWNFOLDERID{0xb7534046, 0x3ecb, 0x4c18, [8]byte{0xbe, 0x4e, 0x64, 0xcd, 0x4c, 0xb7, 0xd6, 0xac}}
|
||||||
|
FOLDERID_ConnectionsFolder = &KNOWNFOLDERID{0x6f0cd92b, 0x2e97, 0x45d1, [8]byte{0x88, 0xff, 0xb0, 0xd1, 0x86, 0xb8, 0xde, 0xdd}}
|
||||||
|
FOLDERID_Fonts = &KNOWNFOLDERID{0xfd228cb7, 0xae11, 0x4ae3, [8]byte{0x86, 0x4c, 0x16, 0xf3, 0x91, 0x0a, 0xb8, 0xfe}}
|
||||||
|
FOLDERID_Desktop = &KNOWNFOLDERID{0xb4bfcc3a, 0xdb2c, 0x424c, [8]byte{0xb0, 0x29, 0x7f, 0xe9, 0x9a, 0x87, 0xc6, 0x41}}
|
||||||
|
FOLDERID_Startup = &KNOWNFOLDERID{0xb97d20bb, 0xf46a, 0x4c97, [8]byte{0xba, 0x10, 0x5e, 0x36, 0x08, 0x43, 0x08, 0x54}}
|
||||||
|
FOLDERID_Programs = &KNOWNFOLDERID{0xa77f5d77, 0x2e2b, 0x44c3, [8]byte{0xa6, 0xa2, 0xab, 0xa6, 0x01, 0x05, 0x4a, 0x51}}
|
||||||
|
FOLDERID_StartMenu = &KNOWNFOLDERID{0x625b53c3, 0xab48, 0x4ec1, [8]byte{0xba, 0x1f, 0xa1, 0xef, 0x41, 0x46, 0xfc, 0x19}}
|
||||||
|
FOLDERID_Recent = &KNOWNFOLDERID{0xae50c081, 0xebd2, 0x438a, [8]byte{0x86, 0x55, 0x8a, 0x09, 0x2e, 0x34, 0x98, 0x7a}}
|
||||||
|
FOLDERID_SendTo = &KNOWNFOLDERID{0x8983036c, 0x27c0, 0x404b, [8]byte{0x8f, 0x08, 0x10, 0x2d, 0x10, 0xdc, 0xfd, 0x74}}
|
||||||
|
FOLDERID_Documents = &KNOWNFOLDERID{0xfdd39ad0, 0x238f, 0x46af, [8]byte{0xad, 0xb4, 0x6c, 0x85, 0x48, 0x03, 0x69, 0xc7}}
|
||||||
|
FOLDERID_Favorites = &KNOWNFOLDERID{0x1777f761, 0x68ad, 0x4d8a, [8]byte{0x87, 0xbd, 0x30, 0xb7, 0x59, 0xfa, 0x33, 0xdd}}
|
||||||
|
FOLDERID_NetHood = &KNOWNFOLDERID{0xc5abbf53, 0xe17f, 0x4121, [8]byte{0x89, 0x00, 0x86, 0x62, 0x6f, 0xc2, 0xc9, 0x73}}
|
||||||
|
FOLDERID_PrintHood = &KNOWNFOLDERID{0x9274bd8d, 0xcfd1, 0x41c3, [8]byte{0xb3, 0x5e, 0xb1, 0x3f, 0x55, 0xa7, 0x58, 0xf4}}
|
||||||
|
FOLDERID_Templates = &KNOWNFOLDERID{0xa63293e8, 0x664e, 0x48db, [8]byte{0xa0, 0x79, 0xdf, 0x75, 0x9e, 0x05, 0x09, 0xf7}}
|
||||||
|
FOLDERID_CommonStartup = &KNOWNFOLDERID{0x82a5ea35, 0xd9cd, 0x47c5, [8]byte{0x96, 0x29, 0xe1, 0x5d, 0x2f, 0x71, 0x4e, 0x6e}}
|
||||||
|
FOLDERID_CommonPrograms = &KNOWNFOLDERID{0x0139d44e, 0x6afe, 0x49f2, [8]byte{0x86, 0x90, 0x3d, 0xaf, 0xca, 0xe6, 0xff, 0xb8}}
|
||||||
|
FOLDERID_CommonStartMenu = &KNOWNFOLDERID{0xa4115719, 0xd62e, 0x491d, [8]byte{0xaa, 0x7c, 0xe7, 0x4b, 0x8b, 0xe3, 0xb0, 0x67}}
|
||||||
|
FOLDERID_PublicDesktop = &KNOWNFOLDERID{0xc4aa340d, 0xf20f, 0x4863, [8]byte{0xaf, 0xef, 0xf8, 0x7e, 0xf2, 0xe6, 0xba, 0x25}}
|
||||||
|
FOLDERID_ProgramData = &KNOWNFOLDERID{0x62ab5d82, 0xfdc1, 0x4dc3, [8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97}}
|
||||||
|
FOLDERID_CommonTemplates = &KNOWNFOLDERID{0xb94237e7, 0x57ac, 0x4347, [8]byte{0x91, 0x51, 0xb0, 0x8c, 0x6c, 0x32, 0xd1, 0xf7}}
|
||||||
|
FOLDERID_PublicDocuments = &KNOWNFOLDERID{0xed4824af, 0xdce4, 0x45a8, [8]byte{0x81, 0xe2, 0xfc, 0x79, 0x65, 0x08, 0x36, 0x34}}
|
||||||
|
FOLDERID_RoamingAppData = &KNOWNFOLDERID{0x3eb685db, 0x65f9, 0x4cf6, [8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d}}
|
||||||
|
FOLDERID_LocalAppData = &KNOWNFOLDERID{0xf1b32785, 0x6fba, 0x4fcf, [8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91}}
|
||||||
|
FOLDERID_LocalAppDataLow = &KNOWNFOLDERID{0xa520a1a4, 0x1780, 0x4ff6, [8]byte{0xbd, 0x18, 0x16, 0x73, 0x43, 0xc5, 0xaf, 0x16}}
|
||||||
|
FOLDERID_InternetCache = &KNOWNFOLDERID{0x352481e8, 0x33be, 0x4251, [8]byte{0xba, 0x85, 0x60, 0x07, 0xca, 0xed, 0xcf, 0x9d}}
|
||||||
|
FOLDERID_Cookies = &KNOWNFOLDERID{0x2b0f765d, 0xc0e9, 0x4171, [8]byte{0x90, 0x8e, 0x08, 0xa6, 0x11, 0xb8, 0x4f, 0xf6}}
|
||||||
|
FOLDERID_History = &KNOWNFOLDERID{0xd9dc8a3b, 0xb784, 0x432e, [8]byte{0xa7, 0x81, 0x5a, 0x11, 0x30, 0xa7, 0x59, 0x63}}
|
||||||
|
FOLDERID_System = &KNOWNFOLDERID{0x1ac14e77, 0x02e7, 0x4e5d, [8]byte{0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7}}
|
||||||
|
FOLDERID_SystemX86 = &KNOWNFOLDERID{0xd65231b0, 0xb2f1, 0x4857, [8]byte{0xa4, 0xce, 0xa8, 0xe7, 0xc6, 0xea, 0x7d, 0x27}}
|
||||||
|
FOLDERID_Windows = &KNOWNFOLDERID{0xf38bf404, 0x1d43, 0x42f2, [8]byte{0x93, 0x05, 0x67, 0xde, 0x0b, 0x28, 0xfc, 0x23}}
|
||||||
|
FOLDERID_Profile = &KNOWNFOLDERID{0x5e6c858f, 0x0e22, 0x4760, [8]byte{0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73}}
|
||||||
|
FOLDERID_Pictures = &KNOWNFOLDERID{0x33e28130, 0x4e1e, 0x4676, [8]byte{0x83, 0x5a, 0x98, 0x39, 0x5c, 0x3b, 0xc3, 0xbb}}
|
||||||
|
FOLDERID_ProgramFilesX86 = &KNOWNFOLDERID{0x7c5a40ef, 0xa0fb, 0x4bfc, [8]byte{0x87, 0x4a, 0xc0, 0xf2, 0xe0, 0xb9, 0xfa, 0x8e}}
|
||||||
|
FOLDERID_ProgramFilesCommonX86 = &KNOWNFOLDERID{0xde974d24, 0xd9c6, 0x4d3e, [8]byte{0xbf, 0x91, 0xf4, 0x45, 0x51, 0x20, 0xb9, 0x17}}
|
||||||
|
FOLDERID_ProgramFilesX64 = &KNOWNFOLDERID{0x6d809377, 0x6af0, 0x444b, [8]byte{0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e}}
|
||||||
|
FOLDERID_ProgramFilesCommonX64 = &KNOWNFOLDERID{0x6365d5a7, 0x0f0d, 0x45e5, [8]byte{0x87, 0xf6, 0x0d, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d}}
|
||||||
|
FOLDERID_ProgramFiles = &KNOWNFOLDERID{0x905e63b6, 0xc1bf, 0x494e, [8]byte{0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a}}
|
||||||
|
FOLDERID_ProgramFilesCommon = &KNOWNFOLDERID{0xf7f1ed05, 0x9f6d, 0x47a2, [8]byte{0xaa, 0xae, 0x29, 0xd3, 0x17, 0xc6, 0xf0, 0x66}}
|
||||||
|
FOLDERID_UserProgramFiles = &KNOWNFOLDERID{0x5cd7aee2, 0x2219, 0x4a67, [8]byte{0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb}}
|
||||||
|
FOLDERID_UserProgramFilesCommon = &KNOWNFOLDERID{0xbcbd3057, 0xca5c, 0x4622, [8]byte{0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16}}
|
||||||
|
FOLDERID_AdminTools = &KNOWNFOLDERID{0x724ef170, 0xa42d, 0x4fef, [8]byte{0x9f, 0x26, 0xb6, 0x0e, 0x84, 0x6f, 0xba, 0x4f}}
|
||||||
|
FOLDERID_CommonAdminTools = &KNOWNFOLDERID{0xd0384e7d, 0xbac3, 0x4797, [8]byte{0x8f, 0x14, 0xcb, 0xa2, 0x29, 0xb3, 0x92, 0xb5}}
|
||||||
|
FOLDERID_Music = &KNOWNFOLDERID{0x4bd8d571, 0x6d19, 0x48d3, [8]byte{0xbe, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0e, 0x43}}
|
||||||
|
FOLDERID_Videos = &KNOWNFOLDERID{0x18989b1d, 0x99b5, 0x455b, [8]byte{0x84, 0x1c, 0xab, 0x7c, 0x74, 0xe4, 0xdd, 0xfc}}
|
||||||
|
FOLDERID_Ringtones = &KNOWNFOLDERID{0xc870044b, 0xf49e, 0x4126, [8]byte{0xa9, 0xc3, 0xb5, 0x2a, 0x1f, 0xf4, 0x11, 0xe8}}
|
||||||
|
FOLDERID_PublicPictures = &KNOWNFOLDERID{0xb6ebfb86, 0x6907, 0x413c, [8]byte{0x9a, 0xf7, 0x4f, 0xc2, 0xab, 0xf0, 0x7c, 0xc5}}
|
||||||
|
FOLDERID_PublicMusic = &KNOWNFOLDERID{0x3214fab5, 0x9757, 0x4298, [8]byte{0xbb, 0x61, 0x92, 0xa9, 0xde, 0xaa, 0x44, 0xff}}
|
||||||
|
FOLDERID_PublicVideos = &KNOWNFOLDERID{0x2400183a, 0x6185, 0x49fb, [8]byte{0xa2, 0xd8, 0x4a, 0x39, 0x2a, 0x60, 0x2b, 0xa3}}
|
||||||
|
FOLDERID_PublicRingtones = &KNOWNFOLDERID{0xe555ab60, 0x153b, 0x4d17, [8]byte{0x9f, 0x04, 0xa5, 0xfe, 0x99, 0xfc, 0x15, 0xec}}
|
||||||
|
FOLDERID_ResourceDir = &KNOWNFOLDERID{0x8ad10c31, 0x2adb, 0x4296, [8]byte{0xa8, 0xf7, 0xe4, 0x70, 0x12, 0x32, 0xc9, 0x72}}
|
||||||
|
FOLDERID_LocalizedResourcesDir = &KNOWNFOLDERID{0x2a00375e, 0x224c, 0x49de, [8]byte{0xb8, 0xd1, 0x44, 0x0d, 0xf7, 0xef, 0x3d, 0xdc}}
|
||||||
|
FOLDERID_CommonOEMLinks = &KNOWNFOLDERID{0xc1bae2d0, 0x10df, 0x4334, [8]byte{0xbe, 0xdd, 0x7a, 0xa2, 0x0b, 0x22, 0x7a, 0x9d}}
|
||||||
|
FOLDERID_CDBurning = &KNOWNFOLDERID{0x9e52ab10, 0xf80d, 0x49df, [8]byte{0xac, 0xb8, 0x43, 0x30, 0xf5, 0x68, 0x78, 0x55}}
|
||||||
|
FOLDERID_UserProfiles = &KNOWNFOLDERID{0x0762d272, 0xc50a, 0x4bb0, [8]byte{0xa3, 0x82, 0x69, 0x7d, 0xcd, 0x72, 0x9b, 0x80}}
|
||||||
|
FOLDERID_Playlists = &KNOWNFOLDERID{0xde92c1c7, 0x837f, 0x4f69, [8]byte{0xa3, 0xbb, 0x86, 0xe6, 0x31, 0x20, 0x4a, 0x23}}
|
||||||
|
FOLDERID_SamplePlaylists = &KNOWNFOLDERID{0x15ca69b3, 0x30ee, 0x49c1, [8]byte{0xac, 0xe1, 0x6b, 0x5e, 0xc3, 0x72, 0xaf, 0xb5}}
|
||||||
|
FOLDERID_SampleMusic = &KNOWNFOLDERID{0xb250c668, 0xf57d, 0x4ee1, [8]byte{0xa6, 0x3c, 0x29, 0x0e, 0xe7, 0xd1, 0xaa, 0x1f}}
|
||||||
|
FOLDERID_SamplePictures = &KNOWNFOLDERID{0xc4900540, 0x2379, 0x4c75, [8]byte{0x84, 0x4b, 0x64, 0xe6, 0xfa, 0xf8, 0x71, 0x6b}}
|
||||||
|
FOLDERID_SampleVideos = &KNOWNFOLDERID{0x859ead94, 0x2e85, 0x48ad, [8]byte{0xa7, 0x1a, 0x09, 0x69, 0xcb, 0x56, 0xa6, 0xcd}}
|
||||||
|
FOLDERID_PhotoAlbums = &KNOWNFOLDERID{0x69d2cf90, 0xfc33, 0x4fb7, [8]byte{0x9a, 0x0c, 0xeb, 0xb0, 0xf0, 0xfc, 0xb4, 0x3c}}
|
||||||
|
FOLDERID_Public = &KNOWNFOLDERID{0xdfdf76a2, 0xc82a, 0x4d63, [8]byte{0x90, 0x6a, 0x56, 0x44, 0xac, 0x45, 0x73, 0x85}}
|
||||||
|
FOLDERID_ChangeRemovePrograms = &KNOWNFOLDERID{0xdf7266ac, 0x9274, 0x4867, [8]byte{0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d}}
|
||||||
|
FOLDERID_AppUpdates = &KNOWNFOLDERID{0xa305ce99, 0xf527, 0x492b, [8]byte{0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4}}
|
||||||
|
FOLDERID_AddNewPrograms = &KNOWNFOLDERID{0xde61d971, 0x5ebc, 0x4f02, [8]byte{0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04}}
|
||||||
|
FOLDERID_Downloads = &KNOWNFOLDERID{0x374de290, 0x123f, 0x4565, [8]byte{0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}
|
||||||
|
FOLDERID_PublicDownloads = &KNOWNFOLDERID{0x3d644c9b, 0x1fb8, 0x4f30, [8]byte{0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0}}
|
||||||
|
FOLDERID_SavedSearches = &KNOWNFOLDERID{0x7d1d3a04, 0xdebb, 0x4115, [8]byte{0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda}}
|
||||||
|
FOLDERID_QuickLaunch = &KNOWNFOLDERID{0x52a4f021, 0x7b75, 0x48a9, [8]byte{0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f}}
|
||||||
|
FOLDERID_Contacts = &KNOWNFOLDERID{0x56784854, 0xc6cb, 0x462b, [8]byte{0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82}}
|
||||||
|
FOLDERID_SidebarParts = &KNOWNFOLDERID{0xa75d362e, 0x50fc, 0x4fb7, [8]byte{0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93}}
|
||||||
|
FOLDERID_SidebarDefaultParts = &KNOWNFOLDERID{0x7b396e54, 0x9ec5, 0x4300, [8]byte{0xbe, 0x0a, 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26}}
|
||||||
|
FOLDERID_PublicGameTasks = &KNOWNFOLDERID{0xdebf2536, 0xe1a8, 0x4c59, [8]byte{0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea}}
|
||||||
|
FOLDERID_GameTasks = &KNOWNFOLDERID{0x054fae61, 0x4dd8, 0x4787, [8]byte{0x80, 0xb6, 0x09, 0x02, 0x20, 0xc4, 0xb7, 0x00}}
|
||||||
|
FOLDERID_SavedGames = &KNOWNFOLDERID{0x4c5c32ff, 0xbb9d, 0x43b0, [8]byte{0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4}}
|
||||||
|
FOLDERID_Games = &KNOWNFOLDERID{0xcac52c1a, 0xb53d, 0x4edc, [8]byte{0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34}}
|
||||||
|
FOLDERID_SEARCH_MAPI = &KNOWNFOLDERID{0x98ec0e18, 0x2098, 0x4d44, [8]byte{0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81}}
|
||||||
|
FOLDERID_SEARCH_CSC = &KNOWNFOLDERID{0xee32e446, 0x31ca, 0x4aba, [8]byte{0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e}}
|
||||||
|
FOLDERID_Links = &KNOWNFOLDERID{0xbfb9d5e0, 0xc6a9, 0x404c, [8]byte{0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68}}
|
||||||
|
FOLDERID_UsersFiles = &KNOWNFOLDERID{0xf3ce0f7c, 0x4901, 0x4acc, [8]byte{0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f}}
|
||||||
|
FOLDERID_UsersLibraries = &KNOWNFOLDERID{0xa302545d, 0xdeff, 0x464b, [8]byte{0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b}}
|
||||||
|
FOLDERID_SearchHome = &KNOWNFOLDERID{0x190337d1, 0xb8ca, 0x4121, [8]byte{0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a}}
|
||||||
|
FOLDERID_OriginalImages = &KNOWNFOLDERID{0x2c36c0aa, 0x5812, 0x4b87, [8]byte{0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39}}
|
||||||
|
FOLDERID_DocumentsLibrary = &KNOWNFOLDERID{0x7b0db17d, 0x9cd2, 0x4a93, [8]byte{0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c}}
|
||||||
|
FOLDERID_MusicLibrary = &KNOWNFOLDERID{0x2112ab0a, 0xc86a, 0x4ffe, [8]byte{0xa3, 0x68, 0x0d, 0xe9, 0x6e, 0x47, 0x01, 0x2e}}
|
||||||
|
FOLDERID_PicturesLibrary = &KNOWNFOLDERID{0xa990ae9f, 0xa03b, 0x4e80, [8]byte{0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x04}}
|
||||||
|
FOLDERID_VideosLibrary = &KNOWNFOLDERID{0x491e922f, 0x5643, 0x4af4, [8]byte{0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74}}
|
||||||
|
FOLDERID_RecordedTVLibrary = &KNOWNFOLDERID{0x1a6fdba2, 0xf42d, 0x4358, [8]byte{0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5}}
|
||||||
|
FOLDERID_HomeGroup = &KNOWNFOLDERID{0x52528a6b, 0xb9e3, 0x4add, [8]byte{0xb6, 0x0d, 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d}}
|
||||||
|
FOLDERID_HomeGroupCurrentUser = &KNOWNFOLDERID{0x9b74b6a3, 0x0dfd, 0x4f11, [8]byte{0x9e, 0x78, 0x5f, 0x78, 0x00, 0xf2, 0xe7, 0x72}}
|
||||||
|
FOLDERID_DeviceMetadataStore = &KNOWNFOLDERID{0x5ce4a5e9, 0xe4eb, 0x479d, [8]byte{0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55}}
|
||||||
|
FOLDERID_Libraries = &KNOWNFOLDERID{0x1b3ea5dc, 0xb587, 0x4786, [8]byte{0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae}}
|
||||||
|
FOLDERID_PublicLibraries = &KNOWNFOLDERID{0x48daf80b, 0xe6cf, 0x4f4e, [8]byte{0xb8, 0x00, 0x0e, 0x69, 0xd8, 0x4e, 0xe3, 0x84}}
|
||||||
|
FOLDERID_UserPinned = &KNOWNFOLDERID{0x9e3995ab, 0x1f9c, 0x4f13, [8]byte{0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74}}
|
||||||
|
FOLDERID_ImplicitAppShortcuts = &KNOWNFOLDERID{0xbcb5256f, 0x79f6, 0x4cee, [8]byte{0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x02, 0xfd, 0x46}}
|
||||||
|
FOLDERID_AccountPictures = &KNOWNFOLDERID{0x008ca0b1, 0x55b4, 0x4c56, [8]byte{0xb8, 0xa8, 0x4d, 0xe4, 0xb2, 0x99, 0xd3, 0xbe}}
|
||||||
|
FOLDERID_PublicUserTiles = &KNOWNFOLDERID{0x0482af6c, 0x08f1, 0x4c34, [8]byte{0x8c, 0x90, 0xe1, 0x7e, 0xc9, 0x8b, 0x1e, 0x17}}
|
||||||
|
FOLDERID_AppsFolder = &KNOWNFOLDERID{0x1e87508d, 0x89c2, 0x42f0, [8]byte{0x8a, 0x7e, 0x64, 0x5a, 0x0f, 0x50, 0xca, 0x58}}
|
||||||
|
FOLDERID_StartMenuAllPrograms = &KNOWNFOLDERID{0xf26305ef, 0x6948, 0x40b9, [8]byte{0xb2, 0x55, 0x81, 0x45, 0x3d, 0x09, 0xc7, 0x85}}
|
||||||
|
FOLDERID_CommonStartMenuPlaces = &KNOWNFOLDERID{0xa440879f, 0x87a0, 0x4f7d, [8]byte{0xb7, 0x00, 0x02, 0x07, 0xb9, 0x66, 0x19, 0x4a}}
|
||||||
|
FOLDERID_ApplicationShortcuts = &KNOWNFOLDERID{0xa3918781, 0xe5f2, 0x4890, [8]byte{0xb3, 0xd9, 0xa7, 0xe5, 0x43, 0x32, 0x32, 0x8c}}
|
||||||
|
FOLDERID_RoamingTiles = &KNOWNFOLDERID{0x00bcfc5a, 0xed94, 0x4e48, [8]byte{0x96, 0xa1, 0x3f, 0x62, 0x17, 0xf2, 0x19, 0x90}}
|
||||||
|
FOLDERID_RoamedTileImages = &KNOWNFOLDERID{0xaaa8d5a5, 0xf1d6, 0x4259, [8]byte{0xba, 0xa8, 0x78, 0xe7, 0xef, 0x60, 0x83, 0x5e}}
|
||||||
|
FOLDERID_Screenshots = &KNOWNFOLDERID{0xb7bede81, 0xdf94, 0x4682, [8]byte{0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f}}
|
||||||
|
FOLDERID_CameraRoll = &KNOWNFOLDERID{0xab5fb87b, 0x7ce2, 0x4f83, [8]byte{0x91, 0x5d, 0x55, 0x08, 0x46, 0xc9, 0x53, 0x7b}}
|
||||||
|
FOLDERID_SkyDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||||
|
FOLDERID_OneDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||||
|
FOLDERID_SkyDriveDocuments = &KNOWNFOLDERID{0x24d89e24, 0x2f19, 0x4534, [8]byte{0x9d, 0xde, 0x6a, 0x66, 0x71, 0xfb, 0xb8, 0xfe}}
|
||||||
|
FOLDERID_SkyDrivePictures = &KNOWNFOLDERID{0x339719b5, 0x8c47, 0x4894, [8]byte{0x94, 0xc2, 0xd8, 0xf7, 0x7a, 0xdd, 0x44, 0xa6}}
|
||||||
|
FOLDERID_SkyDriveMusic = &KNOWNFOLDERID{0xc3f2459e, 0x80d6, 0x45dc, [8]byte{0xbf, 0xef, 0x1f, 0x76, 0x9f, 0x2b, 0xe7, 0x30}}
|
||||||
|
FOLDERID_SkyDriveCameraRoll = &KNOWNFOLDERID{0x767e6811, 0x49cb, 0x4273, [8]byte{0x87, 0xc2, 0x20, 0xf3, 0x55, 0xe1, 0x08, 0x5b}}
|
||||||
|
FOLDERID_SearchHistory = &KNOWNFOLDERID{0x0d4c3db6, 0x03a3, 0x462f, [8]byte{0xa0, 0xe6, 0x08, 0x92, 0x4c, 0x41, 0xb5, 0xd4}}
|
||||||
|
FOLDERID_SearchTemplates = &KNOWNFOLDERID{0x7e636bfe, 0xdfa9, 0x4d5e, [8]byte{0xb4, 0x56, 0xd7, 0xb3, 0x98, 0x51, 0xd8, 0xa9}}
|
||||||
|
FOLDERID_CameraRollLibrary = &KNOWNFOLDERID{0x2b20df75, 0x1eda, 0x4039, [8]byte{0x80, 0x97, 0x38, 0x79, 0x82, 0x27, 0xd5, 0xb7}}
|
||||||
|
FOLDERID_SavedPictures = &KNOWNFOLDERID{0x3b193882, 0xd3ad, 0x4eab, [8]byte{0x96, 0x5a, 0x69, 0x82, 0x9d, 0x1f, 0xb5, 0x9f}}
|
||||||
|
FOLDERID_SavedPicturesLibrary = &KNOWNFOLDERID{0xe25b5812, 0xbe88, 0x4bd9, [8]byte{0x94, 0xb0, 0x29, 0x23, 0x34, 0x77, 0xb6, 0xc3}}
|
||||||
|
FOLDERID_RetailDemo = &KNOWNFOLDERID{0x12d4c69e, 0x24ad, 0x4923, [8]byte{0xbe, 0x19, 0x31, 0x32, 0x1c, 0x43, 0xa7, 0x67}}
|
||||||
|
FOLDERID_Device = &KNOWNFOLDERID{0x1c2ac1dc, 0x4358, 0x4b6c, [8]byte{0x97, 0x33, 0xaf, 0x21, 0x15, 0x65, 0x76, 0xf0}}
|
||||||
|
FOLDERID_DevelopmentFiles = &KNOWNFOLDERID{0xdbe8e08e, 0x3053, 0x4bbc, [8]byte{0xb1, 0x83, 0x2a, 0x7b, 0x2b, 0x19, 0x1e, 0x59}}
|
||||||
|
FOLDERID_Objects3D = &KNOWNFOLDERID{0x31c0dd25, 0x9439, 0x4f12, [8]byte{0xbf, 0x41, 0x7f, 0xf4, 0xed, 0xa3, 0x87, 0x22}}
|
||||||
|
FOLDERID_AppCaptures = &KNOWNFOLDERID{0xedc0fe71, 0x98d8, 0x4f4a, [8]byte{0xb9, 0x20, 0xc8, 0xdc, 0x13, 0x3c, 0xb1, 0x65}}
|
||||||
|
FOLDERID_LocalDocuments = &KNOWNFOLDERID{0xf42ee2d3, 0x909f, 0x4907, [8]byte{0x88, 0x71, 0x4c, 0x22, 0xfc, 0x0b, 0xf7, 0x56}}
|
||||||
|
FOLDERID_LocalPictures = &KNOWNFOLDERID{0x0ddd015d, 0xb06c, 0x45d5, [8]byte{0x8c, 0x4c, 0xf5, 0x97, 0x13, 0x85, 0x46, 0x39}}
|
||||||
|
FOLDERID_LocalVideos = &KNOWNFOLDERID{0x35286a68, 0x3c57, 0x41a1, [8]byte{0xbb, 0xb1, 0x0e, 0xae, 0x73, 0xd7, 0x6c, 0x95}}
|
||||||
|
FOLDERID_LocalMusic = &KNOWNFOLDERID{0xa0c69a99, 0x21c8, 0x4671, [8]byte{0x87, 0x03, 0x79, 0x34, 0x16, 0x2f, 0xcf, 0x1d}}
|
||||||
|
FOLDERID_LocalDownloads = &KNOWNFOLDERID{0x7d83ee9b, 0x2244, 0x4e70, [8]byte{0xb1, 0xf5, 0x53, 0x93, 0x04, 0x2a, 0xf1, 0xe4}}
|
||||||
|
FOLDERID_RecordedCalls = &KNOWNFOLDERID{0x2f8b40c2, 0x83ed, 0x48ee, [8]byte{0xb3, 0x83, 0xa1, 0xf1, 0x57, 0xec, 0x6f, 0x9a}}
|
||||||
|
FOLDERID_AllAppMods = &KNOWNFOLDERID{0x7ad67899, 0x66af, 0x43ba, [8]byte{0x91, 0x56, 0x6a, 0xad, 0x42, 0xe6, 0xc5, 0x96}}
|
||||||
|
FOLDERID_CurrentAppMods = &KNOWNFOLDERID{0x3db40b20, 0x2a30, 0x4dbe, [8]byte{0x91, 0x7e, 0x77, 0x1d, 0xd2, 0x1d, 0xd0, 0x99}}
|
||||||
|
FOLDERID_AppDataDesktop = &KNOWNFOLDERID{0xb2c5e279, 0x7add, 0x439f, [8]byte{0xb2, 0x8c, 0xc4, 0x1f, 0xe1, 0xbb, 0xf6, 0x72}}
|
||||||
|
FOLDERID_AppDataDocuments = &KNOWNFOLDERID{0x7be16610, 0x1f7f, 0x44ac, [8]byte{0xbf, 0xf0, 0x83, 0xe1, 0x5f, 0x2f, 0xfc, 0xa1}}
|
||||||
|
FOLDERID_AppDataFavorites = &KNOWNFOLDERID{0x7cfbefbc, 0xde1f, 0x45aa, [8]byte{0xb8, 0x43, 0xa5, 0x42, 0xac, 0x53, 0x6c, 0xc9}}
|
||||||
|
FOLDERID_AppDataProgramData = &KNOWNFOLDERID{0x559d40a3, 0xa036, 0x40fa, [8]byte{0xaf, 0x61, 0x84, 0xcb, 0x43, 0x0a, 0x4d, 0x34}}
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue