diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index 6ba9902..ecc0431 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -337,6 +337,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { copySrcIfNoneMatch := ctx.Get("X-Amz-Copy-Source-If-None-Match") copySrcModifSince := ctx.Get("X-Amz-Copy-Source-If-Modified-Since") copySrcUnmodifSince := ctx.Get("X-Amz-Copy-Source-If-Unmodified-Since") + copySrcRange := ctx.Get("X-Amz-Copy-Source-Range") // Permission headers acl := ctx.Get("X-Amz-Acl") @@ -401,9 +402,27 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { return SendResponse(ctx, err) } + if ctx.Request().URI().QueryArgs().Has("uploadId") && ctx.Request().URI().QueryArgs().Has("partNumber") && copySource != "" { + partNumber := ctx.QueryInt("partNumber", -1) + if partNumber < 1 || partNumber > 10000 { + return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidPart)) + } + + resp, err := c.be.UploadPartCopy(&s3.UploadPartCopyInput{ + Bucket: &bucket, + Key: &keyStart, + CopySource: ©Source, + PartNumber: int32(partNumber), + UploadId: &uploadId, + ExpectedBucketOwner: &bucketOwner, + CopySourceRange: ©SrcRange, + }) + return SendXMLResponse(ctx, resp, err) + } + if ctx.Request().URI().QueryArgs().Has("uploadId") && ctx.Request().URI().QueryArgs().Has("partNumber") { partNumber := ctx.QueryInt("partNumber", -1) - if partNumber < 1 { + if partNumber < 1 || partNumber > 10000 { return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidPart)) } diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index c766db2..6bcb33c 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -666,6 +666,9 @@ func TestS3ApiController_PutActions(t *testing.T) { SetTagsFunc: func(bucket, object string, tags map[string]string) error { return nil }, + UploadPartCopyFunc: func(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { + return &s3.UploadPartCopyOutput{}, nil + }, }, } app.Use(func(ctx *fiber.Ctx) error { @@ -676,6 +679,14 @@ func TestS3ApiController_PutActions(t *testing.T) { }) app.Put("/:bucket/:key/*", s3ApiController.PutActions) + // UploadPartCopy success + uploadPartCpyReq := httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?uploadId=12asd32&partNumber=3", nil) + uploadPartCpyReq.Header.Set("X-Amz-Copy-Source", "srcBucket/srcObject") + + // UploadPartCopy error case + uploadPartCpyErrReq := httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?uploadId=12asd32&partNumber=invalid", nil) + uploadPartCpyErrReq.Header.Set("X-Amz-Copy-Source", "srcBucket/srcObject") + // CopyObject success cpySrcReq := httptest.NewRequest(http.MethodPut, "/my-bucket/my-key", nil) cpySrcReq.Header.Set("X-Amz-Copy-Source", "srcBucket/srcObject") @@ -789,6 +800,24 @@ func TestS3ApiController_PutActions(t *testing.T) { wantErr: false, statusCode: 200, }, + { + name: "Upload-part-copy-invalid-part-number", + app: app, + args: args{ + req: uploadPartCpyErrReq, + }, + wantErr: false, + statusCode: 400, + }, + { + name: "Upload-part-copy-success", + app: app, + args: args{ + req: uploadPartCpyReq, + }, + wantErr: false, + statusCode: 200, + }, { name: "Copy-object-success", app: app,