Browse Source

s3api: Fix response-content-disposition query parameter not being honored

Fixes #7486

This fix resolves an issue where S3 presigned URLs with query parameters
like `response-content-disposition`, `response-content-type`, etc. were
being ignored, causing browsers to use default file handling instead of
the specified behavior.

Changes:
- Modified `setResponseHeaders()` to accept the HTTP request object
- Added logic to process S3 passthrough headers from query parameters
- Updated all call sites to pass the request object
- Supports all AWS S3 response override parameters:
  - response-content-disposition
  - response-content-type
  - response-cache-control
  - response-content-encoding
  - response-content-language
  - response-expires

The implementation follows the same pattern used in the filer handler
and properly honors the AWS S3 API specification for presigned URLs.

Testing:
- Existing S3 API tests pass without modification
- Build succeeds with no compilation errors
pull/7559/head
Chris Lu 7 days ago
parent
commit
a7a261dbad
  1. 26
      weed/s3api/s3api_object_handlers.go

26
weed/s3api/s3api_object_handlers.go

@ -880,7 +880,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
return newStreamErrorWithResponse(fmt.Errorf("invalid range for inline content: start=%d, end=%d, len=%d", start, end, len(entry.Content)))
}
// Validation passed - now set headers and write
s3a.setResponseHeaders(w, entry, totalSize)
s3a.setResponseHeaders(w, r, entry, totalSize)
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, offset+size-1, totalSize))
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
w.WriteHeader(http.StatusPartialContent)
@ -888,7 +888,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
return err
}
// Non-range request for inline content
s3a.setResponseHeaders(w, entry, totalSize)
s3a.setResponseHeaders(w, r, entry, totalSize)
w.WriteHeader(http.StatusOK)
_, err := w.Write(entry.Content)
return err
@ -908,7 +908,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
return newStreamErrorWithResponse(fmt.Errorf("data integrity error: size %d reported but no content available", totalSize))
}
// Empty object - set headers and write status
s3a.setResponseHeaders(w, entry, totalSize)
s3a.setResponseHeaders(w, r, entry, totalSize)
w.WriteHeader(http.StatusOK)
return nil
}
@ -958,7 +958,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
// All validation and preparation successful - NOW set headers and write status
tHeaderSet := time.Now()
s3a.setResponseHeaders(w, entry, totalSize)
s3a.setResponseHeaders(w, r, entry, totalSize)
// Override/add range-specific headers if this is a range request
if isRangeRequest {
@ -1164,7 +1164,7 @@ func (s3a *S3ApiServer) streamFromVolumeServersWithSSE(w http.ResponseWriter, r
// Set response headers
// IMPORTANT: Set ALL headers BEFORE calling WriteHeader (headers are ignored after WriteHeader)
tHeaderSet := time.Now()
s3a.setResponseHeaders(w, entry, totalSize)
s3a.setResponseHeaders(w, r, entry, totalSize)
s3a.addSSEResponseHeadersFromEntry(w, r, entry, sseType)
// Override/add range-specific headers if this is a range request
@ -1894,7 +1894,7 @@ func (s3a *S3ApiServer) addSSEResponseHeadersFromEntry(w http.ResponseWriter, r
}
// setResponseHeaders sets all standard HTTP response headers from entry metadata
func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, entry *filer_pb.Entry, totalSize int64) {
func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, r *http.Request, entry *filer_pb.Entry, totalSize int64) {
// Safety check: entry must be valid
if entry == nil {
glog.Errorf("setResponseHeaders: entry is nil")
@ -1974,6 +1974,18 @@ func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, entry *filer_p
w.Header().Set(s3_constants.AmzTagCount, strconv.Itoa(tagCount))
}
}
// 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 {
for queryParam, headerValue := range r.URL.Query() {
if normalizedHeader, ok := s3_constants.PassThroughHeaders[strings.ToLower(queryParam)]; ok && len(headerValue) > 0 {
w.Header().Set(normalizedHeader, headerValue[0])
}
}
}
}
// simpleMasterClient implements the minimal interface for streaming
@ -2241,7 +2253,7 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
// For HEAD requests, we already have all metadata - just set headers directly
totalSize := int64(filer.FileSize(objectEntryForSSE))
s3a.setResponseHeaders(w, objectEntryForSSE, totalSize)
s3a.setResponseHeaders(w, r, objectEntryForSSE, totalSize)
// Check if PartNumber query parameter is present (for multipart objects)
// This logic matches the filer handler for consistency

Loading…
Cancel
Save