diff --git a/config/config.go b/config/config.go index 8b17beb0..36853a1c 100644 --- a/config/config.go +++ b/config/config.go @@ -263,8 +263,8 @@ type RawTuicServer struct { } type RawMitm struct { - Port int `yaml:"port" json:"port"` - Rules []string `yaml:"rules" json:"rules"` + Port int `yaml:"port" json:"port"` + Rules []rewrites.RawMitmRule `yaml:"rules" json:"rules"` } type RawConfig struct { @@ -456,7 +456,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { }, MITM: RawMitm{ Port: 0, - Rules: []string{}, + Rules: []rewrites.RawMitmRule{}, }, Profile: Profile{ StoreSelected: true, diff --git a/constant/metadata.go b/constant/metadata.go index daaac62c..67437a25 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -162,6 +162,8 @@ func (m *Metadata) SourceAddress() string { func (m *Metadata) SourceDetail() string { if m.Type == INNER { return fmt.Sprintf("%s", ClashName) + } else if m.Type == MITM { + return fmt.Sprintf("%s-MITM", ClashName) } switch { diff --git a/constant/rewrite.go b/constant/rewrite.go index 4caf0d8c..7ec7df40 100644 --- a/constant/rewrite.go +++ b/constant/rewrite.go @@ -1,6 +1,8 @@ package constant import ( + "encoding/json" + "errors" regexp "github.com/dlclark/regexp2" ) @@ -37,6 +39,42 @@ const ( type RewriteType int +// UnmarshalYAML unserialize RewriteType with yaml +func (e *RewriteType) UnmarshalYAML(unmarshal func(any) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + mode, exist := RewriteTypeMapping[tp] + if !exist { + return errors.New("invalid MITM Action") + } + *e = mode + return nil +} + +// MarshalYAML serialize RewriteType with yaml +func (e RewriteType) MarshalYAML() (any, error) { + return e.String(), nil +} + +// UnmarshalJSON unserialize RewriteType with json +func (e *RewriteType) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, &tp) + mode, exist := RewriteTypeMapping[tp] + if !exist { + return errors.New("invalid MITM Action") + } + *e = mode + return nil +} + +// MarshalJSON serialize RewriteType with json +func (e RewriteType) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + func (rt RewriteType) String() string { switch rt { case MitmReject: diff --git a/rewrite/handler.go b/rewrite/handler.go index 2912a1d1..aa611751 100644 --- a/rewrite/handler.go +++ b/rewrite/handler.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "errors" + log "github.com/sirupsen/logrus" "io" "io/ioutil" "net/http" @@ -31,6 +32,8 @@ func (*RewriteHandler) HandleRequest(session *mitm.Session) (*http.Request, *htt return nil, nil } + log.Infof("[MITM] %s <- request %s", rule.RuleType().String(), request.URL.String()) + switch rule.RuleType() { case C.MitmReject: response = session.NewResponse(http.StatusNotFound, nil) @@ -113,6 +116,8 @@ func (*RewriteHandler) HandleResponse(session *mitm.Session) *http.Response { return nil } + log.Infof("[MITM] %s <- response %s", rule.RuleType().String(), request.URL.String()) + switch rule.RuleType() { case C.MitmResponseHeader: if len(response.Header) == 0 { @@ -182,7 +187,7 @@ func matchRewriteRule(url string, isRequest bool) (rr C.Rewrite, sub []string, f if isRequest { found = rewrites.SearchInRequest(func(r C.Rewrite) bool { sub, err := r.URLRegx().FindStringMatch(url) - if err != nil { + if err != nil || sub == nil { return false } diff --git a/rewrite/parser.go b/rewrite/parser.go index 09cf0396..a170deb2 100644 --- a/rewrite/parser.go +++ b/rewrite/parser.go @@ -7,12 +7,7 @@ import ( C "github.com/Dreamacro/clash/constant" ) -func ParseRewrite(line string) (C.Rewrite, error) { - url, others, found := strings.Cut(strings.TrimSpace(line), "url") - if !found { - return nil, errInvalid - } - +func ParseRewrite(line RawMitmRule) (C.Rewrite, error) { var ( urlRegx *regexp.Regexp ruleType *C.RewriteType @@ -22,57 +17,37 @@ func ParseRewrite(line string) (C.Rewrite, error) { err error ) + url := line.Url urlRegx, err = regexp.Compile(strings.Trim(url, " "), regexp.None) if err != nil { return nil, err } - others = strings.Trim(others, " ") - first := strings.Split(others, " ")[0] - for k, v := range C.RewriteTypeMapping { - if k == others { - ruleType = &v + ruleType = &line.Action + switch *ruleType { + case C.Mitm302, C.Mitm307: + { + rulePayload = line.New break } + case C.MitmRequestHeader, C.MitmRequestBody, C.MitmResponseHeader, C.MitmResponseBody: + { + var old string + if line.Old == nil { + old = ".*" + } else { + old = *line.Old + } - if k != first { - continue - } - - rs := trimArr(strings.Split(others, k)) - l := len(rs) - if l > 2 { - continue - } - - if l == 1 { - ruleType = &v - rulePayload = rs[0] - break - } else { - ruleRegx, err = regexp.Compile(rs[0], regexp.None) + re, err := regexp.Compile(old, regexp.Singleline) if err != nil { return nil, err } + ruleRegx = re - ruleType = &v - rulePayload = rs[1] - break + rulePayload = line.New } } - if ruleType == nil { - return nil, errInvalid - } - return NewRewriteRule(urlRegx, *ruleType, ruleRegx, rulePayload), nil } - -func trimArr(arr []string) (r []string) { - for _, e := range arr { - if s := strings.Trim(e, " "); s != "" { - r = append(r, s) - } - } - return -} diff --git a/rewrite/parser_test.go b/rewrite/parser_test.go deleted file mode 100644 index 58d1149a..00000000 --- a/rewrite/parser_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package rewrites - -import ( - "bytes" - "fmt" - "image" - "image/color" - "image/draw" - "image/png" - "regexp" - "testing" - - "github.com/Dreamacro/clash/constant" - - "github.com/stretchr/testify/assert" -) - -func TestParseRewrite(t *testing.T) { - line0 := `^https?://example\.com/resource1/3/ url reject-dict` - line1 := `^https?://example\.com/(resource2)/ url 307 https://example.com/new-$1` - line2 := `^https?://example\.com/resource4/ url request-header (\r\n)User-Agent:.+(\r\n) request-header $1User-Agent: Fuck-Who$2` - line3 := `should be error` - - c0, err0 := ParseRewrite(line0) - c1, err1 := ParseRewrite(line1) - c2, err2 := ParseRewrite(line2) - _, err3 := ParseRewrite(line3) - - assert.NotNil(t, err3) - - assert.Nil(t, err0) - assert.Equal(t, c0.RuleType(), constant.MitmRejectDict) - - assert.Nil(t, err1) - assert.Equal(t, c1.RuleType(), constant.Mitm307) - assert.Equal(t, c1.URLRegx(), regexp.MustCompile(`^https?://example\.com/(resource2)/`)) - assert.Equal(t, c1.RulePayload(), "https://example.com/new-$1") - - assert.Nil(t, err2) - assert.Equal(t, c2.RuleType(), constant.MitmRequestHeader) - assert.Equal(t, c2.RuleRegx(), regexp.MustCompile(`(\r\n)User-Agent:.+(\r\n)`)) - assert.Equal(t, c2.RulePayload(), "$1User-Agent: Fuck-Who$2") -} - -func Test1PxPNG(t *testing.T) { - m := image.NewRGBA(image.Rect(0, 0, 1, 1)) - - draw.Draw(m, m.Bounds(), &image.Uniform{C: color.Transparent}, image.Point{}, draw.Src) - - buf := &bytes.Buffer{} - - assert.Nil(t, png.Encode(buf, m)) - - fmt.Printf("len: %d\n", buf.Len()) - fmt.Printf("% #x\n", buf.Bytes()) -} diff --git a/rewrite/rewrite.go b/rewrite/rewrite.go index b53870ee..1128f23a 100644 --- a/rewrite/rewrite.go +++ b/rewrite/rewrite.go @@ -1,7 +1,6 @@ package rewrites import ( - "errors" regexp "github.com/dlclark/regexp2" "strconv" "strings" @@ -11,7 +10,12 @@ import ( "github.com/gofrs/uuid" ) -var errInvalid = errors.New("invalid rewrite rule") +type RawMitmRule struct { + Url string `yaml:"url" json:"url"` + Action C.RewriteType `yaml:"action" json:"action"` + Old *string `yaml:"old" json:"old"` + New string `yaml:"new" json:"new"` +} type RewriteRule struct { id string diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 34116e76..aab6557f 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -561,6 +561,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { } for _, rule := range getRules(metadata) { + if metadata.Type == C.MITM && rule.Adapter() == "MITM" { + continue + } + if !resolved && shouldResolveIP(rule, metadata) { func() { ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)