本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
4.5KB

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