diff --git a/context.go b/context.go index c42459ff..7808edac 100644 --- a/context.go +++ b/context.go @@ -40,6 +40,130 @@ const ( MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML MIMEPROTOBUF = binding.MIMEPROTOBUF + + HeaderAuthorization string = "Authorization" + HeaderProxyAuthenticate string = "Proxy-Authenticate" + HeaderProxyAuthorization string = "Proxy-Authorization" + HeaderWWWAuthenticate string = "WWW-Authenticate" + HeaderAge string = "Age" + HeaderCacheControl string = "Cache-Control" + HeaderClearSiteData string = "Clear-Site-Data" + HeaderExpires string = "Expires" + HeaderPragma string = "Pragma" + HeaderWarning string = "Warning" + HeaderAcceptCH string = "Accept-CH" + HeaderAcceptCHLifetime string = "Accept-CH-Lifetime" + HeaderContentDPR string = "Content-DPR" + HeaderDPR string = "DPR" + HeaderEarlyData string = "Early-Data" + HeaderSaveData string = "Save-Data" + HeaderViewportWidth string = "Viewport-Width" + HeaderWidth string = "Width" + HeaderETag string = "ETag" + HeaderIfMatch string = "If-Match" + HeaderIfModifiedSince string = "If-Modified-Since" + HeaderIfNoneMatch string = "If-None-Match" + HeaderIfUnmodifiedSince string = "If-Unmodified-Since" + HeaderLastModified string = "Last-Modified" + HeaderVary string = "Vary" + HeaderConnection string = "Connection" + HeaderKeepAlive string = "Keep-Alive" + HeaderAccept string = "Accept" + HeaderAcceptCharset string = "Accept-Charset" + HeaderAcceptEncoding string = "Accept-Encoding" + HeaderAcceptLanguage string = "Accept-Language" + HeaderCookie string = "Cookie" + HeaderExpect string = "Expect" + HeaderMaxForwards string = "Max-Forwards" + HeaderSetCookie string = "Set-Cookie" + HeaderAccessControlAllowCredentials string = "Access-Control-Allow-Credentials" + HeaderAccessControlAllowHeaders string = "Access-Control-Allow-Headers" + HeaderAccessControlAllowMethods string = "Access-Control-Allow-Methods" + HeaderAccessControlAllowOrigin string = "Access-Control-Allow-Origin" + HeaderAccessControlExposeHeaders string = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge string = "Access-Control-Max-Age" + HeaderAccessControlRequestHeaders string = "Access-Control-Request-Headers" + HeaderAccessControlRequestMethod string = "Access-Control-Request-Method" + HeaderOrigin string = "Origin" + HeaderTimingAllowOrigin string = "Timing-Allow-Origin" + HeaderXPermittedCrossDomainPolicies string = "X-Permitted-Cross-Domain-Policies" + HeaderDNT string = "DNT" + HeaderTk string = "Tk" + HeaderContentDisposition string = "Content-Disposition" + HeaderContentEncoding string = "Content-Encoding" + HeaderContentLanguage string = "Content-Language" + HeaderContentLength string = "Content-Length" + HeaderContentLocation string = "Content-Location" + HeaderContentType string = "Content-Type" + HeaderForwarded string = "Forwarded" + HeaderVia string = "Via" + HeaderXForwardedFor string = "X-Forwarded-For" + HeaderXForwardedHost string = "X-Forwarded-Host" + HeaderXForwardedProto string = "X-Forwarded-Proto" + HeaderXForwardedProtocol string = "X-Forwarded-Protocol" + HeaderXForwardedSsl string = "X-Forwarded-Ssl" + HeaderXUrlScheme string = "X-Url-Scheme" + HeaderRealIp string = "X-Real-IP" + HeaderLocation string = "Location" + HeaderFrom string = "From" + HeaderHost string = "Host" + HeaderReferer string = "Referer" + HeaderReferrerPolicy string = "Referrer-Policy" + HeaderUserAgent string = "User-Agent" + HeaderAllow string = "Allow" + HeaderServer string = "Server" + HeaderAcceptRanges string = "Accept-Ranges" + HeaderContentRange string = "Content-Range" + HeaderIfRange string = "If-Range" + HeaderRange string = "Range" + HeaderContentSecurityPolicy string = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly string = "Content-Security-Policy-Report-Only" + HeaderCrossOriginResourcePolicy string = "Cross-Origin-Resource-Policy" + HeaderExpectCT string = "Expect-CT" + HeaderFeaturePolicy string = "Feature-Policy" + HeaderPublicKeyPins string = "Public-Key-Pins" + HeaderPublicKeyPinsReportOnly string = "Public-Key-Pins-Report-Only" + HeaderStrictTransportSecurity string = "Strict-Transport-Security" + HeaderUpgradeInsecureRequests string = "Upgrade-Insecure-Requests" + HeaderXContentTypeOptions string = "X-Content-Type-Options" + HeaderXDownloadOptions string = "X-Download-Options" + HeaderXFrameOptions string = "X-Frame-Options" + HeaderXPoweredBy string = "X-Powered-By" + HeaderXXSSProtection string = "X-XSS-Protection" + HeaderLastEventID string = "Last-Event-ID" + HeaderNEL string = "NEL" + HeaderPingFrom string = "Ping-From" + HeaderPingTo string = "Ping-To" + HeaderReportTo string = "Report-To" + HeaderTE string = "TE" + HeaderTrailer string = "Trailer" + HeaderTransferEncoding string = "Transfer-Encoding" + HeaderSecWebSocketAccept string = "Sec-WebSocket-Accept" + HeaderSecWebSocketExtensions string = "Sec-WebSocket-Extensions" + HeaderSecWebSocketKey string = "Sec-WebSocket-Key" + HeaderSecWebSocketProtocol string = "Sec-WebSocket-Protocol" + HeaderSecWebSocketVersion string = "Sec-WebSocket-Version" + HeaderAcceptPatch string = "Accept-Patch" + HeaderAcceptPushPolicy string = "Accept-Push-Policy" + HeaderAcceptSignature string = "Accept-Signature" + HeaderAltSvc string = "Alt-Svc" + HeaderDate string = "Date" + HeaderIndex string = "Index" + HeaderLargeAllocation string = "Large-Allocation" + HeaderLink string = "Link" + HeaderPushPolicy string = "Push-Policy" + HeaderRetryAfter string = "Retry-After" + HeaderServerTiming string = "Server-Timing" + HeaderSignature string = "Signature" + HeaderSignedHeaders string = "Signed-Headers" + HeaderSourceMap string = "SourceMap" + HeaderUpgrade string = "Upgrade" + HeaderXDNSPrefetchControl string = "X-DNS-Prefetch-Control" + HeaderXPingback string = "X-Pingback" + HeaderXRequestID string = "X-Request-ID" + HeaderXRequestedWith string = "X-Requested-With" + HeaderXRobotsTag string = "X-Robots-Tag" + HeaderXUACompatible string = "X-UA-Compatible" ) // BodyBytesKey indicates a default body bytes key. diff --git a/context_test.go b/context_test.go index 3080015c..2056f903 100644 --- a/context_test.go +++ b/context_test.go @@ -1531,6 +1531,22 @@ func TestContextHeaders(t *testing.T) { assert.False(t, exist) } +func TestContextHeadersConstants(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Header(HeaderContentType, "text/plain") + c.Header("X-Custom", "value") + + assert.Equal(t, "text/plain", c.Writer.Header().Get(HeaderContentType)) + assert.Equal(t, "value", c.Writer.Header().Get("X-Custom")) + + c.Header(HeaderContentType, "text/html") + c.Header("X-Custom", "") + + assert.Equal(t, "text/html", c.Writer.Header().Get(HeaderContentType)) + _, exist := c.Writer.Header()["X-Custom"] + assert.False(t, exist) +} + // TODO func TestContextRenderRedirectWithRelativePath(t *testing.T) { w := httptest.NewRecorder() @@ -2040,6 +2056,137 @@ func TestContextClientIP(t *testing.T) { assert.Empty(t, c.ClientIP()) } +func TestContextClientIPConstants(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) + c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() + resetContextForClientIPTests(c) + + // Legacy tests (validating that the defaults don't break the + // (insecure!) old behaviour) + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + c.Request.Header.Del(HeaderXForwardedFor) + assert.Equal(t, "10.10.10.10", c.ClientIP()) + + c.Request.Header.Set(HeaderXForwardedFor, "30.30.30.30 ") + assert.Equal(t, "30.30.30.30", c.ClientIP()) + + c.Request.Header.Del(HeaderXForwardedFor) + c.Request.Header.Del(HeaderRealIp) + c.engine.TrustedPlatform = PlatformGoogleAppEngine + assert.Equal(t, "50.50.50.50", c.ClientIP()) + + c.Request.Header.Del("X-Appengine-Remote-Addr") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // no port + c.Request.RemoteAddr = "50.50.50.50" + assert.Empty(t, c.ClientIP()) + + // Tests exercising the TrustedProxies functionality + resetContextForClientIPTests(c) + + // IPv6 support + c.Request.RemoteAddr = "[::1]:12345" + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + resetContextForClientIPTests(c) + // No trusted proxies + _ = c.engine.SetTrustedProxies([]string{}) + c.engine.RemoteIPHeaders = []string{HeaderXForwardedFor} + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Disabled TrustedProxies feature + _ = c.engine.SetTrustedProxies(nil) + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Last proxy is trusted, but the RemoteAddr is not + _ = c.engine.SetTrustedProxies([]string{"30.30.30.30"}) + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Only trust RemoteAddr + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) + assert.Equal(t, "30.30.30.30", c.ClientIP()) + + // All steps are trusted + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"}) + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // Use CIDR + _ = c.engine.SetTrustedProxies([]string{"40.40.25.25/16", "30.30.30.30"}) + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // Use hostname that resolves to all the proxies + _ = c.engine.SetTrustedProxies([]string{"foo"}) + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Use hostname that returns an error + _ = c.engine.SetTrustedProxies([]string{"bar"}) + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // X-Forwarded-For has a non-IP element + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) + c.Request.Header.Set("X-Forwarded-For", " blah ") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Result from LookupHost has non-IP element. This should never + // happen, but we should test it to make sure we handle it + // gracefully. + _ = c.engine.SetTrustedProxies([]string{"baz"}) + c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) + c.Request.Header.Del("X-Forwarded-For") + c.engine.RemoteIPHeaders = []string{HeaderXForwardedFor, HeaderRealIp} + assert.Equal(t, "10.10.10.10", c.ClientIP()) + + c.engine.RemoteIPHeaders = []string{} + c.engine.TrustedPlatform = PlatformGoogleAppEngine + assert.Equal(t, "50.50.50.50", c.ClientIP()) + + // Use custom TrustedPlatform header + c.engine.TrustedPlatform = "X-CDN-IP" + c.Request.Header.Set("X-CDN-IP", "80.80.80.80") + assert.Equal(t, "80.80.80.80", c.ClientIP()) + // wrong header + c.engine.TrustedPlatform = "X-Wrong-Header" + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.Request.Header.Del("X-CDN-IP") + // TrustedPlatform is empty + c.engine.TrustedPlatform = "" + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Test the legacy flag + c.engine.AppEngine = true + assert.Equal(t, "50.50.50.50", c.ClientIP()) + c.engine.AppEngine = false + c.engine.TrustedPlatform = PlatformGoogleAppEngine + + c.Request.Header.Del("X-Appengine-Remote-Addr") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.engine.TrustedPlatform = PlatformCloudflare + assert.Equal(t, "60.60.60.60", c.ClientIP()) + + c.Request.Header.Del("CF-Connecting-IP") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.engine.TrustedPlatform = PlatformFlyIO + assert.Equal(t, "70.70.70.70", c.ClientIP()) + + c.Request.Header.Del("Fly-Client-IP") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.engine.TrustedPlatform = "" + + // no port + c.Request.RemoteAddr = "50.50.50.50" + assert.Empty(t, c.ClientIP()) +} + func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") diff --git a/docs/doc.md b/docs/doc.md index 0dd86684..28344afc 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -34,6 +34,7 @@ - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) + - [Header Constants](#header-constants) - [SecureJSON](#securejson) - [JSONP](#jsonp) - [AsciiJSON](#asciijson) @@ -2431,6 +2432,141 @@ func main() { } ``` +### header-constants + +```go + +const ( + HeaderAuthorization string = "Authorization" + HeaderProxyAuthenticate string = "Proxy-Authenticate" + HeaderProxyAuthorization string = "Proxy-Authorization" + HeaderWWWAuthenticate string = "WWW-Authenticate" + HeaderAge string = "Age" + HeaderCacheControl string = "Cache-Control" + HeaderClearSiteData string = "Clear-Site-Data" + HeaderExpires string = "Expires" + HeaderPragma string = "Pragma" + HeaderWarning string = "Warning" + HeaderAcceptCH string = "Accept-CH" + HeaderAcceptCHLifetime string = "Accept-CH-Lifetime" + HeaderContentDPR string = "Content-DPR" + HeaderDPR string = "DPR" + HeaderEarlyData string = "Early-Data" + HeaderSaveData string = "Save-Data" + HeaderViewportWidth string = "Viewport-Width" + HeaderWidth string = "Width" + HeaderETag string = "ETag" + HeaderIfMatch string = "If-Match" + HeaderIfModifiedSince string = "If-Modified-Since" + HeaderIfNoneMatch string = "If-None-Match" + HeaderIfUnmodifiedSince string = "If-Unmodified-Since" + HeaderLastModified string = "Last-Modified" + HeaderVary string = "Vary" + HeaderConnection string = "Connection" + HeaderKeepAlive string = "Keep-Alive" + HeaderAccept string = "Accept" + HeaderAcceptCharset string = "Accept-Charset" + HeaderAcceptEncoding string = "Accept-Encoding" + HeaderAcceptLanguage string = "Accept-Language" + HeaderCookie string = "Cookie" + HeaderExpect string = "Expect" + HeaderMaxForwards string = "Max-Forwards" + HeaderSetCookie string = "Set-Cookie" + HeaderAccessControlAllowCredentials string = "Access-Control-Allow-Credentials" + HeaderAccessControlAllowHeaders string = "Access-Control-Allow-Headers" + HeaderAccessControlAllowMethods string = "Access-Control-Allow-Methods" + HeaderAccessControlAllowOrigin string = "Access-Control-Allow-Origin" + HeaderAccessControlExposeHeaders string = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge string = "Access-Control-Max-Age" + HeaderAccessControlRequestHeaders string = "Access-Control-Request-Headers" + HeaderAccessControlRequestMethod string = "Access-Control-Request-Method" + HeaderOrigin string = "Origin" + HeaderTimingAllowOrigin string = "Timing-Allow-Origin" + HeaderXPermittedCrossDomainPolicies string = "X-Permitted-Cross-Domain-Policies" + HeaderDNT string = "DNT" + HeaderTk string = "Tk" + HeaderContentDisposition string = "Content-Disposition" + HeaderContentEncoding string = "Content-Encoding" + HeaderContentLanguage string = "Content-Language" + HeaderContentLength string = "Content-Length" + HeaderContentLocation string = "Content-Location" + HeaderContentType string = "Content-Type" + HeaderForwarded string = "Forwarded" + HeaderVia string = "Via" + HeaderXForwardedFor string = "X-Forwarded-For" + HeaderXForwardedHost string = "X-Forwarded-Host" + HeaderXForwardedProto string = "X-Forwarded-Proto" + HeaderXForwardedProtocol string = "X-Forwarded-Protocol" + HeaderXForwardedSsl string = "X-Forwarded-Ssl" + HeaderXUrlScheme string = "X-Url-Scheme" + HeaderRealIp string = "X-Real-IP" + HeaderLocation string = "Location" + HeaderFrom string = "From" + HeaderHost string = "Host" + HeaderReferer string = "Referer" + HeaderReferrerPolicy string = "Referrer-Policy" + HeaderUserAgent string = "User-Agent" + HeaderAllow string = "Allow" + HeaderServer string = "Server" + HeaderAcceptRanges string = "Accept-Ranges" + HeaderContentRange string = "Content-Range" + HeaderIfRange string = "If-Range" + HeaderRange string = "Range" + HeaderContentSecurityPolicy string = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly string = "Content-Security-Policy-Report-Only" + HeaderCrossOriginResourcePolicy string = "Cross-Origin-Resource-Policy" + HeaderExpectCT string = "Expect-CT" + HeaderFeaturePolicy string = "Feature-Policy" + HeaderPublicKeyPins string = "Public-Key-Pins" + HeaderPublicKeyPinsReportOnly string = "Public-Key-Pins-Report-Only" + HeaderStrictTransportSecurity string = "Strict-Transport-Security" + HeaderUpgradeInsecureRequests string = "Upgrade-Insecure-Requests" + HeaderXContentTypeOptions string = "X-Content-Type-Options" + HeaderXDownloadOptions string = "X-Download-Options" + HeaderXFrameOptions string = "X-Frame-Options" + HeaderXPoweredBy string = "X-Powered-By" + HeaderXXSSProtection string = "X-XSS-Protection" + HeaderLastEventID string = "Last-Event-ID" + HeaderNEL string = "NEL" + HeaderPingFrom string = "Ping-From" + HeaderPingTo string = "Ping-To" + HeaderReportTo string = "Report-To" + HeaderTE string = "TE" + HeaderTrailer string = "Trailer" + HeaderTransferEncoding string = "Transfer-Encoding" + HeaderSecWebSocketAccept string = "Sec-WebSocket-Accept" + HeaderSecWebSocketExtensions string = "Sec-WebSocket-Extensions" + HeaderSecWebSocketKey string = "Sec-WebSocket-Key" + HeaderSecWebSocketProtocol string = "Sec-WebSocket-Protocol" + HeaderSecWebSocketVersion string = "Sec-WebSocket-Version" + HeaderAcceptPatch string = "Accept-Patch" + HeaderAcceptPushPolicy string = "Accept-Push-Policy" + HeaderAcceptSignature string = "Accept-Signature" + HeaderAltSvc string = "Alt-Svc" + HeaderDate string = "Date" + HeaderIndex string = "Index" + HeaderLargeAllocation string = "Large-Allocation" + HeaderLink string = "Link" + HeaderPushPolicy string = "Push-Policy" + HeaderRetryAfter string = "Retry-After" + HeaderServerTiming string = "Server-Timing" + HeaderSignature string = "Signature" + HeaderSignedHeaders string = "Signed-Headers" + HeaderSourceMap string = "SourceMap" + HeaderUpgrade string = "Upgrade" + HeaderXDNSPrefetchControl string = "X-DNS-Prefetch-Control" + HeaderXPingback string = "X-Pingback" + HeaderXRequestID string = "X-Request-ID" + HeaderXRequestedWith string = "X-Requested-With" + HeaderXRobotsTag string = "X-Robots-Tag" + HeaderXUACompatible string = "X-UA-Compatible" +) + +``` + + + + ## Don't trust all proxies Gin lets you specify which headers to hold the real client IP (if any),