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  }