@ -659,16 +659,14 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
glog . V ( 3 ) . Infof ( "GetObject: Set PartsCount=%d for multipart GET with PartNumber=%d" , partsCount , partNumber )
// Calculate the byte range for this part
// Note: ETag is NOT overridden - AWS S3 returns the complete object's ETag
// even when requesting a specific part via PartNumber
var startOffset , endOffset int64
if partInfo != nil {
// Use part boundaries from metadata (accurate for multi-chunk parts)
startOffset = objectEntryForSSE . Chunks [ partInfo . StartChunk ] . Offset
lastChunk := objectEntryForSSE . Chunks [ partInfo . EndChunk - 1 ]
endOffset = lastChunk . Offset + int64 ( lastChunk . Size ) - 1
// Override ETag with the part's ETag from metadata
w . Header ( ) . Set ( "ETag" , "\"" + partInfo . ETag + "\"" )
glog . V ( 3 ) . Infof ( "GetObject: Override ETag with part %d ETag: %s (from metadata)" , partNumber , partInfo . ETag )
} else {
// Fallback: assume 1:1 part-to-chunk mapping (backward compatibility)
chunkIndex := partNumber - 1
@ -680,15 +678,6 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
partChunk := objectEntryForSSE . Chunks [ chunkIndex ]
startOffset = partChunk . Offset
endOffset = partChunk . Offset + int64 ( partChunk . Size ) - 1
// Override ETag with chunk's ETag (fallback)
if partChunk . ETag != "" {
if md5Bytes , decodeErr := base64 . StdEncoding . DecodeString ( partChunk . ETag ) ; decodeErr == nil {
partETag := fmt . Sprintf ( "%x" , md5Bytes )
w . Header ( ) . Set ( "ETag" , "\"" + partETag + "\"" )
glog . V ( 3 ) . Infof ( "GetObject: Override ETag with part %d ETag: %s (fallback from chunk)" , partNumber , partETag )
}
}
}
// Check if client supplied a Range header - if so, apply it within the part's boundaries
@ -2266,7 +2255,7 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
if partNumberStr != "" {
if partNumber , parseErr := strconv . Atoi ( partNumberStr ) ; parseErr == nil && partNumber > 0 {
// Get actual parts count from metadata (not chunk count)
partsCount , partInfo := s3a . getMultipartInfo ( objectEntryForSSE , partNumber )
partsCount , _ := s3a . getMultipartInfo ( objectEntryForSSE , partNumber )
// Validate part number
if partNumber > partsCount {
@ -2276,31 +2265,10 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
}
// Set parts count header
// Note: ETag is NOT overridden - AWS S3 returns the complete object's ETag
// even when requesting a specific part via PartNumber
w . Header ( ) . Set ( s3_constants . AmzMpPartsCount , strconv . Itoa ( partsCount ) )
glog . V ( 3 ) . Infof ( "HeadObject: Set PartsCount=%d for part %d" , partsCount , partNumber )
// Override ETag with the part's ETag
if partInfo != nil {
// Use part ETag from metadata (accurate for multi-chunk parts)
w . Header ( ) . Set ( "ETag" , "\"" + partInfo . ETag + "\"" )
glog . V ( 3 ) . Infof ( "HeadObject: Override ETag with part %d ETag: %s (from metadata)" , partNumber , partInfo . ETag )
} else {
// Fallback: use chunk's ETag (backward compatibility)
chunkIndex := partNumber - 1
if chunkIndex >= len ( objectEntryForSSE . Chunks ) {
glog . Warningf ( "HeadObject: Part %d chunk index %d out of range (chunks: %d)" , partNumber , chunkIndex , len ( objectEntryForSSE . Chunks ) )
s3err . WriteErrorResponse ( w , r , s3err . ErrInvalidPart )
return
}
partChunk := objectEntryForSSE . Chunks [ chunkIndex ]
if partChunk . ETag != "" {
if md5Bytes , decodeErr := base64 . StdEncoding . DecodeString ( partChunk . ETag ) ; decodeErr == nil {
partETag := fmt . Sprintf ( "%x" , md5Bytes )
w . Header ( ) . Set ( "ETag" , "\"" + partETag + "\"" )
glog . V ( 3 ) . Infof ( "HeadObject: Override ETag with part %d ETag: %s (fallback from chunk)" , partNumber , partETag )
}
}
}
}
}