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 }