GoDoxy/internal/route/rules/on.go

254 lines
5.1 KiB
Go

package rules
import (
"net"
"net/http"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type (
RuleOn struct {
raw string
check CheckFulfill
}
CheckFulfill func(r *http.Request) bool
Checkers []CheckFulfill
)
const (
OnHeader = "header"
OnQuery = "query"
OnCookie = "cookie"
OnForm = "form"
OnPostForm = "postform"
OnMethod = "method"
OnPath = "path"
OnRemote = "remote"
)
var checkers = map[string]struct {
help Help
validate ValidateFunc
check func(r *http.Request, args any) bool
}{
OnHeader: {
help: Help{
command: OnHeader,
args: map[string]string{
"key": "the header key",
"value": "the header value",
},
},
validate: toStrTuple,
check: func(r *http.Request, args any) bool {
return r.Header.Get(args.(StrTuple).First) == args.(StrTuple).Second
},
},
OnQuery: {
help: Help{
command: OnQuery,
args: map[string]string{
"key": "the query key",
"value": "the query value",
},
},
validate: toStrTuple,
check: func(r *http.Request, args any) bool {
return r.URL.Query().Get(args.(StrTuple).First) == args.(StrTuple).Second
},
},
OnCookie: {
help: Help{
command: OnCookie,
args: map[string]string{
"key": "the cookie key",
"value": "the cookie value",
},
},
validate: toStrTuple,
check: func(r *http.Request, args any) bool {
cookies := r.CookiesNamed(args.(StrTuple).First)
for _, cookie := range cookies {
if cookie.Value == args.(StrTuple).Second {
return true
}
}
return false
},
},
OnForm: {
help: Help{
command: OnForm,
args: map[string]string{
"key": "the form key",
"value": "the form value",
},
},
validate: toStrTuple,
check: func(r *http.Request, args any) bool {
return r.FormValue(args.(StrTuple).First) == args.(StrTuple).Second
},
},
OnPostForm: {
help: Help{
command: OnPostForm,
args: map[string]string{
"key": "the form key",
"value": "the form value",
},
},
validate: toStrTuple,
check: func(r *http.Request, args any) bool {
return r.PostFormValue(args.(StrTuple).First) == args.(StrTuple).Second
},
},
OnMethod: {
help: Help{
command: OnMethod,
args: map[string]string{
"method": "the http method",
},
},
validate: validateMethod,
check: func(r *http.Request, method any) bool {
return r.Method == method.(string)
},
},
OnPath: {
help: Help{
command: OnPath,
description: `The path can be a glob pattern, e.g.:
/path/to
/path/to/*`,
args: map[string]string{
"path": "the request path, must start with /",
},
},
validate: validateURLPath,
check: func(r *http.Request, globPath any) bool {
reqPath := r.URL.Path
if len(reqPath) > 0 && reqPath[0] != '/' {
reqPath = "/" + reqPath
}
return strutils.GlobMatch(globPath.(string), reqPath)
},
},
OnRemote: {
help: Help{
command: OnRemote,
args: map[string]string{
"ip|cidr": "the remote ip or cidr",
},
},
validate: validateCIDR,
check: func(r *http.Request, cidr any) bool {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host = r.RemoteAddr
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
return cidr.(*net.IPNet).Contains(ip)
},
},
}
// Parse implements strutils.Parser.
func (on *RuleOn) Parse(v string) error {
on.raw = v
lines := strutils.SplitLine(v)
checks := make(Checkers, 0, len(lines))
errs := E.NewBuilder("rule.on syntax errors")
for i, line := range lines {
if line == "" {
continue
}
parsed, err := parseOn(line)
if err != nil {
errs.Add(err.Subjectf("line %d", i+1))
continue
}
checks = append(checks, parsed.matchOne())
}
on.check = checks.matchAll()
return errs.Error()
}
func (on *RuleOn) String() string {
return on.raw
}
func (on *RuleOn) MarshalJSON() ([]byte, error) {
return []byte("\"" + on.String() + "\""), nil
}
func parseOn(line string) (Checkers, E.Error) {
ors := strutils.SplitRune(line, '|')
if len(ors) > 1 {
errs := E.NewBuilder("rule.on syntax errors")
checks := make([]CheckFulfill, len(ors))
for i, or := range ors {
curCheckers, err := parseOn(or)
if err != nil {
errs.Add(err)
continue
}
checks[i] = curCheckers[0]
}
if err := errs.Error(); err != nil {
return nil, err
}
return checks, nil
}
subject, args, err := parse(line)
if err != nil {
return nil, err
}
checker, ok := checkers[subject]
if !ok {
return nil, ErrInvalidOnTarget.Subject(subject)
}
validArgs, err := checker.validate(args)
if err != nil {
return nil, err.Subject(subject).Withf("%s", checker.help.String())
}
return Checkers{
func(r *http.Request) bool {
return checker.check(r, validArgs)
},
}, nil
}
func (checkers Checkers) matchOne() CheckFulfill {
return func(r *http.Request) bool {
for _, checker := range checkers {
if checker(r) {
return true
}
}
return false
}
}
func (checkers Checkers) matchAll() CheckFulfill {
return func(r *http.Request) bool {
for _, checker := range checkers {
if !checker(r) {
return false
}
}
return true
}
}