github.com/yaling888/clash@v1.53.0/mitm/handler.go (about) 1 package mitm 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "io" 8 "net/http" 9 "net/textproto" 10 "strconv" 11 "strings" 12 13 C "github.com/yaling888/clash/constant" 14 "github.com/yaling888/clash/tunnel" 15 ) 16 17 var _ C.RewriteHandler = (*RewriteHandler)(nil) 18 19 type RewriteHandler struct{} 20 21 func (*RewriteHandler) HandleRequest(session *C.MitmSession) (*http.Request, *http.Response) { 22 var ( 23 request = session.Request 24 response *http.Response 25 ) 26 27 rule, sub, found := matchRewriteRule(request.URL.String(), true) 28 if !found { 29 return nil, nil 30 } 31 32 switch rule.RuleType() { 33 case C.MitmReject: 34 response = session.NewResponse(http.StatusNotFound, nil) 35 response.Header.Set("Content-Type", "text/html; charset=UTF-8") 36 case C.MitmReject200: 37 var payload string 38 if len(rule.RulePayload()) > 0 { 39 payload = rule.RulePayload()[0] 40 } 41 response = session.NewResponse(http.StatusOK, nil) 42 if payload != "" { 43 if strings.Contains(payload, "{") { 44 response.Header.Set("Content-Type", "application/json; charset=UTF-8") 45 } else { 46 response.Header.Set("Content-Type", "text/plain; charset=UTF-8") 47 } 48 response.Body = io.NopCloser(strings.NewReader(payload)) 49 response.ContentLength = int64(len(payload)) 50 } else { 51 response.Header.Set("Content-Type", "text/html; charset=UTF-8") 52 } 53 case C.MitmReject204: 54 response = session.NewResponse(http.StatusNoContent, nil) 55 response.Header.Set("Content-Type", "text/html; charset=UTF-8") 56 case C.MitmRejectImg: 57 response = session.NewResponse(http.StatusOK, OnePixelPNG.Body()) 58 response.Header.Set("Content-Type", "image/png") 59 response.ContentLength = OnePixelPNG.ContentLength() 60 case C.MitmRejectDict: 61 response = session.NewResponse(http.StatusOK, EmptyDict.Body()) 62 response.Header.Set("Content-Type", "application/json; charset=UTF-8") 63 response.ContentLength = EmptyDict.ContentLength() 64 case C.MitmRejectArray: 65 response = session.NewResponse(http.StatusOK, EmptyArray.Body()) 66 response.Header.Set("Content-Type", "application/json; charset=UTF-8") 67 response.ContentLength = EmptyArray.ContentLength() 68 case C.Mitm302: 69 response = session.NewResponse(http.StatusFound, nil) 70 response.Header.Set("Location", rule.ReplaceURLPayload(sub)) 71 case C.Mitm307: 72 response = session.NewResponse(http.StatusTemporaryRedirect, nil) 73 response.Header.Set("Location", rule.ReplaceURLPayload(sub)) 74 case C.MitmRequestHeader: 75 if len(request.Header) == 0 { 76 return nil, nil 77 } 78 79 rawHeader := &bytes.Buffer{} 80 oldHeader := request.Header 81 if err := oldHeader.Write(rawHeader); err != nil { 82 return nil, nil 83 } 84 85 newRawHeader, ok := rule.ReplaceSubPayload(rawHeader.String()) 86 if !ok { 87 return nil, nil 88 } 89 90 tb := textproto.NewReader(bufio.NewReader(strings.NewReader(newRawHeader))) 91 newHeader, err := tb.ReadMIMEHeader() 92 if err != nil && !errors.Is(err, io.EOF) { 93 return nil, nil 94 } 95 request.Header = http.Header(newHeader) 96 case C.MitmRequestBody: 97 if !CanRewriteBody(request.ContentLength, "", request.Header.Get("Content-Type")) { 98 return nil, nil 99 } 100 101 buf := make([]byte, request.ContentLength) 102 _, err := io.ReadFull(request.Body, buf) 103 if err != nil { 104 return nil, nil 105 } 106 107 newBody, _ := rule.ReplaceSubPayload(string(buf)) 108 request.Body = io.NopCloser(strings.NewReader(newBody)) 109 request.ContentLength = int64(len(newBody)) 110 default: 111 found = false 112 } 113 114 if found { 115 if response != nil { 116 response.Close = true 117 } 118 return request, response 119 } 120 return nil, nil 121 } 122 123 func (*RewriteHandler) HandleResponse(session *C.MitmSession) *http.Response { 124 var ( 125 request = session.Request 126 response = session.Response 127 ) 128 129 rule, _, found := matchRewriteRule(request.URL.String(), false) 130 found = found && rule.RuleRegx() != nil 131 if !found { 132 return nil 133 } 134 135 switch rule.RuleType() { 136 case C.MitmResponseHeader: 137 if len(response.Header) == 0 { 138 return nil 139 } 140 141 rawHeader := &bytes.Buffer{} 142 oldHeader := response.Header 143 if err := oldHeader.Write(rawHeader); err != nil { 144 return nil 145 } 146 147 newRawHeader, ok := rule.ReplaceSubPayload(rawHeader.String()) 148 if !ok { 149 return nil 150 } 151 152 tb := textproto.NewReader(bufio.NewReader(strings.NewReader(newRawHeader))) 153 newHeader, err := tb.ReadMIMEHeader() 154 if err != nil && !errors.Is(err, io.EOF) { 155 return nil 156 } 157 158 response.Header = http.Header(newHeader) 159 response.Header.Set("Content-Length", strconv.FormatInt(response.ContentLength, 10)) 160 case C.MitmResponseBody: 161 contentType := response.Header.Get("Content-Type") 162 if !CanRewriteBody(response.ContentLength, response.Header.Get("Content-Encoding"), contentType) { 163 return nil 164 } 165 166 b, err := C.ReadDecompressedBody(response) 167 _ = response.Body.Close() 168 if err != nil { 169 return nil 170 } 171 172 body := "" 173 isUTF8 := strings.HasSuffix(strings.ToUpper(contentType), "UTF-8") 174 if isUTF8 { 175 body = string(b) 176 } else { 177 body, err = C.DecodeLatin1(bytes.NewReader(b)) 178 if err != nil { 179 return nil 180 } 181 } 182 183 newBody, _ := rule.ReplaceSubPayload(body) 184 185 var modifiedBody []byte 186 if isUTF8 { 187 modifiedBody = []byte(newBody) 188 } else { 189 modifiedBody, err = C.EncodeLatin1(newBody) 190 if err != nil { 191 return nil 192 } 193 } 194 195 response.Body = io.NopCloser(bytes.NewReader(modifiedBody)) 196 response.ContentLength = int64(len(modifiedBody)) 197 response.Header.Del("Content-Encoding") 198 response.Header.Set("Content-Length", strconv.FormatInt(response.ContentLength, 10)) 199 default: 200 found = false 201 } 202 203 if found { 204 return response 205 } 206 return nil 207 } 208 209 func (h *RewriteHandler) HandleApiRequest(*C.MitmSession) bool { 210 return false 211 } 212 213 // HandleError session maybe nil 214 func (h *RewriteHandler) HandleError(*C.MitmSession, error) {} 215 216 func matchRewriteRule(url string, isRequest bool) (rr C.Rewrite, sub []string, found bool) { 217 rewrites := tunnel.Rewrites() 218 if isRequest { 219 found = rewrites.SearchInRequest(func(r C.Rewrite) bool { 220 // sub = r.URLRegx().FindStringSubmatch(url) // std 221 sub = findStringSubmatch(r.URLRegx(), url) 222 if len(sub) != 0 { 223 rr = r 224 return true 225 } 226 return false 227 }) 228 } else { 229 found = rewrites.SearchInResponse(func(r C.Rewrite) bool { 230 // if r.URLRegx().FindString(url) != "" { // std 231 if m, _ := r.URLRegx().MatchString(url); m { 232 rr = r 233 return true 234 } 235 return false 236 }) 237 } 238 239 return 240 }