Files
ezbookkeeping/pkg/core/ip_pattern.go
T
2025-07-06 03:02:19 +08:00

178 lines
3.6 KiB
Go

package core
import (
"regexp"
"strconv"
"strings"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
// IPPattern represents a pattern for matching IP addresses, either IPv4 or IPv6
type IPPattern struct {
Pattern string
regex *regexp.Regexp
}
// Match returns if the given IP address matches the pattern
func (p *IPPattern) Match(ip string) bool {
if p.regex == nil {
return false
}
return p.regex.MatchString(ip)
}
// GobEncode returns the encoded data for this IP pattern
func (p *IPPattern) GobEncode() ([]byte, error) {
return []byte(p.Pattern), nil
}
// GobDecode decodes the data into the IP pattern
func (p *IPPattern) GobDecode(data []byte) error {
pattern := string(data)
if pattern == "" {
p.Pattern = ""
p.regex = nil
return nil
}
newPattern, err := ParseIPPattern(pattern)
if err != nil {
return err
}
p.Pattern = newPattern.Pattern
p.regex = newPattern.regex
return nil
}
// ParseIPPattern parses the given IP address pattern and returns an IPPattern object
func ParseIPPattern(ipPattern string) (*IPPattern, error) {
if ipPattern == "" {
return nil, nil
}
hasDot := false
hasSemicolon := false
for i := 0; i < len(ipPattern); i++ {
ch := rune(ipPattern[i])
if ch == '.' { // may be IPv4
if hasSemicolon {
return nil, errs.ErrInvalidIpAddressPattern
}
hasDot = true
} else if ch == ':' { // may be IPv6
if hasDot {
return nil, errs.ErrInvalidIpAddressPattern
}
hasSemicolon = true
}
}
if hasDot {
return ParseIPv4Pattern(ipPattern)
} else if hasSemicolon {
return ParseIPv6Pattern(ipPattern)
} else {
return nil, errs.ErrInvalidIpAddressPattern
}
}
// ParseIPv4Pattern parses the given IPv4 address pattern and returns an IPPattern object
func ParseIPv4Pattern(ipPattern string) (*IPPattern, error) {
items := strings.Split(ipPattern, ".")
if len(items) != 4 {
return nil, errs.ErrInvalidIpAddressPattern
}
regexBuilder := strings.Builder{}
regexBuilder.WriteRune('^')
for i := 0; i < len(items); i++ {
item := strings.TrimSpace(items[i])
if item == "*" {
regexBuilder.WriteString("[0-9]{1,3}")
} else if item == "" {
return nil, errs.ErrInvalidIpAddressPattern
} else {
num, err := strconv.Atoi(item)
if err != nil || num < 0 || num > 255 {
return nil, errs.ErrInvalidIpAddressPattern
}
regexBuilder.WriteString(item)
}
if i < len(items)-1 {
regexBuilder.WriteRune('\\')
regexBuilder.WriteRune('.')
}
}
regexBuilder.WriteRune('$')
regex, err := regexp.Compile(regexBuilder.String())
if err != nil {
return nil, errs.ErrInvalidIpAddressPattern
}
return &IPPattern{
Pattern: ipPattern,
regex: regex,
}, nil
}
// ParseIPv6Pattern parses the given IPv6 address pattern and returns an IPPattern object
func ParseIPv6Pattern(ipPattern string) (*IPPattern, error) {
items := strings.Split(ipPattern, ":")
if len(items) < 2 || len(items) > 8 {
return nil, errs.ErrInvalidIpAddressPattern
}
regexBuilder := strings.Builder{}
regexBuilder.WriteRune('^')
for i := 0; i < len(items); i++ {
item := strings.TrimSpace(items[i])
if item == "*" {
regexBuilder.WriteString("[0-9a-fA-F]{1,4}")
} else if i < len(items)-1 && item == "" {
// Do Nothing
} else {
num, err := strconv.ParseInt(item, 16, 32)
if err != nil || num < 0 || num > 0xFFFF {
return nil, errs.ErrInvalidIpAddressPattern
}
regexBuilder.WriteString(item)
}
if i < len(items)-1 {
regexBuilder.WriteRune(':')
}
}
regexBuilder.WriteRune('$')
regex, err := regexp.Compile(regexBuilder.String())
if err != nil {
return nil, errs.ErrInvalidIpAddressPattern
}
return &IPPattern{
Pattern: ipPattern,
regex: regex,
}, nil
}