diff --git a/fcors_anonymous_multiple_origins_test.go b/fcors_anonymous_multiple_origins_test.go index 40bc11b..7f3f004 100644 --- a/fcors_anonymous_multiple_origins_test.go +++ b/fcors_anonymous_multiple_origins_test.go @@ -793,238 +793,6 @@ func Test_AllowAccess_From_Multiple_Origins_And_Expose_Header_With_PrivateNetwor process(t, cors(dummyHandler), cases) } -func Test_AllowAccess_From_Multiple_Origins_And_AssumeNoWebCachingOfPreflightResponses(t *testing.T) { - const ( - dummyVaryValue = "whatever" - dummyStatusCode = 299 - ) - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - // remarkable Vary header value to make sure - // it isn't suppressed by the middleware - w.Header().Add(headerVary, dummyVaryValue) - w.WriteHeader(dummyStatusCode) - }) - const ( - dummyPreflightSuccessStatus = 279 - dummyMaxAge = 30 - exposedResponseHeader = "dummyResponseHeader" - ) - cors, err := fcors.AllowAccess( - fcors.FromOrigins("https://*.example.com"), - fcors.PreflightSuccessStatus(dummyPreflightSuccessStatus), - fcors.MaxAgeInSeconds(dummyMaxAge), - risky.AssumeNoWebCachingOfPreflightResponses(), - ) - if err != nil { - t.Errorf("got error with message %q; want nil error", err.Error()) - return - } - const ( - allowedOrigin = "https://foo.example.com" - disallowedBaseOrigin = "https://example.com" - ) - cases := []TestCase{ - { - name: "non-CORS GET request", - reqMethod: http.MethodGet, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "non-CORS OPTIONS request", - reqMethod: http.MethodOptions, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "CORS GET request from a valid and allowed origin", - reqMethod: http.MethodGet, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "CORS GET request from a valid but disallowed origin", - reqMethod: http.MethodGet, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "CORS GET request from an invalid origin", - reqMethod: http.MethodGet, - reqHeaders: http.Header{ - headerOrigin: []string{dummyInvalidOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "CORS GET request from an abnormally long and disallowed origin", - reqMethod: http.MethodGet, - reqHeaders: http.Header{ - headerOrigin: []string{abnormallyLongOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "non-preflight CORS OPTIONS request from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "non-preflight CORS OPTIONS request from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - }, - expectedStatus: dummyStatusCode, - expectedRespHeaders: http.Header{ - headerVary: []string{headerOrigin, dummyVaryValue}, - }, - }, { - name: "CORS preflight request with GET from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - headerACRM: []string{http.MethodGet}, - }, - expectedStatus: dummyPreflightSuccessStatus, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - headerACMA: []string{stringFromUint(dummyMaxAge)}, - }, - }, { - name: "CORS preflight request with GET from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - headerACRM: []string{http.MethodGet}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with GET from an invalid origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{dummyInvalidOrigin}, - headerACRM: []string{http.MethodGet}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with GET from an abnormally long and disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{abnormallyLongOrigin}, - headerACRM: []string{http.MethodGet}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with disallowed PUT from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - headerACRM: []string{http.MethodPut}, - }, - expectedStatus: dummyPreflightSuccessStatus, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - }, - }, { - name: "CORS preflight request with disallowed PUT from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - headerACRM: []string{http.MethodPut}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with GET with non-safelisted header names from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - headerACRM: []string{http.MethodGet}, - headerACRH: []string{"foo,bar,baz"}, - }, - expectedStatus: dummyPreflightSuccessStatus, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - }, - }, { - name: "CORS preflight request with GET with non-safelisted header names from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - headerACRM: []string{http.MethodGet}, - headerACRH: []string{"foo,bar,baz"}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with GET with ACRPN from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - headerACRM: []string{http.MethodGet}, - headerACRPN: []string{headerValueTrue}, - }, - expectedStatus: dummyPreflightSuccessStatus, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - }, - }, { - name: "CORS preflight request with PUT with non-safelisted header names with ACRPN from a valid and allowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{allowedOrigin}, - headerACRM: []string{http.MethodPut}, - headerACRH: []string{"foo,bar,baz"}, - headerACRPN: []string{headerValueTrue}, - }, - expectedStatus: dummyPreflightSuccessStatus, - expectedRespHeaders: http.Header{ - headerACAO: []string{allowedOrigin}, - }, - }, { - name: "CORS preflight request with GET with ACRPN from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - headerACRM: []string{http.MethodGet}, - headerACRPN: []string{headerValueTrue}, - }, - expectedStatus: http.StatusForbidden, - }, { - name: "CORS preflight request with PUT with non-safelisted header names with ACRPN from a valid but disallowed origin", - reqMethod: http.MethodOptions, - reqHeaders: http.Header{ - headerOrigin: []string{disallowedBaseOrigin}, - headerACRM: []string{http.MethodPut}, - headerACRH: []string{"foo,bar,baz"}, - headerACRPN: []string{headerValueTrue}, - }, - expectedStatus: http.StatusForbidden, - }, - } - process(t, cors(dummyHandler), cases) -} - func Test_AllowAccess_From_Subdomains_Of_Public_Suffix(t *testing.T) { const ( dummyVaryValue = "whatever" diff --git a/fcors_invalid_policies_test.go b/fcors_invalid_policies_test.go index bf03363..97bf895 100644 --- a/fcors_invalid_policies_test.go +++ b/fcors_invalid_policies_test.go @@ -430,14 +430,6 @@ func TestInvalidPoliciesForAllowAccess(t *testing.T) { fcors.PreflightSuccessStatus(202), }, errorMsg: `fcors: option PreflightSuccessStatus used multiple times`, - }, { - desc: "option AssumeNoWebCachingOfPreflightResponses used multiple times", - options: []fcors.OptionAnon{ - fcors.FromOrigins("https://example.com"), - risky.AssumeNoWebCachingOfPreflightResponses(), - risky.AssumeNoWebCachingOfPreflightResponses(), - }, - errorMsg: `fcors/risky: option AssumeNoWebCachingOfPreflightResponses used multiple times`, }, { desc: "option PrivateNetworkAccess used multiple times", options: []fcors.OptionAnon{ @@ -989,14 +981,6 @@ func TestInvalidPoliciesForAllowAccessWithCredentials(t *testing.T) { fcors.PreflightSuccessStatus(202), }, errorMsg: `fcors: option PreflightSuccessStatus used multiple times`, - }, { - desc: "option AssumeNoWebCachingOfPreflightResponses used multiple times", - options: []fcors.Option{ - fcors.FromOrigins("https://example.com"), - risky.AssumeNoWebCachingOfPreflightResponses(), - risky.AssumeNoWebCachingOfPreflightResponses(), - }, - errorMsg: `fcors/risky: option AssumeNoWebCachingOfPreflightResponses used multiple times`, }, { desc: "option PrivateNetworkAccess used multiple times", options: []fcors.Option{ diff --git a/internal/middleware.go b/internal/middleware.go index 924a7fd..dd10bf9 100644 --- a/internal/middleware.go +++ b/internal/middleware.go @@ -75,22 +75,21 @@ type TempConfig struct { type Config struct { // A nil ACAO indicates that the corresponding header // is set dynamically. - ACAO []string - ACAM []string - Corpus origin.Corpus - tmp *TempConfig - ACAH []string - ACMA []string - PreflightSuccessStatus int - AllowAnyMethod bool - AllowAnyRequestHeaders bool - AllowAnyOrigin bool - AllowCredentials bool - ExposeAllResponseHeaders bool - PrivateNetworkAccess bool - PrivateNetworkAccessInNoCORSModeOnly bool - AssumeNoWebCachingOfPreflightResponses bool - ACEH []string + ACAO []string + ACAM []string + Corpus origin.Corpus + tmp *TempConfig + ACAH []string + ACMA []string + PreflightSuccessStatus int + AllowAnyMethod bool + AllowAnyRequestHeaders bool + AllowAnyOrigin bool + AllowCredentials bool + ExposeAllResponseHeaders bool + PrivateNetworkAccess bool + PrivateNetworkAccessInNoCORSModeOnly bool + ACEH []string //lint:ignore U1000 because we pad to the end of the 3rd cache line _padding40 [40]bool } @@ -265,13 +264,13 @@ func (cfg *Config) middleware() Middleware { func (cfg *Config) handleNonCORSRequest(respHeaders http.Header, isOptionsReq bool) { // see https://wicg.github.io/private-network-access/#shortlinks if cfg.PrivateNetworkAccessInNoCORSModeOnly { - if isOptionsReq && !cfg.AssumeNoWebCachingOfPreflightResponses { + if isOptionsReq { fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) } return } var varyHeaderAdded bool - if isOptionsReq && !cfg.AssumeNoWebCachingOfPreflightResponses { + if isOptionsReq { fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) varyHeaderAdded = true } @@ -304,9 +303,7 @@ func (cfg *Config) handleCORSPreflightRequest( acrm []string, // assumed non-empty ) { respHeaders := w.Header() - if !cfg.AssumeNoWebCachingOfPreflightResponses { - fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) - } + fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) if !cfg.processOriginForPreflight(respHeaders, origins) { w.WriteHeader(http.StatusForbidden) return @@ -392,13 +389,13 @@ func (cfg *Config) handleNonPreflightCORSRequest( respHeaders := w.Header() // see https://wicg.github.io/private-network-access/#shortlinks if cfg.PrivateNetworkAccessInNoCORSModeOnly { - if isOptionsReq && !cfg.AssumeNoWebCachingOfPreflightResponses { + if isOptionsReq { fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) } return } switch { - case isOptionsReq && !cfg.AssumeNoWebCachingOfPreflightResponses: + case isOptionsReq: fastAdd(respHeaders, headerVary, precomputedPreflightVaryValue) case cfg.ACAO == nil: fastAdd(respHeaders, headerVary, precomputedHeaderOrigin) diff --git a/internal/options.go b/internal/options.go index 75fcf03..d8f0819 100644 --- a/internal/options.go +++ b/internal/options.go @@ -10,21 +10,20 @@ import ( ) const ( - optANWCOPR = "AssumeNoWebCachingOfPreflightResponses" - optEARH = "ExposeAllResponseHeaders" - optERH = "ExposeResponseHeaders" - optFAO = "FromAnyOrigin" - optFO = "FromOrigins" - optPNA = "PrivateNetworkAccess" - optPNANC = "PrivateNetworkAccessInNoCORSModeOnly" - optTIO = "TolerateInsecureOrigins" - optSPSC = "SkipPublicSuffixCheck" - optWAM = "WithAnyMethod" - optWARH = "WithAnyRequestHeaders" - optWM = "WithMethods" - optWMAIS = "MaxAgeInSeconds" - optWPSS = "PreflightSuccessStatus" - optWRH = "WithRequestHeaders" + optEARH = "ExposeAllResponseHeaders" + optERH = "ExposeResponseHeaders" + optFAO = "FromAnyOrigin" + optFO = "FromOrigins" + optPNA = "PrivateNetworkAccess" + optPNANC = "PrivateNetworkAccessInNoCORSModeOnly" + optTIO = "TolerateInsecureOrigins" + optSPSC = "SkipPublicSuffixCheck" + optWAM = "WithAnyMethod" + optWARH = "WithAnyRequestHeaders" + optWM = "WithMethods" + optWMAIS = "MaxAgeInSeconds" + optWPSS = "PreflightSuccessStatus" + optWRH = "WithRequestHeaders" ) type Option interface { @@ -383,17 +382,6 @@ func PreflightSuccessStatus(status uint) Option { return option(f) } -func AssumeNoWebCachingOfPreflightResponses() Option { - f := func(cfg *Config) error { - if cfg.AssumeNoWebCachingOfPreflightResponses { - return util.NewErrorRisky("option " + optANWCOPR + " used multiple times") - } - cfg.AssumeNoWebCachingOfPreflightResponses = true - return nil - } - return option(f) -} - func PrivateNetworkAccess() Option { // blanket policy that applies to all origins // see https://github.com/WICG/private-network-access/issues/84 diff --git a/risky/risky.go b/risky/risky.go index 1f0d01b..d96b114 100644 --- a/risky/risky.go +++ b/risky/risky.go @@ -51,30 +51,6 @@ func PrivateNetworkAccessInNoCORSModeOnly() fcors.Option { return internal.PrivateNetworkAccessInNoCORSModeOnly() } -// AssumeNoWebCachingOfPreflightResponses configures a CORS middleware -// to eschew the use of the [Vary header] in preflight responses. -// Responses to OPTIONS requests are [not meant to be cached] but, -// for better or worse, some caching intermediaries can nevertheless be -// configured to cache such responses. -// To avoid poisoning such caches with inadequate preflight responses, -// [github.com/jub0bs/fcors] by default lists the following header names -// in the Vary header of preflight responses: -// -// - Access-Control-Request-Headers -// - Access-Control-Request-Methods -// - Access-Control-Request-Private-Network -// - Origin -// -// Use this option if you are absolutely sure that no caching intermediaries -// cache your responses to OPTIONS requests and you want to minimize the size -// of preflight responses. -// -// [Vary header]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary -// [not meant to be cached]: https://www.rfc-editor.org/rfc/rfc7231#section-4.3.7 -func AssumeNoWebCachingOfPreflightResponses() fcors.Option { - return internal.AssumeNoWebCachingOfPreflightResponses() -} - // TolerateInsecureOrigins enables you to tolerate insecure origins // (i.e. origins whose scheme is http), // which option [github.com/jub0bs/fcors.FromOrigins] by default prohibits