github.com/coreos/goproxy@v0.0.0-20190513173959-f8dc2d7ba04e/dispatcher.go (about) 1 package goproxy 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "net" 7 "net/http" 8 "regexp" 9 "strings" 10 ) 11 12 // ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request 13 // before sending it to the remote server 14 type ReqCondition interface { 15 RespCondition 16 HandleReq(req *http.Request, ctx *ProxyCtx) bool 17 } 18 19 // RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response 20 // before sending it to the proxy client. Note that resp might be nil, in case there was an 21 // error sending the request. 22 type RespCondition interface { 23 HandleResp(resp *http.Response, ctx *ProxyCtx) bool 24 } 25 26 // ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx) 27 type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool 28 29 // RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx) 30 type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool 31 32 func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool { 33 return c(req, ctx) 34 } 35 36 // ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that 37 // to be usable as RespCondition. 38 func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { 39 return c(ctx.Req, ctx) 40 } 41 42 func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { 43 return c(resp, ctx) 44 } 45 46 // UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested 47 // has the given prefix, with or without the host. 48 // For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match 49 // requests to url 'http://host/x' 50 func UrlHasPrefix(prefix string) ReqConditionFunc { 51 return func(req *http.Request, ctx *ProxyCtx) bool { 52 return strings.HasPrefix(req.URL.Path, prefix) || 53 strings.HasPrefix(req.URL.Host+req.URL.Path, prefix) || 54 strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix) 55 } 56 } 57 58 // UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings 59 // with or without the host prefix. 60 // UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to 61 // any host, and requests of the form 'GET foo'. 62 func UrlIs(urls ...string) ReqConditionFunc { 63 urlSet := make(map[string]bool) 64 for _, u := range urls { 65 urlSet[u] = true 66 } 67 return func(req *http.Request, ctx *ProxyCtx) bool { 68 _, pathOk := urlSet[req.URL.Path] 69 _, hostAndOk := urlSet[req.URL.Host+req.URL.Path] 70 return pathOk || hostAndOk 71 } 72 } 73 74 // ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches 75 // any of the given regular expressions. 76 func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc { 77 return func(req *http.Request, ctx *ProxyCtx) bool { 78 for _, re := range regexps { 79 if re.MatchString(req.Host) { 80 return true 81 } 82 } 83 return false 84 } 85 } 86 87 // ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal 88 // to one of the given strings 89 func ReqHostIs(hosts ...string) ReqConditionFunc { 90 hostSet := make(map[string]bool) 91 for _, h := range hosts { 92 hostSet[h] = true 93 } 94 return func(req *http.Request, ctx *ProxyCtx) bool { 95 _, ok := hostSet[req.URL.Host] 96 return ok 97 } 98 } 99 100 var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`) 101 102 // IsLocalHost checks whether the destination host is explicitly local host 103 // (buggy, there can be IPv6 addresses it doesn't catch) 104 var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool { 105 return req.URL.Host == "::1" || 106 req.URL.Host == "0:0:0:0:0:0:0:1" || 107 localHostIpv4.MatchString(req.URL.Host) || 108 req.URL.Host == "localhost" 109 } 110 111 // UrlMatches returns a ReqCondition testing whether the destination URL 112 // of the request matches the given regexp, with or without prefix 113 func UrlMatches(re *regexp.Regexp) ReqConditionFunc { 114 return func(req *http.Request, ctx *ProxyCtx) bool { 115 return re.MatchString(req.URL.Path) || 116 re.MatchString(req.URL.Host+req.URL.Path) 117 } 118 } 119 120 // DstHostIs returns a ReqCondition testing wether the host in the request url is the given string 121 func DstHostIs(host string) ReqConditionFunc { 122 return func(req *http.Request, ctx *ProxyCtx) bool { 123 return req.URL.Host == host 124 } 125 } 126 127 // SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings 128 func SrcIpIs(ips ...string) ReqCondition { 129 return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool { 130 for _, ip := range ips { 131 if strings.HasPrefix(req.RemoteAddr, ip+":") { 132 return true 133 } 134 } 135 return false 136 }) 137 } 138 139 // Not returns a ReqCondition negating the given ReqCondition 140 func Not(r ReqCondition) ReqConditionFunc { 141 return func(req *http.Request, ctx *ProxyCtx) bool { 142 return !r.HandleReq(req, ctx) 143 } 144 } 145 146 // ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal 147 // to one of the given strings. 148 func ContentTypeIs(typ string, types ...string) RespCondition { 149 types = append(types, typ) 150 return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { 151 if resp == nil { 152 return false 153 } 154 contentType := resp.Header.Get("Content-Type") 155 for _, typ := range types { 156 if contentType == typ || strings.HasPrefix(contentType, typ+";") { 157 return true 158 } 159 } 160 return false 161 }) 162 } 163 164 // ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. 165 // You will use the ReqProxyConds struct to register a ReqHandler, that would filter 166 // the request, only if all the given ReqCondition matched. 167 // Typical usage: 168 // proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) 169 func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { 170 return &ReqProxyConds{proxy, conds} 171 } 172 173 // ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would 174 // handle the request if all conditions on the HTTP request are met. 175 type ReqProxyConds struct { 176 proxy *ProxyHttpServer 177 reqConds []ReqCondition 178 } 179 180 // DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) 181 func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { 182 pcond.Do(FuncReqHandler(f)) 183 } 184 185 // ReqProxyConds.Do will register the ReqHandler on the proxy, 186 // the ReqHandler will handle the HTTP request if all the conditions 187 // aggregated in the ReqProxyConds are met. Typical usage: 188 // proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy 189 // proxy.OnRequest(cond1,cond2).Do(handler) 190 // // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true 191 // // if they are, will call handler.Handle(req,ctx) 192 func (pcond *ReqProxyConds) Do(h ReqHandler) { 193 pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, 194 FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { 195 for _, cond := range pcond.reqConds { 196 if !cond.HandleReq(r, ctx) { 197 return r, nil 198 } 199 } 200 return h.Handle(r, ctx) 201 })) 202 } 203 204 // HandleConnect is used when proxy receives an HTTP CONNECT request, 205 // it'll then use the HttpsHandler to determine what should it 206 // do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction 207 // struct returned will determine what to do with this request. ConnectAccept will simply accept the request 208 // forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the 209 // client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man 210 // in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped 211 // connection. 212 // The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy 213 // will use the default tls configuration. 214 // proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests 215 func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { 216 pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 217 FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 218 for _, cond := range pcond.reqConds { 219 if !cond.HandleReq(ctx.Req, ctx) { 220 return nil, "" 221 } 222 } 223 return h.HandleConnect(host, ctx) 224 })) 225 } 226 227 // HandleConnectFunc is equivalent to HandleConnect, 228 // for example, accepting CONNECT request if they contain a password in header 229 // io.WriteString(h,password) 230 // passHash := h.Sum(nil) 231 // proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 232 // c := sha1.New() 233 // io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) 234 // if c.Sum(nil) == passHash { 235 // return OkConnect, host 236 // } 237 // return RejectConnect, host 238 // }) 239 func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { 240 pcond.HandleConnect(FuncHttpsHandler(f)) 241 } 242 243 func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { 244 pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 245 FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 246 for _, cond := range pcond.reqConds { 247 if !cond.HandleReq(ctx.Req, ctx) { 248 return nil, "" 249 } 250 } 251 return &ConnectAction{Action: ConnectHijack, Hijack: f}, host 252 })) 253 } 254 255 // ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. 256 // Upon calling ProxyConds.Do, it will register a RespHandler that would 257 // handle the HTTP response from remote server if all conditions on the HTTP response are met. 258 type ProxyConds struct { 259 proxy *ProxyHttpServer 260 reqConds []ReqCondition 261 respCond []RespCondition 262 } 263 264 // ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) 265 func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { 266 pcond.Do(FuncRespHandler(f)) 267 } 268 269 // ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every 270 // request that matches the conditions aggregated in pcond. 271 func (pcond *ProxyConds) Do(h RespHandler) { 272 pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, 273 FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 274 for _, cond := range pcond.reqConds { 275 if !cond.HandleReq(ctx.Req, ctx) { 276 return resp 277 } 278 } 279 for _, cond := range pcond.respCond { 280 if !cond.HandleResp(resp, ctx) { 281 return resp 282 } 283 } 284 return h.Handle(resp, ctx) 285 })) 286 } 287 288 // OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is 289 // proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used 290 // // if cond1.HandleResp(resp) && cond2.HandleResp(resp) 291 func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { 292 return &ProxyConds{proxy, make([]ReqCondition, 0), conds} 293 } 294 295 // AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to 296 // eavesdrop all https connections to www.google.com, we can use 297 // proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) 298 var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 299 return MitmConnect, host 300 } 301 302 // AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow 303 // connections to hosts on any other port than 443 304 // proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). 305 // HandleConnect(goproxy.AlwaysReject) 306 var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 307 return RejectConnect, host 308 } 309 310 // HandleBytes will return a RespHandler that read the entire body of the request 311 // to a byte array in memory, would run the user supplied f function on the byte arra, 312 // and will replace the body of the original response with the resulting byte array. 313 func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { 314 return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 315 b, err := ioutil.ReadAll(resp.Body) 316 if err != nil { 317 ctx.Warnf("Cannot read response %s", err) 318 return resp 319 } 320 resp.Body.Close() 321 322 resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) 323 return resp 324 }) 325 }