Browse Source

Missing PartsCount Header

pull/7481/head
chrislu 3 weeks ago
parent
commit
9a71ffda9d
  1. 6
      weed/s3api/filer_multipart.go
  2. 4
      weed/s3api/s3_constants/header.go
  3. 10
      weed/s3api/s3api_object_handlers.go
  4. 18
      weed/s3api/s3api_object_handlers_copy.go

6
weed/s3api/filer_multipart.go

@ -325,6 +325,8 @@ func (s3a *S3ApiServer) completeMultipartUpload(r *http.Request, input *s3.Compl
} }
versionEntry.Extended[s3_constants.ExtVersionIdKey] = []byte(versionId) versionEntry.Extended[s3_constants.ExtVersionIdKey] = []byte(versionId)
versionEntry.Extended[s3_constants.SeaweedFSUploadId] = []byte(*input.UploadId) versionEntry.Extended[s3_constants.SeaweedFSUploadId] = []byte(*input.UploadId)
// Store parts count for x-amz-mp-parts-count header
versionEntry.Extended[s3_constants.SeaweedFSMultipartPartsCount] = []byte(fmt.Sprintf("%d", len(completedPartNumbers)))
// Set object owner for versioned multipart objects // Set object owner for versioned multipart objects
amzAccountId := r.Header.Get(s3_constants.AmzAccountId) amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
@ -387,6 +389,8 @@ func (s3a *S3ApiServer) completeMultipartUpload(r *http.Request, input *s3.Compl
entry.Extended = make(map[string][]byte) entry.Extended = make(map[string][]byte)
} }
entry.Extended[s3_constants.ExtVersionIdKey] = []byte("null") entry.Extended[s3_constants.ExtVersionIdKey] = []byte("null")
// Store parts count for x-amz-mp-parts-count header
entry.Extended[s3_constants.SeaweedFSMultipartPartsCount] = []byte(fmt.Sprintf("%d", len(completedPartNumbers)))
// Set object owner for suspended versioning multipart objects // Set object owner for suspended versioning multipart objects
amzAccountId := r.Header.Get(s3_constants.AmzAccountId) amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
@ -440,6 +444,8 @@ func (s3a *S3ApiServer) completeMultipartUpload(r *http.Request, input *s3.Compl
entry.Extended = make(map[string][]byte) entry.Extended = make(map[string][]byte)
} }
entry.Extended[s3_constants.SeaweedFSUploadId] = []byte(*input.UploadId) entry.Extended[s3_constants.SeaweedFSUploadId] = []byte(*input.UploadId)
// Store parts count for x-amz-mp-parts-count header
entry.Extended[s3_constants.SeaweedFSMultipartPartsCount] = []byte(fmt.Sprintf("%d", len(completedPartNumbers)))
// Set object owner for non-versioned multipart objects // Set object owner for non-versioned multipart objects
amzAccountId := r.Header.Get(s3_constants.AmzAccountId) amzAccountId := r.Header.Get(s3_constants.AmzAccountId)

4
weed/s3api/s3_constants/header.go

@ -42,7 +42,9 @@ const (
SeaweedFSIsDirectoryKey = "X-Seaweedfs-Is-Directory-Key" SeaweedFSIsDirectoryKey = "X-Seaweedfs-Is-Directory-Key"
SeaweedFSPartNumber = "X-Seaweedfs-Part-Number" SeaweedFSPartNumber = "X-Seaweedfs-Part-Number"
SeaweedFSUploadId = "X-Seaweedfs-Upload-Id" SeaweedFSUploadId = "X-Seaweedfs-Upload-Id"
SeaweedFSMultipartPartsCount = "X-Seaweedfs-Multipart-Parts-Count"
SeaweedFSExpiresS3 = "X-Seaweedfs-Expires-S3" SeaweedFSExpiresS3 = "X-Seaweedfs-Expires-S3"
AmzMpPartsCount = "x-amz-mp-parts-count"
// S3 ACL headers // S3 ACL headers
AmzCannedAcl = "X-Amz-Acl" AmzCannedAcl = "X-Amz-Acl"
@ -70,8 +72,6 @@ const (
AmzCopySourceIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since" AmzCopySourceIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since"
AmzCopySourceIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since" AmzCopySourceIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since"
AmzMpPartsCount = "X-Amz-Mp-Parts-Count"
// S3 Server-Side Encryption with Customer-provided Keys (SSE-C) // S3 Server-Side Encryption with Customer-provided Keys (SSE-C)
AmzServerSideEncryptionCustomerAlgorithm = "X-Amz-Server-Side-Encryption-Customer-Algorithm" AmzServerSideEncryptionCustomerAlgorithm = "X-Amz-Server-Side-Encryption-Customer-Algorithm"
AmzServerSideEncryptionCustomerKey = "X-Amz-Server-Side-Encryption-Customer-Key" AmzServerSideEncryptionCustomerKey = "X-Amz-Server-Side-Encryption-Customer-Key"

10
weed/s3api/s3api_object_handlers.go

@ -1091,6 +1091,16 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
totalSize := int64(filer.FileSize(objectEntryForSSE)) totalSize := int64(filer.FileSize(objectEntryForSSE))
s3a.setResponseHeaders(w, objectEntryForSSE, totalSize) s3a.setResponseHeaders(w, objectEntryForSSE, totalSize)
// Check if PartNumber query parameter is present (for multipart objects)
partNumberStr := r.URL.Query().Get("partNumber")
if partNumberStr != "" && objectEntryForSSE.Extended != nil {
// If this is a multipart object, add the parts count header
if partsCountStr, exists := objectEntryForSSE.Extended[s3_constants.SeaweedFSMultipartPartsCount]; exists {
w.Header().Set(s3_constants.AmzMpPartsCount, string(partsCountStr))
glog.V(3).Infof("HeadObject: Set PartsCount=%s for multipart object", string(partsCountStr))
}
}
// Detect and handle SSE // Detect and handle SSE
sseType := s3a.detectPrimarySSEType(objectEntryForSSE) sseType := s3a.detectPrimarySSEType(objectEntryForSSE)
if sseType != "" && sseType != "None" { if sseType != "" && sseType != "None" {

18
weed/s3api/s3api_object_handlers_copy.go

@ -348,9 +348,12 @@ func pathToBucketAndObject(path string) (bucket, object string) {
bucket = parts[0] bucket = parts[0]
object = "/" + parts[1] object = "/" + parts[1]
return bucket, object return bucket, object
}
// Only bucket provided, no object - this is invalid in copy operations
} else if len(parts) == 1 && parts[0] != "" {
// Only bucket provided, no object
return parts[0], "" return parts[0], ""
}
// Empty path
return "", ""
} }
func pathToBucketObjectAndVersion(path string) (bucket, object, versionId string) { func pathToBucketObjectAndVersion(path string) (bucket, object, versionId string) {
@ -393,20 +396,23 @@ func (s3a *S3ApiServer) CopyObjectPartHandler(w http.ResponseWriter, r *http.Req
// Copy source path. // Copy source path.
cpSrcPath := r.Header.Get("X-Amz-Copy-Source") cpSrcPath := r.Header.Get("X-Amz-Copy-Source")
glog.V(2).Infof("CopyObjectPart: Raw copy source header=%q (len=%d)", cpSrcPath, len(cpSrcPath))
// Try URL unescaping - AWS SDK sends URL-encoded copy sources // Try URL unescaping - AWS SDK sends URL-encoded copy sources
unescapedPath, err := url.QueryUnescape(cpSrcPath) unescapedPath, err := url.QueryUnescape(cpSrcPath)
if err != nil { if err != nil {
// If unescaping fails, log and use original // If unescaping fails, log and use original
glog.V(2).Infof("CopyObjectPart: Failed to unescape copy source %q: %v, using as-is", cpSrcPath, err) glog.V(2).Infof("CopyObjectPart: Failed to unescape copy source %q: %v, using as-is", cpSrcPath, err)
} else {
cpSrcPath = unescapedPath
unescapedPath = cpSrcPath
} }
cpSrcPath = unescapedPath
glog.V(2).Infof("CopyObjectPart: Copy source header=%q, after unescape=%q", r.Header.Get("X-Amz-Copy-Source"), cpSrcPath)
glog.V(2).Infof("CopyObjectPart: After unescape=%q (len=%d, bytes=%v)", cpSrcPath, len(cpSrcPath), []byte(cpSrcPath))
srcBucket, srcObject, srcVersionId := pathToBucketObjectAndVersion(cpSrcPath) srcBucket, srcObject, srcVersionId := pathToBucketObjectAndVersion(cpSrcPath)
glog.V(2).Infof("CopyObjectPart: Parsed srcBucket=%q, srcObject=%q, srcVersionId=%q", srcBucket, srcObject, srcVersionId)
glog.V(2).Infof("CopyObjectPart: Parsed srcBucket=%q (len=%d), srcObject=%q (len=%d), srcVersionId=%q",
srcBucket, len(srcBucket), srcObject, len(srcObject), srcVersionId)
// If source object is empty or bucket is empty, reply back invalid copy source. // If source object is empty or bucket is empty, reply back invalid copy source.
// Note: srcObject can be "/" for root-level objects, but empty string means parsing failed // Note: srcObject can be "/" for root-level objects, but empty string means parsing failed

Loading…
Cancel
Save