@ -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 ) ) )
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
// 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-Range" , fmt . Sprintf ( "bytes %d-%d/%d" , offset , offset + size - 1 , totalSize ) )
w . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( size , 10 ) )
w . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( size , 10 ) )
w . WriteHeader ( http . StatusPartialContent )
w . WriteHeader ( http . StatusPartialContent )
@ -888,7 +888,7 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
return err
return err
}
}
// Non-range request for inline content
// Non-range request for inline content
s3a . setResponseHeaders ( w , entry , totalSize )
s3a . setResponseHeaders ( w , r , entry , totalSize )
w . WriteHeader ( http . StatusOK )
w . WriteHeader ( http . StatusOK )
_ , err := w . Write ( entry . Content )
_ , err := w . Write ( entry . Content )
return err
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 ) )
return newStreamErrorWithResponse ( fmt . Errorf ( "data integrity error: size %d reported but no content available" , totalSize ) )
}
}
// Empty object - set headers and write status
// Empty object - set headers and write status
s3a . setResponseHeaders ( w , entry , totalSize )
s3a . setResponseHeaders ( w , r , entry , totalSize )
w . WriteHeader ( http . StatusOK )
w . WriteHeader ( http . StatusOK )
return nil
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
// All validation and preparation successful - NOW set headers and write status
tHeaderSet := time . Now ( )
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
// Override/add range-specific headers if this is a range request
if isRangeRequest {
if isRangeRequest {
@ -1164,7 +1164,7 @@ func (s3a *S3ApiServer) streamFromVolumeServersWithSSE(w http.ResponseWriter, r
// Set response headers
// Set response headers
// IMPORTANT: Set ALL headers BEFORE calling WriteHeader (headers are ignored after WriteHeader)
// IMPORTANT: Set ALL headers BEFORE calling WriteHeader (headers are ignored after WriteHeader)
tHeaderSet := time . Now ( )
tHeaderSet := time . Now ( )
s3a . setResponseHeaders ( w , entry , totalSize )
s3a . setResponseHeaders ( w , r , entry , totalSize )
s3a . addSSEResponseHeadersFromEntry ( w , r , entry , sseType )
s3a . addSSEResponseHeadersFromEntry ( w , r , entry , sseType )
// Override/add range-specific headers if this is a range request
// 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
// 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
// Safety check: entry must be valid
if entry == nil {
if entry == nil {
glog . Errorf ( "setResponseHeaders: entry is 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 ) )
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
// 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
// For HEAD requests, we already have all metadata - just set headers directly
totalSize := int64 ( filer . FileSize ( objectEntryForSSE ) )
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)
// Check if PartNumber query parameter is present (for multipart objects)
// This logic matches the filer handler for consistency
// This logic matches the filer handler for consistency