Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions internal/handler/cors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
"net/http/httptest"
"testing"
"testing/fstest"
)

func TestParseCORSOrigins(t *testing.T) {
Expand Down Expand Up @@ -167,6 +168,41 @@ func TestCORSMiddlewareVaryOriginForDisallowedOrigin(t *testing.T) {
}
}

func TestCORSMiddlewareVaryPreservedThroughStaticHandler(t *testing.T) {
// Regression test: serveFromCache used to call w.Header().Set("Vary",
// "Accept-Encoding") which silently dropped the "Origin" entry that
// CORSMiddleware had already added. After the fix (Set → Add) both
// values must appear in the response Vary header for an allowed origin
// requesting a static file.
const index = "<!doctype html><title>test</title>"
prev := StaticFS
StaticFS = fstest.MapFS{
"index.html": &fstest.MapFile{Data: []byte(index)},
}
defer func() { StaticFS = prev }()

staticHandler := NewStaticHandler()
h := CORSMiddleware(ParseCORSOrigins("https://ui.example.com"), staticHandler)

req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Origin", "https://ui.example.com")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200 from static handler, got %d", rec.Code)
}
vary := rec.Header().Values("Vary")
for _, want := range []string{"Origin", "Accept-Encoding"} {
if !containsStr(vary, want) {
t.Fatalf("static handler response Vary=%v missing %q — CORSMiddleware Vary was overwritten", vary, want)
}
}
if got := rec.Header().Get("Access-Control-Allow-Origin"); got != "https://ui.example.com" {
t.Fatalf("Allow-Origin=%q, want reflected origin", got)
}
}

func containsStr(s []string, want string) bool {
for _, v := range s {
if v == want {
Expand Down
8 changes: 5 additions & 3 deletions internal/handler/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,14 @@ func serveFromCache(w http.ResponseWriter, r *http.Request, cached *staticFileCa
// Set content type
w.Header().Set("Content-Type", cached.contentType)

// Always set Vary header to ensure caches differentiate by Accept-Encoding
w.Header().Set("Vary", "Accept-Encoding")
// Add Accept-Encoding to Vary so caches differentiate by encoding. Use Add
// (not Set) to preserve any Vary values already written by upstream
// middleware (e.g. "Origin" from CORSMiddleware).
w.Header().Add("Vary", "Accept-Encoding")

// Check if client accepts gzip and we have gzipped content
if cached.gzipped != nil && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")

w.Header().Set("Content-Length", fmt.Sprintf("%d", len(cached.gzipped)))
w.WriteHeader(http.StatusOK)
w.Write(cached.gzipped)
Expand Down