@@ -626,3 +626,83 @@ func Test_allowOriginFunc(t *testing.T) {
626626 }
627627 }
628628}
629+
630+ // TestCORSProxyChain tests that CORS headers are not duplicated when
631+ // multiple CORS middlewares are chained in a proxy scenario
632+ func TestCORSProxyChain (t * testing.T ) {
633+ e := echo .New ()
634+ req := httptest .NewRequest (http .MethodGet , "/" , nil )
635+ req .Header .Set (echo .HeaderOrigin , "http://example.com" )
636+ rec := httptest .NewRecorder ()
637+ c := e .NewContext (req , rec )
638+
639+ // Simulate Service B (upstream) that already set CORS headers
640+ upstreamHandler := func (c * echo.Context ) error {
641+ c .Response ().Header ().Set (echo .HeaderAccessControlAllowOrigin , "http://example.com" )
642+ c .Response ().Header ().Set (echo .HeaderVary , "Origin" )
643+ return c .String (http .StatusOK , "test" )
644+ }
645+
646+ // Apply CORS middleware on Service A (proxy layer)
647+ mw := CORS ("*" )
648+ handler := mw (upstreamHandler )
649+
650+ err := handler (c )
651+ assert .NoError (t , err )
652+ assert .Equal (t , http .StatusOK , rec .Code )
653+
654+ // Verify headers are not duplicated
655+ assert .Equal (t , "http://example.com" , rec .Header ().Get (echo .HeaderAccessControlAllowOrigin ))
656+
657+ // Check that Vary header contains "Origin" only once
658+ varyHeader := rec .Header ().Get (echo .HeaderVary )
659+ assert .Contains (t , varyHeader , "Origin" )
660+
661+ // Count occurrences of "Origin" in Vary header - should be exactly 1
662+ originCount := strings .Count (varyHeader , "Origin" )
663+ assert .Equal (t , 1 , originCount , "Vary header should contain 'Origin' only once, got: %s" , varyHeader )
664+
665+ // Verify there's no duplicate Access-Control-Allow-Origin header
666+ allowOriginHeaders := rec .Header ().Values (echo .HeaderAccessControlAllowOrigin )
667+ assert .Equal (t , 1 , len (allowOriginHeaders ), "Should have exactly one Access-Control-Allow-Origin header" )
668+ }
669+
670+ // TestCORSProxyChainPreflight tests preflight requests in proxy chains
671+ func TestCORSProxyChainPreflight (t * testing.T ) {
672+ e := echo .New ()
673+ req := httptest .NewRequest (http .MethodOptions , "/" , nil )
674+ req .Header .Set (echo .HeaderOrigin , "http://example.com" )
675+ req .Header .Set (echo .HeaderAccessControlRequestMethod , "POST" )
676+ rec := httptest .NewRecorder ()
677+ c := e .NewContext (req , rec )
678+
679+ // Simulate Service B (upstream) that already set CORS headers
680+ upstreamHandler := func (c * echo.Context ) error {
681+ c .Response ().Header ().Set (echo .HeaderAccessControlAllowOrigin , "*" )
682+ c .Response ().Header ().Set (echo .HeaderVary , "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" )
683+ c .Response ().Header ().Set (echo .HeaderAccessControlAllowMethods , "GET,POST,PUT" )
684+ return c .NoContent (http .StatusNoContent )
685+ }
686+
687+ // Apply CORS middleware on Service A (proxy layer)
688+ mw := CORS ("*" )
689+ handler := mw (upstreamHandler )
690+
691+ err := handler (c )
692+ assert .NoError (t , err )
693+ assert .Equal (t , http .StatusNoContent , rec .Code )
694+
695+ // Verify headers are not duplicated
696+ assert .Equal (t , "*" , rec .Header ().Get (echo .HeaderAccessControlAllowOrigin ))
697+
698+ // Check that Vary header contains each value only once
699+ varyHeader := rec .Header ().Get (echo .HeaderVary )
700+ assert .Contains (t , varyHeader , "Origin" )
701+
702+ originCount := strings .Count (varyHeader , "Origin" )
703+ assert .Equal (t , 1 , originCount , "Vary header should contain 'Origin' only once, got: %s" , varyHeader )
704+
705+ // Verify there's no duplicate Access-Control-Allow-Origin header
706+ allowOriginHeaders := rec .Header ().Values (echo .HeaderAccessControlAllowOrigin )
707+ assert .Equal (t , 1 , len (allowOriginHeaders ), "Should have exactly one Access-Control-Allow-Origin header" )
708+ }
0 commit comments