diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 69805b9b..c357e2d1 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -3844,8 +3844,8 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput } if string(vId) != srcVersionId { - srcBucket = filepath.Join(p.versioningDir, srcBucket) - srcObject = filepath.Join(genObjVersionKey(srcObject), srcVersionId) + srcBucket = joinPathWithTrailer(p.versioningDir, srcBucket) + srcObject = joinPathWithTrailer(genObjVersionKey(srcObject), srcVersionId) } } @@ -3857,7 +3857,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput return nil, fmt.Errorf("stat bucket: %w", err) } - objPath := filepath.Join(srcBucket, srcObject) + objPath := joinPathWithTrailer(srcBucket, srcObject) f, err := os.Open(objPath) if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) { if p.versioningEnabled() && vEnabled { @@ -3896,7 +3896,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput var crc64nvme *string var chType types.ChecksumType - dstObjdPath := filepath.Join(dstBucket, dstObject) + dstObjdPath := joinPathWithTrailer(dstBucket, dstObject) if dstObjdPath == objPath { if input.MetadataDirective == types.MetadataDirectiveCopy { return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest) @@ -4917,3 +4917,11 @@ func getString(str *string) string { } return *str } + +func joinPathWithTrailer(paths ...string) string { + joined := filepath.Join(paths...) + if strings.HasSuffix(paths[len(paths)-1], "/") { + joined += "/" + } + return joined +} diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index ad7a147b..12b12116 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -641,6 +641,8 @@ func TestPosix(s *S3Conf) { PutObject_name_too_long(s) HeadObject_name_too_long(s) DeleteObject_name_too_long(s) + CopyObject_overwrite_same_dir_object(s) + CopyObject_overwrite_same_file_object(s) DeleteObject_directory_not_empty(s) // posix specific versioning tests if !s.versioningEnabled { @@ -912,6 +914,8 @@ func GetIntTests() IntTests { "DeleteObject_non_existing_object": DeleteObject_non_existing_object, "DeleteObject_directory_object_noslash": DeleteObject_directory_object_noslash, "DeleteObject_name_too_long": DeleteObject_name_too_long, + "CopyObject_overwrite_same_dir_object": CopyObject_overwrite_same_dir_object, + "CopyObject_overwrite_same_file_object": CopyObject_overwrite_same_file_object, "DeleteObject_non_existing_dir_object": DeleteObject_non_existing_dir_object, "DeleteObject_directory_object": DeleteObject_directory_object, "DeleteObject_success": DeleteObject_success, diff --git a/tests/integration/tests.go b/tests/integration/tests.go index bfafc92f..0dcc2eb6 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -14635,6 +14635,52 @@ func DeleteObject_name_too_long(s *S3Conf) error { }) } +func CopyObject_overwrite_same_dir_object(s *S3Conf) error { + testName := "CopyObject_overwrite_same_dir_object" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + _, err := putObjects(s3client, []string{"foo/"}, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: getPtr("foo"), + CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, "foo/")), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory)); err != nil { + return err + } + + return nil + }) +} + +func CopyObject_overwrite_same_file_object(s *S3Conf) error { + testName := "CopyObject_overwrite_same_file_object" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + _, err := putObjects(s3client, []string{"foo"}, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: getPtr("foo/"), + CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, "foo")), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectParentIsFile)); err != nil { + return err + } + + return nil + }) +} + // Versioning tests func PutBucketVersioning_non_existing_bucket(s *S3Conf) error { testName := "PutBucketVersioning_non_existing_bucket"