github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/handlers/proxy_headers.go (about)

     1  package handlers
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  
     7  	http "github.com/hxx258456/ccgo/gmhttp"
     8  )
     9  
    10  var (
    11  	// De-facto standard header keys.
    12  	xForwardedFor    = http.CanonicalHeaderKey("X-Forwarded-For")
    13  	xForwardedHost   = http.CanonicalHeaderKey("X-Forwarded-Host")
    14  	xForwardedProto  = http.CanonicalHeaderKey("X-Forwarded-Proto")
    15  	xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
    16  	xRealIP          = http.CanonicalHeaderKey("X-Real-IP")
    17  )
    18  
    19  var (
    20  	// RFC7239 defines a new "Forwarded: " header designed to replace the
    21  	// existing use of X-Forwarded-* headers.
    22  	// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
    23  	forwarded = http.CanonicalHeaderKey("Forwarded")
    24  	// Allows for a sub-match of the first value after 'for=' to the next
    25  	// comma, semi-colon or space. The match is case-insensitive.
    26  	forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
    27  	// Allows for a sub-match for the first instance of scheme (http|https)
    28  	// prefixed by 'proto='. The match is case-insensitive.
    29  	protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
    30  )
    31  
    32  // ProxyHeaders inspects common reverse proxy headers and sets the corresponding
    33  // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
    34  // for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
    35  // for the scheme (http|https), X-Forwarded-Host for the host and the RFC7239
    36  // Forwarded header, which may include both client IPs and schemes.
    37  //
    38  // NOTE: This middleware should only be used when behind a reverse
    39  // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
    40  // configured not to) strip these headers from client requests, or where these
    41  // headers are accepted "as is" from a remote client (e.g. when Go is not behind
    42  // a proxy), can manifest as a vulnerability if your application uses these
    43  // headers for validating the 'trustworthiness' of a request.
    44  func ProxyHeaders(h http.Handler) http.Handler {
    45  	fn := func(w http.ResponseWriter, r *http.Request) {
    46  		// Set the remote IP with the value passed from the proxy.
    47  		if fwd := getIP(r); fwd != "" {
    48  			r.RemoteAddr = fwd
    49  		}
    50  
    51  		// Set the scheme (proto) with the value passed from the proxy.
    52  		if scheme := getScheme(r); scheme != "" {
    53  			r.URL.Scheme = scheme
    54  		}
    55  		// Set the host with the value passed by the proxy
    56  		if r.Header.Get(xForwardedHost) != "" {
    57  			r.Host = r.Header.Get(xForwardedHost)
    58  		}
    59  		// Call the next handler in the chain.
    60  		h.ServeHTTP(w, r)
    61  	}
    62  
    63  	return http.HandlerFunc(fn)
    64  }
    65  
    66  // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
    67  // Forwarded headers (in that order).
    68  func getIP(r *http.Request) string {
    69  	var addr string
    70  
    71  	if fwd := r.Header.Get(xForwardedFor); fwd != "" {
    72  		// Only grab the first (client) address. Note that '192.168.0.1,
    73  		// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
    74  		// the first may represent forwarding proxies earlier in the chain.
    75  		s := strings.Index(fwd, ", ")
    76  		if s == -1 {
    77  			s = len(fwd)
    78  		}
    79  		addr = fwd[:s]
    80  	} else if fwd := r.Header.Get(xRealIP); fwd != "" {
    81  		// X-Real-IP should only contain one IP address (the client making the
    82  		// request).
    83  		addr = fwd
    84  	} else if fwd := r.Header.Get(forwarded); fwd != "" {
    85  		// match should contain at least two elements if the protocol was
    86  		// specified in the Forwarded header. The first element will always be
    87  		// the 'for=' capture, which we ignore. In the case of multiple IP
    88  		// addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
    89  		// extract the first, which should be the client IP.
    90  		if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
    91  			// IPv6 addresses in Forwarded headers are quoted-strings. We strip
    92  			// these quotes.
    93  			addr = strings.Trim(match[1], `"`)
    94  		}
    95  	}
    96  
    97  	return addr
    98  }
    99  
   100  // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
   101  // Forwarded headers (in that order).
   102  func getScheme(r *http.Request) string {
   103  	var scheme string
   104  
   105  	// Retrieve the scheme from X-Forwarded-Proto.
   106  	if proto := r.Header.Get(xForwardedProto); proto != "" {
   107  		scheme = strings.ToLower(proto)
   108  	} else if proto = r.Header.Get(xForwardedScheme); proto != "" {
   109  		scheme = strings.ToLower(proto)
   110  	} else if proto = r.Header.Get(forwarded); proto != "" {
   111  		// match should contain at least two elements if the protocol was
   112  		// specified in the Forwarded header. The first element will always be
   113  		// the 'proto=' capture, which we ignore. In the case of multiple proto
   114  		// parameters (invalid) we only extract the first.
   115  		if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
   116  			scheme = strings.ToLower(match[1])
   117  		}
   118  	}
   119  
   120  	return scheme
   121  }