mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-22 00:59:11 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
129 lines
2.4 KiB
Go
129 lines
2.4 KiB
Go
package acl
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/yusing/godoxy/internal/maxmind"
|
|
gperr "github.com/yusing/goutils/errs"
|
|
)
|
|
|
|
type MatcherFunc func(*maxmind.IPInfo) bool
|
|
|
|
type Matcher struct {
|
|
match MatcherFunc
|
|
raw string
|
|
}
|
|
|
|
type Matchers []Matcher
|
|
|
|
const (
|
|
MatcherTypeIP = "ip"
|
|
MatcherTypeCIDR = "cidr"
|
|
MatcherTypeTimeZone = "tz"
|
|
MatcherTypeCountry = "country"
|
|
)
|
|
|
|
// TODO: use this error in the future
|
|
//
|
|
//nolint:unused
|
|
var errMatcherFormat = gperr.Multiline().AddLines(
|
|
"invalid matcher format, expect {type}:{value}",
|
|
"Available types: ip|cidr|tz|country",
|
|
"ip:127.0.0.1",
|
|
"cidr:127.0.0.0/8",
|
|
"tz:Asia/Shanghai",
|
|
"country:GB",
|
|
)
|
|
|
|
var (
|
|
errSyntax = errors.New("syntax error")
|
|
errInvalidIP = errors.New("invalid IP")
|
|
errInvalidCIDR = errors.New("invalid CIDR")
|
|
)
|
|
|
|
func (matcher *Matcher) Parse(s string) error {
|
|
parts := strings.Split(s, ":")
|
|
if len(parts) != 2 {
|
|
return errSyntax
|
|
}
|
|
matcher.raw = s
|
|
|
|
switch parts[0] {
|
|
case MatcherTypeIP:
|
|
ip := net.ParseIP(parts[1])
|
|
if ip == nil {
|
|
return errInvalidIP
|
|
}
|
|
matcher.match = matchIP(ip)
|
|
case MatcherTypeCIDR:
|
|
_, net, err := net.ParseCIDR(parts[1])
|
|
if err != nil {
|
|
return errInvalidCIDR
|
|
}
|
|
matcher.match = matchCIDR(net)
|
|
case MatcherTypeTimeZone:
|
|
matcher.match = matchTimeZone(parts[1])
|
|
case MatcherTypeCountry:
|
|
matcher.match = matchISOCode(parts[1])
|
|
default:
|
|
return errSyntax
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (matchers Matchers) Match(ip *maxmind.IPInfo) bool {
|
|
for _, m := range matchers {
|
|
if m.match(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (matchers Matchers) MarshalText() ([]byte, error) {
|
|
if len(matchers) == 0 {
|
|
return []byte("[]"), nil
|
|
}
|
|
var buf bytes.Buffer
|
|
for _, m := range matchers {
|
|
buf.WriteString(m.raw)
|
|
buf.WriteByte('\n')
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func matchIP(ip net.IP) MatcherFunc {
|
|
return func(ip2 *maxmind.IPInfo) bool {
|
|
return ip.Equal(ip2.IP)
|
|
}
|
|
}
|
|
|
|
func matchCIDR(n *net.IPNet) MatcherFunc {
|
|
return func(ip *maxmind.IPInfo) bool {
|
|
return n.Contains(ip.IP)
|
|
}
|
|
}
|
|
|
|
func matchTimeZone(tz string) MatcherFunc {
|
|
return func(ip *maxmind.IPInfo) bool {
|
|
city, ok := maxmind.LookupCity(ip)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return city.Location.TimeZone == tz
|
|
}
|
|
}
|
|
|
|
func matchISOCode(iso string) MatcherFunc {
|
|
return func(ip *maxmind.IPInfo) bool {
|
|
city, ok := maxmind.LookupCity(ip)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return city.Country.IsoCode == iso
|
|
}
|
|
}
|