mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
254 lines
5.1 KiB
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
|
|
}
|
|
}
|