s3api: ignore response header overrides on anonymous requests

AWS requires response-* query parameters to be signed; an anonymous
caller must not choose how a stored object is served.
This commit is contained in:
Chris Lu committed 2026-10-12 02:49:55 -07:00
1 parent 6bac506842
commit a4c8d31cb8
2 files changed
+31 -2

No files matched your search

+4 -2
View File
@@ -2430,8 +2430,10 @@ func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, r *http.Reques
// Apply S3 passthrough headers from query parameters
// AWS S3 supports overriding response headers via query parameters like:
// ?response-cache-control=no-cache&response-content-type=application/json
// This allows presigned URLs to control how browsers handle the downloaded content
if r != nil {
// This allows presigned URLs to control how browsers handle the downloaded content.
// AWS requires the request to be signed; anonymous requests must not influence
// how the stored object is served.
if r != nil && getRequestAuthType(r) != authTypeAnonymous {
for queryParam, headerValue := range r.URL.Query() {
if normalizedHeader, ok := s3_constants.PassThroughHeaders[strings.ToLower(queryParam)]; ok && len(headerValue) > 0 && headerValue[0] != "" {
w.Header().Set(normalizedHeader, headerValue[0])
@@ -238,3 +238,30 @@ func TestRemoteObjectETagFallback(t *testing.T) {
t.Errorf("local object ETag = %q", got)
}
}
func TestSetResponseHeadersIgnoresOverridesForAnonymous(t *testing.T) {
s3a := &S3ApiServer{}
entry := &filer_pb.Entry{
Name: "object",
Attributes: &filer_pb.FuseAttributes{FileSize: 8, Mtime: 100},
}
for name, url := range map[string]string{
"anonymous": "/bucket/object?response-content-type=text%2Fhtml&response-content-disposition=inline",
"signed": "/bucket/object?response-content-type=text%2Fhtml&response-content-disposition=inline",
"presigned": "/bucket/object?X-Amz-Credential=k&response-content-type=text%2Fhtml",
} {
r := httptest.NewRequest(http.MethodGet, url, nil)
if name == "signed" {
r.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=k")
}
w := httptest.NewRecorder()
s3a.setResponseHeaders(w, r, entry, 8)
if name == "anonymous" {
if got := w.Header().Get("Content-Disposition"); got != "" {
t.Errorf("anonymous request must not override response headers, got Content-Disposition %q", got)
}
} else if got := w.Header().Get("Content-Type"); got != "text/html" {
t.Errorf("%s request should apply response overrides, got Content-Type %q", name, got)
}
}
}