From 6704314feaf3a91797ed535f0883c72eb4d26b71 Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 2 Oct 2026 16:01:08 +0200 Subject: [PATCH] Upstream versity/versitygw state of the touched files before eecdc6b996 --- s3api/router.go | 1560 +++++++++++++++++++++ s3err/invalid-argument.go | 381 +++++ tests/integration/ListMultipartUploads.go | 674 +++++++++ tests/integration/group-tests.go | 48 +- tests/test_rest_list_multipart_uploads.sh | 231 +++ 5 files changed, 2892 insertions(+), 2 deletions(-) create mode 100644 s3api/router.go create mode 100644 s3err/invalid-argument.go create mode 100644 tests/integration/ListMultipartUploads.go create mode 100644 tests/test_rest_list_multipart_uploads.sh diff --git a/s3api/router.go b/s3api/router.go new file mode 100644 index 0000000..3ec46da --- /dev/null +++ b/s3api/router.go @@ -0,0 +1,1560 @@ +// Copyright 2023 Versity Software +// This file is licensed under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package s3api + +import ( + "net/http" + + "github.com/gofiber/fiber/v3" + "github.com/versity/versitygw/auth" + "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/metrics" + "github.com/versity/versitygw/s3api/controllers" + "github.com/versity/versitygw/s3api/middlewares" + "github.com/versity/versitygw/s3err" + "github.com/versity/versitygw/s3event" + "github.com/versity/versitygw/s3log" +) + +type S3ApiRouter struct { + WithAdmSrv bool + app *fiber.App + be backend.Backend + iam auth.IAMService + logger s3log.AuditLogger + aLogger s3log.AuditLogger + evs s3event.S3EventSender + mm metrics.Manager + root middlewares.RootUserConfig + Ctrl controllers.S3ApiController + readonly bool + disableACL bool + region string + virtualDomain string + corsAllowOrigin string + mpMaxParts int + disableSSECTLS bool + adminPathPrefix string +} + +func (sa *S3ApiRouter) Init() { + ctrl := controllers.New(sa.be, sa.iam, sa.logger, sa.evs, sa.mm, sa.readonly, sa.disableACL, sa.virtualDomain, sa.mpMaxParts, sa.disableSSECTLS) + sa.Ctrl = ctrl + // initialize global host-style parser middleware if virtual domain is specified + if sa.virtualDomain != "" { + sa.app.Use("*", middlewares.HostStyleParser(sa.virtualDomain)) + } + + if sa.WithAdmSrv { + adminRouter := &S3AdminRouter{s3api: ctrl} + adminRouter.Init(sa.app.Group(sa.adminPathPrefix), sa.be, sa.iam, sa.aLogger, sa.root, sa.region, false, sa.corsAllowOrigin) + } + + services := &controllers.Services{ + Logger: sa.logger, + EventSender: sa.evs, + MetricsManager: sa.mm, + } + + // ListBuckets action + + // copy source is not allowed on '/' + sa.app.Get("/", middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + middlewares.ApplyDefaultCORS(sa.corsAllowOrigin), + ), + ) + + sa.app.Get("/", + controllers.ProcessHandlers( + ctrl.ListBuckets, + metrics.ActionListAllMyBuckets, + services, + middlewares.ApplyDefaultCORS(sa.corsAllowOrigin), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListAllMyBuckets, "", auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, true), + )) + + sa.app.Options("/", + middlewares.ApplyDefaultCORSPreflight(sa.corsAllowOrigin), + middlewares.ApplyDefaultCORS(sa.corsAllowOrigin), + ) + + bucketRouter := sa.app.Group("/:bucket") + objectRouter := sa.app.Group("/:bucket/*") + applyBucketCORS := middlewares.ApplyBucketCORS(sa.be, middlewares.BucketFromPath, sa.corsAllowOrigin) + + // PUT bucket operations + bucketRouter.Put("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.PutBucketTagging, + metrics.ActionPutBucketTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketTagging, auth.PutBucketTaggingAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, true), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("ownershipControls"), + controllers.ProcessHandlers( + ctrl.PutBucketOwnershipControls, + metrics.ActionPutBucketOwnershipControls, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketOwnershipControls, auth.PutBucketOwnershipControlsAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("versioning"), + controllers.ProcessHandlers( + ctrl.PutBucketVersioning, + metrics.ActionPutBucketVersioning, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketVersioning, auth.PutBucketVersioningAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("object-lock"), + controllers.ProcessHandlers( + ctrl.PutObjectLockConfiguration, + metrics.ActionPutObjectLockConfiguration, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectLockConfiguration, auth.PutBucketObjectLockConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, true), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("cors"), + controllers.ProcessHandlers( + ctrl.PutBucketCors, + metrics.ActionPutBucketCors, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketCors, auth.PutBucketCorsAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, true), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("policy"), + controllers.ProcessHandlers( + ctrl.PutBucketPolicy, + metrics.ActionPutBucketPolicy, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketPolicy, auth.PutBucketPolicyAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("acl"), + controllers.ProcessHandlers( + ctrl.PutBucketAcl, + metrics.ActionPutBucketAcl, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketAcl, auth.PutBucketAclAction, auth.PermissionWriteAcp, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Put("", + middlewares.MatchQueryArgs("analytics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketAnalyticsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketAnalyticsConfiguration, auth.PutAnalyticsConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("encryption"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketEncryption, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketEncryption, auth.PutEncryptionConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("intelligent-tiering"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketIntelligentTieringConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketIntelligentTieringConfiguration, auth.PutIntelligentTieringConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("inventory"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketInventoryConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketInventoryConfiguration, auth.PutInventoryConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("lifecycle"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketLifecycleConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketLifecycleConfiguration, auth.PutLifecycleConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("logging"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketLogging, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketLogging, auth.PutBucketLoggingAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("requestPayment"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketRequestPayment, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketRequestPayment, auth.PutBucketRequestPaymentAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("metrics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketMetricsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketMetricsConfiguration, auth.PutMetricsConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("replication"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketReplication, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketReplication, auth.PutReplicationConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("publicAccessBlock"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutPublicAccessBlock, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutPublicAccessBlock, auth.PutBucketPublicAccessBlockAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("notification"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketNotificationConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketNotificationConfiguration, auth.PutBucketNotificationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("accelerate"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutBucketAccelerateConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketAccelerateConfiguration, auth.PutAccelerateConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + middlewares.MatchQueryArgs("website"), + controllers.ProcessHandlers( + ctrl.PutBucketWebsite, + metrics.ActionPutBucketWebsite, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutBucketWebsite, auth.PutBucketWebsiteAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Put("", + controllers.ProcessHandlers( + ctrl.CreateBucket, + metrics.ActionCreateBucket, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionCreateBucket, auth.CreateBucketAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + )) + + // HeadBucket action + + // copy source is not allowed on bucket HEAD operation + bucketRouter.Head("/", middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + bucketRouter.Head("", + controllers.ProcessHandlers( + ctrl.HeadBucket, + metrics.ActionHeadBucket, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionHeadBucket, auth.ListBucketAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, false, false), + middlewares.ParseAcl(sa.be), + )) + + // DELETE bucket operations + + // copy source is not allowed on bucket DELETE operation + bucketRouter.Delete("/", middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + bucketRouter.Delete("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.DeleteBucketTagging, + metrics.ActionDeleteBucketTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketTagging, auth.PutBucketTaggingAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("ownershipControls"), + controllers.ProcessHandlers( + ctrl.DeleteBucketOwnershipControls, + metrics.ActionDeleteBucketOwnershipControls, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketOwnershipControls, auth.PutBucketOwnershipControlsAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("policy"), + controllers.ProcessHandlers( + ctrl.DeleteBucketPolicy, + metrics.ActionDeleteBucketPolicy, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketPolicy, auth.PutBucketPolicyAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("cors"), + controllers.ProcessHandlers( + ctrl.DeleteBucketCors, + metrics.ActionDeleteBucketCors, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketCors, auth.PutBucketCorsAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("analytics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketAnalyticsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketAnalyticsConfiguration, auth.PutAnalyticsConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("encryption"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketEncryption, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketEncryption, auth.PutEncryptionConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("intelligent-tiering"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketIntelligentTieringConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketIntelligentTieringConfiguration, auth.PutIntelligentTieringConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("inventory"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketInventoryConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketInventoryConfiguration, auth.PutInventoryConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("lifecycle"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketLifecycle, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketLifecycle, auth.PutLifecycleConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("metrics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketMetricsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketMetricsConfiguration, auth.PutMetricsConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("replication"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteBucketReplication, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketReplication, auth.PutReplicationConfigurationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("publicAccessBlock"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeletePublicAccessBlock, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeletePublicAccessBlock, auth.PutBucketPublicAccessBlockAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + middlewares.MatchQueryArgs("website"), + controllers.ProcessHandlers( + ctrl.DeleteBucketWebsite, + metrics.ActionDeleteBucketWebsite, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucketWebsite, auth.DeleteBucketWebsiteAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Delete("", + controllers.ProcessHandlers( + ctrl.DeleteBucket, + metrics.ActionDeleteBucket, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteBucket, auth.DeleteBucketAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + + // GET bucket operations + + // copy source is not allowed on bucket GET operation + bucketRouter.Get("/", middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + bucketRouter.Get("", + middlewares.MatchQueryArgs("location"), + controllers.ProcessHandlers( + ctrl.GetBucketLocation, + metrics.ActionGetBucketLocation, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketLocation, auth.GetBucketLocationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.GetBucketTagging, + metrics.ActionGetBucketTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketTagging, auth.GetBucketTaggingAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("ownershipControls"), + controllers.ProcessHandlers( + ctrl.GetBucketOwnershipControls, + metrics.ActionGetBucketOwnershipControls, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketOwnershipControls, auth.GetBucketOwnershipControlsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("versioning"), + controllers.ProcessHandlers( + ctrl.GetBucketVersioning, + metrics.ActionGetBucketVersioning, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketVersioning, auth.GetBucketVersioningAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("policy"), + controllers.ProcessHandlers( + ctrl.GetBucketPolicy, + metrics.ActionGetBucketPolicy, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketPolicy, auth.GetBucketPolicyAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("cors"), + controllers.ProcessHandlers( + ctrl.GetBucketCors, + metrics.ActionGetBucketCors, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketCors, auth.GetBucketCorsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("object-lock"), + controllers.ProcessHandlers( + ctrl.GetObjectLockConfiguration, + metrics.ActionGetObjectLockConfiguration, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectLockConfiguration, auth.GetBucketObjectLockConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("acl"), + controllers.ProcessHandlers( + ctrl.GetBucketAcl, + metrics.ActionGetBucketAcl, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketAcl, auth.GetBucketAclAction, auth.PermissionReadAcp, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, false, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("uploads"), + controllers.ProcessHandlers( + ctrl.ListMultipartUploads, + metrics.ActionListMultipartUploads, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListMultipartUploads, auth.ListBucketMultipartUploadsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("versions"), + controllers.ProcessHandlers( + ctrl.ListObjectVersions, + metrics.ActionListObjectVersions, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListObjectVersions, auth.ListBucketVersionsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("policyStatus"), + controllers.ProcessHandlers( + ctrl.GetBucketPolicyStatus, + metrics.ActionGetBucketPolicyStatus, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketPolicyStatus, auth.GetBucketPolicyStatusAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + middlewares.MatchQueryArgs("analytics", "id"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketAnalyticsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketAnalyticsConfiguration, auth.GetAnalyticsConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("analytics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionListBucketAnalyticsConfigurations, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListBucketAnalyticsConfigurations, auth.GetAnalyticsConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("encryption"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketEncryption, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketEncryption, auth.GetEncryptionConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("intelligent-tiering", "id"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketIntelligentTieringConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketIntelligentTieringConfiguration, auth.GetIntelligentTieringConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("intelligent-tiering"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionListBucketIntelligentTieringConfigurations, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListBucketIntelligentTieringConfigurations, auth.GetIntelligentTieringConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("inventory", "id"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketInventoryConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketInventoryConfiguration, auth.GetInventoryConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("inventory"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionListBucketInventoryConfigurations, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListBucketInventoryConfigurations, auth.GetInventoryConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("lifecycle"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketLifecycleConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketLifecycleConfiguration, auth.GetLifecycleConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("logging"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketLogging, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketLogging, auth.GetBucketLoggingAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("requestPayment"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketRequestPayment, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketRequestPayment, auth.GetBucketRequestPaymentAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("metrics", "id"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketMetricsConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketMetricsConfiguration, auth.GetMetricsConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("metrics"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionListBucketMetricsConfigurations, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListBucketMetricsConfigurations, auth.GetMetricsConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("replication"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketReplication, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketReplication, auth.GetReplicationConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("publicAccessBlock"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetPublicAccessBlock, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetPublicAccessBlock, auth.GetBucketPublicAccessBlockAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("notification"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketNotificationConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketNotificationConfiguration, auth.GetBucketNotificationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("accelerate"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetBucketAccelerateConfiguration, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketAccelerateConfiguration, auth.GetAccelerateConfigurationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgs("website"), + controllers.ProcessHandlers( + ctrl.GetBucketWebsite, + metrics.ActionGetBucketWebsite, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetBucketWebsite, auth.GetBucketWebsiteAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + bucketRouter.Get("", + middlewares.MatchQueryArgWithValue("list-type", "2"), + controllers.ProcessHandlers( + ctrl.ListObjectsV2, + metrics.ActionListObjectsV2, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListObjectsV2, auth.ListBucketAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + bucketRouter.Get("", + controllers.ProcessHandlers( + ctrl.ListObjects, + metrics.ActionListObjects, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListObjects, auth.ListBucketAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + + // bucket POST operation is not allowed with uploadId and copy source + bucketRouter.Post("/", + middlewares.MatchHeader("X-Amz-Copy-Source"), + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + // DeleteObjects action + bucketRouter.Post("", + middlewares.MatchQueryArgs("delete"), + controllers.ProcessHandlers( + ctrl.DeleteObjects, + metrics.ActionDeleteObjects, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteObjects, auth.DeleteObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, true), + middlewares.ParseAcl(sa.be), + )) + + bucketRouter.Post("", + controllers.ProcessHandlers( + ctrl.POSTObject, + metrics.ActionPostObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePostObject(sa.root, sa.iam, sa.region), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPostObject, auth.PutObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.ParseAcl(sa.be), + )) + + objectRouter.Put("", + middlewares.MatchQueryArgs("annotation"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionPutObjectAnnotation, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectAnnotation, auth.PutObjectAnnotationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + objectRouter.Get("", + middlewares.MatchQueryArgs("annotation", "annotationName"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionGetObjectAnnotation, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectAnnotation, auth.GetObjectAnnotationAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + objectRouter.Get("", + middlewares.MatchQueryArgs("annotation"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionListObjectAnnotations, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListObjectAnnotations, auth.ListObjectAnnotationsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + objectRouter.Delete("", + middlewares.MatchQueryArgs("annotation"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), + metrics.ActionDeleteObjectAnnotation, + services, + middlewares.BucketObjectNameValidator(), + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteObjectAnnotation, auth.DeleteObjectAnnotationAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + ), + ) + objectRouter.Put("", + middlewares.MatchQueryArgs("renameObject"), + controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrNotImplemented)), metrics.ActionRenameObject, services), + ) + + // '?torrent' is rejected for every method. GET is the method + // GetObjectTorrent is defined for, so it gets no Allow header. + objectRouter.Get("", + middlewares.MatchQueryArgs("torrent"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetMethodNotAllowedErr(http.MethodGet, s3err.ResourceTypeTorrent, nil)), + metrics.ActionGetObjectTorrent, + services, + ), + ) + objectRouter.Add([]string{http.MethodHead, http.MethodPut, http.MethodDelete, http.MethodPost}, "", + middlewares.MatchQueryArgs("torrent"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetMethodNotAllowedErr(ctx.Method(), s3err.ResourceTypeTorrent, []string{http.MethodGet}) + }, + metrics.ActionUndetected, + services, + ), + ) + + // object HEAD operation is not allowed with copy source + objectRouter.Head("/", + middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + // HeadObject + objectRouter.Head("", + controllers.ProcessHandlers( + ctrl.HeadObject, + metrics.ActionHeadObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionHeadObject, auth.GetObjectAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, false, false), + middlewares.ParseAcl(sa.be), + )) + + // GET object operations + + // object operation with '?uploads' is rejected with a specific error + objectRouter.Get("", + middlewares.MatchQueryArgs("uploads"), + controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrGetUploadsWithKey)), metrics.ActionUndetected, services), + ) + + // object operation with '?versions' is rejected with a specific error + objectRouter.Get("", + middlewares.MatchQueryArgs("versions"), + controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrVersionsWithKey)), metrics.ActionUndetected, services), + ) + + // object GET operation is not allowed with copy source + objectRouter.Get("/", + middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + objectRouter.Get("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.GetObjectTagging, + metrics.ActionGetObjectTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectTagging, auth.GetObjectTaggingAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + middlewares.MatchQueryArgs("retention"), + controllers.ProcessHandlers( + ctrl.GetObjectRetention, + metrics.ActionGetObjectRetention, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectRetention, auth.GetObjectRetentionAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + middlewares.MatchQueryArgs("legal-hold"), + controllers.ProcessHandlers( + ctrl.GetObjectLegalHold, + metrics.ActionGetObjectLegalHold, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectLegalHold, auth.GetObjectLegalHoldAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + middlewares.MatchQueryArgs("acl"), + controllers.ProcessHandlers( + ctrl.GetObjectAcl, + metrics.ActionGetObjectAcl, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectAcl, auth.GetObjectAclAction, auth.PermissionReadAcp, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + middlewares.MatchQueryArgs("attributes"), + controllers.ProcessHandlers( + ctrl.GetObjectAttributes, + metrics.ActionGetObjectAttributes, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObjectAttributes, auth.GetObjectAttributesAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + ctrl.ListParts, + metrics.ActionListParts, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionListParts, auth.ListMultipartUploadPartsAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Get("", + controllers.ProcessHandlers( + ctrl.GetObject, + metrics.ActionGetObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionGetObject, auth.GetObjectAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + + // DELETE object operations + + // object DELETE operation is not allowed with copy source + objectRouter.Delete("/", + middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + objectRouter.Delete("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.DeleteObjectTagging, + metrics.ActionDeleteObjectTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteObjectTagging, auth.DeleteObjectTaggingAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Delete("", + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + ctrl.AbortMultipartUpload, + metrics.ActionAbortMultipartUpload, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionAbortMultipartUpload, auth.AbortMultipartUploadAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Delete("", + controllers.ProcessHandlers( + ctrl.DeleteObject, + metrics.ActionDeleteObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionDeleteObject, auth.DeleteObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + + // object POST operations + + // object POST operation is not allowed with copy source and uploadId + objectRouter.Post("/", + middlewares.MatchHeader("X-Amz-Copy-Source"), + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetInvalidArgumentErr(s3err.InvalidArgCopySource, ctx.Get("X-Amz-Copy-Source")) + }, + metrics.ActionUndetected, + services, + ), + ) + + objectRouter.Post("", + middlewares.MatchQueryArgs("restore"), + controllers.ProcessHandlers( + ctrl.RestoreObject, + metrics.ActionRestoreObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionRestoreObject, auth.RestoreObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Post("", + middlewares.MatchQueryArgs("select"), + middlewares.MatchQueryArgWithValue("select-type", "2"), + controllers.ProcessHandlers( + ctrl.SelectObjectContent, + metrics.ActionSelectObjectContent, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionSelectObjectContent, auth.GetObjectAction, auth.PermissionRead, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Post("", + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + ctrl.CompleteMultipartUpload, + metrics.ActionCompleteMultipartUpload, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionCompleteMultipartUpload, auth.PutObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Post("", + middlewares.MatchQueryArgs("uploads"), + controllers.ProcessHandlers( + ctrl.CreateMultipartUpload, + metrics.ActionCreateMultipartUpload, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionCreateMultipartUpload, auth.PutObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + + // PUT object operations + objectRouter.Put("", + middlewares.MatchQueryArgs("tagging"), + controllers.ProcessHandlers( + ctrl.PutObjectTagging, + metrics.ActionPutObjectTagging, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectTagging, auth.PutObjectTaggingAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + middlewares.MatchQueryArgs("retention"), + controllers.ProcessHandlers( + ctrl.PutObjectRetention, + metrics.ActionPutObjectRetention, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectRetention, auth.PutObjectRetentionAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, true), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + middlewares.MatchQueryArgs("legal-hold"), + controllers.ProcessHandlers( + ctrl.PutObjectLegalHold, + metrics.ActionPutObjectLegalHold, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectLegalHold, auth.PutObjectLegalHoldAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, true), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + middlewares.MatchQueryArgs("acl"), + controllers.ProcessHandlers( + ctrl.PutObjectAcl, + metrics.ActionPutObjectAcl, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObjectAcl, auth.PutObjectAclAction, auth.PermissionWriteAcp, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.VerifyChecksums(false, false, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + middlewares.MatchQueryArgs("uploadId", "partNumber"), + middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + ctrl.UploadPartCopy, + metrics.ActionUploadPartCopy, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionUploadPartCopy, auth.PutObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + middlewares.MatchQueryArgs("uploadId", "partNumber"), + controllers.ProcessHandlers( + ctrl.UploadPart, + metrics.ActionUploadPart, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionUploadPart, auth.PutObjectAction, auth.PermissionWrite, sa.region, true), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, true), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, true, true, false), + middlewares.VerifyChecksums(true, false, false), + middlewares.ParseAcl(sa.be), + )) + + // return error if partNumber is used without uploadId + objectRouter.Put("", + middlewares.MatchQueryArgs("partNumber"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute(s3err.GetInvalidArgumentErr(s3err.InvalidArgMissingUploadId, "partNumber")), + metrics.ActionUndetected, + services, + ), + ) + + // return 'MethodNotAllowed' if uploadId is provided without partNumber + // before the router reaches to 'PutObject' + objectRouter.Put("", + middlewares.MatchQueryArgs("uploadId"), + controllers.ProcessHandlers( + ctrl.HandleErrorRoute( + s3err.GetMethodNotAllowedErr( + http.MethodPut, + s3err.ResourceTypeUpload, + []string{http.MethodDelete, http.MethodPost, http.MethodGet}, + ), + ), + metrics.ActionUndetected, + services, + )) + + objectRouter.Put("", + middlewares.MatchHeader("X-Amz-Copy-Source"), + controllers.ProcessHandlers( + ctrl.CopyObject, + metrics.ActionCopyObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionCopyObject, auth.PutObjectAction, auth.PermissionWrite, sa.region, false), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, false), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, false, true, false), + middlewares.ParseAcl(sa.be), + )) + objectRouter.Put("", + controllers.ProcessHandlers( + ctrl.PutObject, + metrics.ActionPutObject, + services, + middlewares.BucketObjectNameValidator(), + applyBucketCORS, + middlewares.AuthorizePublicBucketAccess(sa.be, metrics.ActionPutObject, auth.PutObjectAction, auth.PermissionWrite, sa.region, true), + middlewares.VerifyPresignedV4Signature(sa.root, sa.iam, sa.region, true), + middlewares.VerifyV4Signature(sa.root, sa.iam, sa.region, true, true, false), + middlewares.VerifyChecksums(true, false, false), + middlewares.ParseAcl(sa.be), + )) + + sa.app.Options("/:bucket", + middlewares.ApplyBucketCORSPreflightFallback(sa.be, sa.corsAllowOrigin), + controllers.ProcessHandlers(ctrl.CORSOptions, metrics.ActionOptions, services, + middlewares.BucketObjectNameValidator(), + middlewares.ParseAcl(sa.be), + ), + ) + + sa.app.Options("/:bucket/*", + middlewares.ApplyBucketCORSPreflightFallback(sa.be, sa.corsAllowOrigin), + controllers.ProcessHandlers(ctrl.CORSOptions, metrics.ActionOptions, services, + middlewares.BucketObjectNameValidator(), + middlewares.ParseAcl(sa.be), + ), + ) + + // Return MethodNotAllowed for all the unmatched routes + sa.app.All("*", controllers.ProcessHandlers(func(ctx fiber.Ctx) (*controllers.Response, error) { + return &controllers.Response{}, s3err.GetMethodNotAllowedErr(ctx.Method(), s3err.ResourceTypeService, nil) + }, metrics.ActionUndetected, services)) +} diff --git a/s3err/invalid-argument.go b/s3err/invalid-argument.go new file mode 100644 index 0000000..e814bfb --- /dev/null +++ b/s3err/invalid-argument.go @@ -0,0 +1,381 @@ +// Copyright 2026 Versity Software +// This file is licensed under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package s3err + +import ( + "bytes" + "encoding/xml" + "fmt" + "net/http" +) + +type InvalidArgErrorCode int + +const ( + InvalidArgMaxBuckets InvalidArgErrorCode = iota + InvalidArgNegativeMaxKeys + InvalidArgObjectAttributes + InvalidArgPartNumber + InvalidArgCompleteMpPartNumber + InvalidArgCopySourceRange + InvalidArgCopySourceBucket + InvalidArgCopySourceObject + InvalidArgCopySourceEncoding + InvalidArgURLEncodedTagging + InvalidArgAuthHeader + InvalidArgAuthorizationType + InvalidArgPOSTFileRequired + InvalidArgSHA256Payload + InvalidArgCopySource + InvalidArgRetainUntilDate + InvalidArgPastObjectLockRetainDate + InvalidArgObjectLockRetentionDays + InvalidArgObjectLockRetentionYears + InvalidArgObjectLockRetentionDaysTooLarge + InvalidArgObjectLockRetentionYearsTooLarge + InvalidArgMissingObjectLockRetainDate + InvalidArgMissingObjectLockMode + InvalidArgLegalHoldStatus + InvalidArgObjectLockMode + InvalidArgAnonymousObjectLock + InvalidArgMetadataDirective + InvalidArgTaggingDirective + InvalidArgVersionId + InvalidArgEmptyVersionId + InvalidArgChecksumPart + InvalidArgMissingUploadId + InvalidArgUploadIdMarker + InvalidArgCannedAcl + InvalidArgOnlyAws4HmacSha256 + InvalidArgDateHeader + InvalidArgIndexDocumentSuffix + InvalidArgMissingIndexDocumentSuffix + InvalidArgErrorDocumentKey + InvalidArgAwsChunkedUnsignedPayload + InvalidArgSSECMissingAlgorithm + InvalidArgSSECMissingKey + InvalidArgSSECMissingKeyMD5 + InvalidArgSSECInvalidKey + InvalidArgSSECKeyMD5Mismatch + InvalidArgSSECInvalidAlgorithm + InvalidArgSSECIncompatibleEncryption + InvalidArgSSEInvalidEncryptionMethod + InvalidArgSSECRequiresTLS +) + +var invalidArgErrResponses = map[InvalidArgErrorCode]InvalidArgumentError{ + InvalidArgMaxBuckets: { + Description: "Argument max-buckets must be an integer between 1 and 10000.", + ArgumentName: "max-buckets", + }, + InvalidArgNegativeMaxKeys: { + Description: "max-keys cannot be negative", + ArgumentName: "maxKeys", + }, + InvalidArgObjectAttributes: { + Description: "Invalid attribute name specified.", + ArgumentName: "x-amz-object-attributes", + }, + InvalidArgPartNumber: { + Description: "Part number must be an integer between 1 and 10000, inclusive.", + ArgumentName: "partNumber", + }, + InvalidArgCompleteMpPartNumber: { + Description: "PartNumber must be >= 1", + ArgumentName: "PartNumber", + }, + InvalidArgCopySourceRange: { + Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy", + ArgumentName: "x-amz-copy-source-range", + }, + InvalidArgCopySourceBucket: { + Description: "Invalid copy source bucket name", + ArgumentName: "x-amz-copy-source", + }, + InvalidArgCopySourceObject: { + Description: "Invalid copy source object key", + ArgumentName: "x-amz-copy-source", + }, + InvalidArgCopySourceEncoding: { + Description: "Invalid copy source encoding", + ArgumentName: "x-amz-copy-source", + }, + InvalidArgURLEncodedTagging: { + Description: "The header 'x-amz-tagging' shall be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.", + ArgumentName: "x-amz-tagging", + }, + InvalidArgAuthHeader: { + Description: "Authorization header is invalid -- one and only one ' ' (space) required.", + ArgumentName: "Authorization", + }, + InvalidArgAuthorizationType: { + Description: "Unsupported Authorization Type", + ArgumentName: "Authorization", + }, + InvalidArgPOSTFileRequired: { + Description: "POST requires exactly one file upload per request.", + ArgumentName: "file", + }, + InvalidArgSHA256Payload: { + Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.", + ArgumentName: "x-amz-content-sha256", + }, + InvalidArgAwsChunkedUnsignedPayload: { + Description: "aws-chunked encoding is not supported when x-amz-content-sha256 UNSIGNED-PAYLOAD is supplied.", + ArgumentName: "Content-Encoding", + }, + InvalidArgCopySource: { + Description: "You can only specify a copy source header for copy requests.", + ArgumentName: "x-amz-copy-source", + }, + InvalidArgRetainUntilDate: { + Description: "The retain until date must be provided in ISO 8601 format", + ArgumentName: "x-amz-object-lock-retain-until-date", + }, + InvalidArgPastObjectLockRetainDate: { + Description: "The retain until date must be in the future!", + ArgumentName: "x-amz-object-lock-retain-until-date", + }, + InvalidArgMissingObjectLockRetainDate: { + Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied", + ArgumentName: "x-amz-object-lock-retain-until-date", + }, + InvalidArgMissingObjectLockMode: { + Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied", + ArgumentName: "x-amz-object-lock-mode", + }, + InvalidArgObjectLockRetentionDays: { + Description: "Default retention period must be a positive integer value.", + ArgumentName: "Days", + }, + InvalidArgObjectLockRetentionYears: { + Description: "Default retention period must be a positive integer value.", + ArgumentName: "Years", + }, + InvalidArgObjectLockRetentionDaysTooLarge: { + Description: "Default retention period too large.", + ArgumentName: "Days", + }, + InvalidArgObjectLockRetentionYearsTooLarge: { + Description: "Default retention period too large.", + ArgumentName: "Years", + }, + InvalidArgLegalHoldStatus: { + Description: "Legal Hold must be either of 'ON' or 'OFF'", + ArgumentName: "x-amz-object-lock-legal-hold", + }, + InvalidArgObjectLockMode: { + Description: "Unknown wormMode directive.", + ArgumentName: "x-amz-object-lock-mode", + }, + InvalidArgAnonymousObjectLock: { + Description: "Put Object requests with Object Lock parameters require AWS Signature Version 4", + ArgumentName: "Authorization", + }, + InvalidArgMetadataDirective: { + Description: "Unknown metadata directive.", + ArgumentName: "x-amz-metadata-directive", + }, + InvalidArgTaggingDirective: { + Description: "Unknown tagging directive.", + ArgumentName: "x-amz-tagging-directive", + }, + InvalidArgVersionId: { + Description: "Invalid version id specified", + ArgumentName: "versionId", + }, + InvalidArgEmptyVersionId: { + Description: "Version id cannot be the empty string", + ArgumentName: "versionId", + }, + InvalidArgChecksumPart: { + Description: "Invalid Base64 or multiple checksums present in request", + ArgumentName: "Checksum", + }, + InvalidArgMissingUploadId: { + Description: "This operation does not accept partNumber without uploadId", + ArgumentName: "partNumber", + }, + InvalidArgUploadIdMarker: { + Description: "Invalid uploadId marker", + ArgumentName: "upload-id-marker", + }, + InvalidArgCannedAcl: { + Description: "", + ArgumentName: "x-amz-acl", + }, + InvalidArgOnlyAws4HmacSha256: { + Description: "Only AWS4-HMAC-SHA256 is supported", + ArgumentName: "X-Amz-Algorithm", + }, + InvalidArgDateHeader: { + Description: "X-Amz-Date must be formated via ISO8601 Long format", + ArgumentName: "X-Amz-Date", + }, + InvalidArgIndexDocumentSuffix: { + Description: "The IndexDocument Suffix is not well formed", + ArgumentName: "IndexDocument", + }, + InvalidArgMissingIndexDocumentSuffix: { + Description: "A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty", + ArgumentName: "IndexDocument", + }, + InvalidArgErrorDocumentKey: { + Description: "The ErrorDocument Key is not well formed", + ArgumentName: "ErrorDocument", + }, + InvalidArgSSECMissingAlgorithm: { + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECMissingKey: { + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECMissingKeyMD5: { + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide the client calculated MD5 of the secret key.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECInvalidKey: { + Description: "The secret key was invalid for the specified algorithm.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECKeyMD5Mismatch: { + Description: "The calculated MD5 hash of the key did not match the hash that was provided.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECInvalidAlgorithm: { + Description: "The Encryption request you specified is not valid. Supported value: AES256.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECIncompatibleEncryption: { + Description: "Server Side Encryption with Customer provided key is incompatible with the encryption method specified", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSEInvalidEncryptionMethod: { + Description: "The encryption method specified is not valid.", + ArgumentName: "x-amz-server-side-encryption", + }, + InvalidArgSSECRequiresTLS: { + Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.", + ArgumentName: "x-amz-server-side-encryption", + }, +} + +// InvalidArgumentError is returned when a request argument is invalid. +// Produces and fields in the XML response, and +// a field when the expected gateway region is known. +type InvalidArgumentError struct { + Description string + ArgumentName string + ArgumentValue string + Region string `xml:",omitempty"` +} + +func (e InvalidArgumentError) BaseError() APIError { + return APIError{ + Code: "InvalidArgument", + Description: e.Description, + HTTPStatusCode: http.StatusBadRequest, + } +} + +// InvalidArgumentError http status code is always 400 +func (e InvalidArgumentError) StatusCode() int { return http.StatusBadRequest } + +func (e InvalidArgumentError) Error() string { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + enc := xml.NewEncoder(&bytesBuffer) + _ = enc.Encode(e) + return bytesBuffer.String() +} + +func (e InvalidArgumentError) XMLBody(requestID, hostID string) []byte { + return encodeResponse(struct { + XMLName xml.Name `xml:"Error"` + Code string + Message string + ArgumentName string `xml:"ArgumentName,omitempty"` + ArgumentValue string `xml:"ArgumentValue,omitempty"` + Region string `xml:"Region,omitempty"` + RequestId string `xml:"RequestId,omitempty"` + HostId string `xml:"HostId,omitempty"` + }{ + Code: "InvalidArgument", + Message: e.Description, + ArgumentName: e.ArgumentName, + ArgumentValue: e.ArgumentValue, + Region: e.Region, + RequestId: requestID, + HostId: hostID, + }) +} + +func (e InvalidArgumentError) HTMLBody(requestID, hostID string) []byte { + fields := []ErrorField{ + {Name: "ArgumentName", Value: e.ArgumentName}, + {Name: "ArgumentValue", Value: e.ArgumentValue}, + } + // Region is only set for the credential region mismatch; every other + // invalid argument error leaves it out. + if e.Region != "" { + fields = append(fields, ErrorField{Name: "Region", Value: e.Region}) + } + + return e.BaseError().encodeHTMLResponse(requestID, hostID, fields...) +} + +// ExpectedRegion implements RegionMismatchError. Only the credential region +// mismatch sets Region; every other invalid argument error returns "". +func (e InvalidArgumentError) ExpectedRegion() string { return e.Region } + +func GetInvalidArgumentErr(code InvalidArgErrorCode, value string) InvalidArgumentError { + err := invalidArgErrResponses[code] + err.ArgumentValue = value + return err +} + +func GetInvalidArgMaxLimiter(name, value string) InvalidArgumentError { + return InvalidArgumentError{ + ArgumentName: name, + ArgumentValue: value, + Description: fmt.Sprintf("Provided %s not an integer or within integer range", value), + } +} + +func GetInvalidArgNegativeMaxLimiter(name, value string) InvalidArgumentError { + return InvalidArgumentError{ + ArgumentName: name, + ArgumentValue: value, + Description: fmt.Sprintf("Argument %s must be an integer between 0 and 2147483647", value), + } +} + +func GetInvalidArgExceedingRange(size int64) InvalidArgumentError { + return InvalidArgumentError{ + ArgumentName: "x-amz-copy-source-range", + ArgumentValue: fmt.Sprint(size), + Description: fmt.Sprintf("Range specified is not valid for source object of size: %d", size), + } +} + +func GetInvalidArgObjectOwnership(value string) InvalidArgumentError { + return InvalidArgumentError{ + ArgumentName: "x-amz-object-ownership", + // no ArgumentValue is returned for this error + Description: fmt.Sprintf("Invalid x-amz-object-ownership header: %s", value), + } +} diff --git a/tests/integration/ListMultipartUploads.go b/tests/integration/ListMultipartUploads.go new file mode 100644 index 0000000..f163875 --- /dev/null +++ b/tests/integration/ListMultipartUploads.go @@ -0,0 +1,674 @@ +// Copyright 2023 Versity Software +// This file is licensed under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package integration + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/google/uuid" + "github.com/versity/versitygw/s3err" +) + +func ListMultipartUploads_non_existing_bucket(s *S3Conf) error { + testName := "ListMultipartUploads_non_existing_bucket" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + bucketName := getBucketName() + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucketName, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { + return err + } + + return nil + }) +} + +func ListMultipartUploads_empty_result(s *S3Conf) error { + testName := "ListMultipartUploads_empty_result" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + }) + cancel() + if err != nil { + return err + } + if len(out.Uploads) != 0 { + return fmt.Errorf("expected empty uploads, instead got %+v", + out.Uploads) + } + + return nil + }) +} + +func ListMultipartUploads_invalid_max_uploads(s *S3Conf) error { + testName := "ListMultipartUploads_invalid_max_uploads" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + maxUploads := int32(-3) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + MaxUploads: &maxUploads, + }) + cancel() + if err := checkApiErr(err, s3err.GetInvalidArgNegativeMaxLimiter("max-uploads", fmt.Sprint(maxUploads))); err != nil { + return err + } + + return nil + }) +} + +func ListMultipartUploads_max_uploads(s *S3Conf) error { + testName := "ListMultipartUploads_max_uploads" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := []types.MultipartUpload{} + for i := 1; i < 6; i++ { + out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i)) + if err != nil { + return err + } + uploads = append(uploads, types.MultipartUpload{ + UploadId: out.UploadId, + Key: out.Key, + StorageClass: types.StorageClassStandard, + }) + } + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + maxUploads := int32(2) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + MaxUploads: &maxUploads, + }) + cancel() + if err != nil { + return err + } + + if out.IsTruncated == nil { + return fmt.Errorf("unexpected nil is-truncated") + } + if out.MaxUploads == nil { + return fmt.Errorf("unexpected nil max-uploads") + } + if !*out.IsTruncated { + return fmt.Errorf("expected the output to be truncated") + } + if *out.MaxUploads != 2 { + return fmt.Errorf("expected max-uploads to be 2, instead got %v", + out.MaxUploads) + } + if ok := compareMultipartUploads(out.Uploads, uploads[:2]); !ok { + return fmt.Errorf("expected multipart uploads to be %v, instead got %v", + uploads[:2], out.Uploads) + } + if getString(out.NextKeyMarker) != getString(uploads[1].Key) { + return fmt.Errorf("expected next-key-marker to be %v, instead got %v", + getString(uploads[1].Key), getString(out.NextKeyMarker)) + } + if getString(out.NextUploadIdMarker) != getString(uploads[1].UploadId) { + return fmt.Errorf("expected next-upload-id-marker to be %v, instead got %v", + getString(uploads[1].UploadId), getString(out.NextUploadIdMarker)) + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: out.NextKeyMarker, + }) + cancel() + if err != nil { + return err + } + if ok := compareMultipartUploads(out.Uploads, uploads[2:]); !ok { + return fmt.Errorf("expected multipart uploads to be %v, instead got %v", + uploads[2:], out.Uploads) + } + + return nil + }) +} + +func ListMultipartUploads_exceeding_max_uploads(s *S3Conf) error { + testName := "ListMultipartUploads_exceeding_max_uploads" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + maxUploads := int32(1343235) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + MaxUploads: &maxUploads, + }) + cancel() + if err != nil { + return err + } + + if res.MaxUploads == nil { + return fmt.Errorf("unexpected nil max-uploads") + } + if *res.MaxUploads != 1000 { + return fmt.Errorf("expected max-uploads to be %v, instaed got %v", + 1000, *res.MaxUploads) + } + + return nil + }) +} + +func ListMultipartUploads_ignore_upload_id_marker(s *S3Conf) error { + testName := "ListMultipartUploads_ignore_upload_id_marker" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := []types.MultipartUpload{} + for i := 1; i < 6; i++ { + out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i)) + if err != nil { + return err + } + uploads = append(uploads, types.MultipartUpload{ + UploadId: out.UploadId, + Key: out.Key, + StorageClass: types.StorageClassStandard, + }) + } + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + UploadIdMarker: uploads[2].UploadId, + }) + cancel() + if err != nil { + return err + } + if !compareMultipartUploads(out.Uploads, uploads) { + return fmt.Errorf("expected multipart uploads to be %v, instead got %v", + uploads, out.Uploads) + } + + // should ignore invalid uploaId marker + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + UploadIdMarker: getPtr("invalid_uploadId_marker"), + }) + cancel() + if err != nil { + return err + } + if !compareMultipartUploads(out.Uploads, uploads) { + return fmt.Errorf("expected multipart uploads to be %v, instead got %v", + uploads, out.Uploads) + } + + return nil + }) +} + +func ListMultipartUploads_invalid_uploadId_marker(s *S3Conf) error { + testName := "ListMultipartUploads_invalid_uploadId_marker" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := make([]types.MultipartUpload, 0, 5) + for i := range 5 { + out, err := createMp(s3client, bucket, fmt.Sprintf("obj-%v", i)) + if err != nil { + return err + } + + uploads = append(uploads, types.MultipartUpload{ + UploadId: out.UploadId, + Key: out.Key, + StorageClass: types.StorageClassStandard, + }) + } + + // invalid UUID + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: getPtr("obj-2"), + UploadIdMarker: getPtr("invalid_uploadId_marker"), + }) + cancel() + if err := checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, "invalid_uploadId_marker")); err != nil { + return err + } + + // valid UUID, but not from the list + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + uploadIDMarker := uuid.New().String() + _, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: getPtr("obj-2"), + UploadIdMarker: &uploadIDMarker, + }) + cancel() + if err := checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, uploadIDMarker)); err != nil { + return err + } + + // uploadId marker and key marker mismatch + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: getPtr("obj-2"), + UploadIdMarker: uploads[4].UploadId, + }) + cancel() + return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgUploadIdMarker, *uploads[4].UploadId)) + }) +} + +func ListMultipartUploads_keyMarker_not_from_list(s *S3Conf) error { + testName := "ListMultipartUploads_keyMarker_not_from_list" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := make([]types.MultipartUpload, 0, 9) + for _, mp := range []struct { + key string + count int + }{ + {"bar", 3}, + {"baz", 4}, + {"foo", 2}, + } { + for range mp.count { + out, err := createMp(s3client, bucket, mp.key) + if err != nil { + return err + } + uploads = append(uploads, types.MultipartUpload{ + Key: out.Key, + UploadId: out.UploadId, + StorageClass: types.StorageClassStandard, + }) + // add an artificial delay, since some backends + // (e.g. azure, or posix on filesystems with coarse + // mtime resolution) can report the same identical + // creation/initiated time for uploads created back + // to back, making the resulting order non-deterministic + time.Sleep(time.Second) + } + } + + // without uploadId marker + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: getPtr("bat"), + }) + cancel() + if err != nil { + return err + } + + if !compareMultipartUploads(uploads[3:], out.Uploads) { + return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[3:], out.Uploads) + } + + // should start the listing after the specified uploadId marker + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + KeyMarker: getPtr("bat"), + UploadIdMarker: uploads[4].UploadId, + }) + cancel() + if err != nil { + return err + } + + if !compareMultipartUploads(uploads[5:], out.Uploads) { + return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[5:], out.Uploads) + } + + return nil + }) +} + +func ListMultipartUploads_delimiter_truncated(s *S3Conf) error { + testName := "ListMultipartUploads_delimiter_truncated" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := make([]types.MultipartUpload, 0, 6) + for _, key := range []string{ + "abc/something", + "foo/bar/baz", + "foo/quxx", + "xyz/hello", + "zzz/bca", + "some/very/nested/mp/object", + } { + out, err := createMp(s3client, bucket, key) + if err != nil { + return err + } + uploads = append(uploads, types.MultipartUpload{ + Key: out.Key, + UploadId: out.UploadId, + StorageClass: types.StorageClassStandard, + }) + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Delimiter: getPtr("/"), + MaxUploads: getPtr(int32(2)), + }) + cancel() + if err != nil { + return err + } + + if len(out.Uploads) != 0 { + return fmt.Errorf("expected empty uplodas list, instead got %v", out.Uploads) + } + expectedCps := []string{"abc/", "foo/"} + if !comparePrefixes(expectedCps, out.CommonPrefixes) { + return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out.CommonPrefixes) + } + if getString(out.NextKeyMarker) != "foo/" { + return fmt.Errorf("expected the next key marker to be 'foo/', instead got %s", getString(out.NextKeyMarker)) + } + if getString(out.NextUploadIdMarker) != getString(uploads[1].UploadId) { + return fmt.Errorf("expected the next upload id marker to be %s, instead got %s", getString(uploads[1].UploadId), getString(out.NextUploadIdMarker)) + } + if !*out.IsTruncated { + return fmt.Errorf("expected a truncated response") + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + out2, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Delimiter: getPtr("/"), + UploadIdMarker: out.NextUploadIdMarker, + KeyMarker: out.NextKeyMarker, + }) + cancel() + if err != nil { + return err + } + + if len(out2.Uploads) != 0 { + return fmt.Errorf("expected empty uplodas list, instead got %v", out2.Uploads) + } + expectedCps = []string{"foo/", "some/", "xyz/", "zzz/"} + if !comparePrefixes(expectedCps, out2.CommonPrefixes) { + return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out2.CommonPrefixes) + } + if getString(out2.KeyMarker) != "foo/" { + return fmt.Errorf("expected key marker to be 'foo/', instead got %s", getString(out2.KeyMarker)) + } + if getString(out2.UploadIdMarker) != getString(uploads[1].UploadId) { + return fmt.Errorf("expected the upload id marker to be %s, instead got %s", getString(uploads[1].UploadId), getString(out2.UploadIdMarker)) + } + if getString(out2.NextKeyMarker) != "" { + return fmt.Errorf("expected empty next key marker, instead got %s", getString(out2.NextKeyMarker)) + } + if getString(out2.NextUploadIdMarker) != "" { + return fmt.Errorf("expected empty next upload id marker, instead got %s", getString(out2.NextUploadIdMarker)) + } + if *out2.IsTruncated { + return fmt.Errorf("expected a non-truncated response") + } + + return nil + }) +} + +func ListMultipartUploads_prefix(s *S3Conf) error { + testName := "ListMultipartUploads_prefix" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + prefix := "foo" + uploads := make([]types.MultipartUpload, 0, 8) + for _, key := range []string{ + "abc/something", + "foo/bar/baz", + "foo/quxx", + "hello/world", + "xyz/hello", + "zzz/bca", + "some/very/nested/mp/object", + "foo/xyz", + } { + out, err := createMp(s3client, bucket, key) + if err != nil { + return err + } + + if strings.HasPrefix(key, prefix) { + uploads = append(uploads, types.MultipartUpload{ + Key: out.Key, + UploadId: out.UploadId, + StorageClass: types.StorageClassStandard, + }) + } + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Prefix: &prefix, + }) + cancel() + if err != nil { + return err + } + + if getString(out.Prefix) != prefix { + return fmt.Errorf("expected the prefix to be %s, instead got %s", prefix, getString(out.Prefix)) + } + if !compareMultipartUploads(out.Uploads, uploads) { + return fmt.Errorf("expected the uploads list to be %v, instead got %v", uploads, out.Uploads) + } + + return nil + }) +} + +func ListMultipartUploads_both_delimiter_and_prefix(s *S3Conf) error { + testName := "ListMultipartUploads_both_delimiter_and_prefix" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + for _, key := range []string{ + "foo/abc/bbb/aaa/c", + "abc/something", + "foo/bar/baz", + "foo/quxx", + "hello/world", + "foo/random/object", + "foo/random/another/object", + "xyz/hello", + "zzz/bca", + "some/very/nested/mp/object", + "foo/xyz", + } { + _, err := createMp(s3client, bucket, key) + if err != nil { + return err + } + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Delimiter: getPtr("/"), + Prefix: getPtr("foo/"), + }) + cancel() + if err != nil { + return err + } + + expectedCps := []string{"foo/abc/", "foo/bar/", "foo/random/"} + if !comparePrefixes(expectedCps, out.CommonPrefixes) { + return fmt.Errorf("expected the common prefixes to be %v, instead got %v", expectedCps, out.CommonPrefixes) + } + + return nil + }) +} + +func ListMultipartUploads_delimiter_no_matches(s *S3Conf) error { + testName := "ListMultipartUploads_delimiter_no_matches" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := make([]types.MultipartUpload, 0, 8) + for _, key := range []string{ + "abc/something", + "foo/bar/baz", + "foo/quxx", + "hello/world", + "xyz/hello", + "zzz/bca", + "some/very/nested/mp/object", + "foo/xyz", + } { + out, err := createMp(s3client, bucket, key) + if err != nil { + return err + } + + uploads = append(uploads, types.MultipartUpload{ + Key: out.Key, + UploadId: out.UploadId, + StorageClass: types.StorageClassStandard, + }) + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Delimiter: getPtr("delim"), + }) + cancel() + if err != nil { + return err + } + + sort.SliceStable(uploads, func(i, j int) bool { + return *uploads[i].Key < *uploads[j].Key + }) + + if !compareMultipartUploads(uploads, out.Uploads) { + return fmt.Errorf("expected the uploads to be %v, instead got %v", uploads, out.Uploads) + } + if len(out.CommonPrefixes) != 0 { + return fmt.Errorf("expected empty common prefixes, instead got %v", out.CommonPrefixes) + } + + return nil + }) +} + +func ListMultipartUploads_with_checksums(s *S3Conf) error { + testName := "ListMultipartUploads_with_checksums" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + uploads := []types.MultipartUpload{} + for _, el := range []struct { + obj string + algo types.ChecksumAlgorithm + t types.ChecksumType + }{ + { + obj: "obj-1", + algo: types.ChecksumAlgorithmCrc32, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-2", + algo: types.ChecksumAlgorithmCrc32c, + t: types.ChecksumTypeFullObject, + }, + { + obj: "obj-3", + algo: types.ChecksumAlgorithmSha1, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-4", + algo: types.ChecksumAlgorithmSha256, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-5", + algo: types.ChecksumAlgorithmCrc64nvme, + t: types.ChecksumTypeFullObject, + }, + { + obj: "obj-6", + algo: types.ChecksumAlgorithmSha512, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-7", + algo: types.ChecksumAlgorithmMd5, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-8", + algo: types.ChecksumAlgorithmXxhash64, + t: types.ChecksumTypeComposite, + }, + { + obj: "obj-9", + algo: types.ChecksumAlgorithmXxhash3, + t: types.ChecksumTypeComposite, + }, + // obj-91 to preserve the lexicographical order + { + obj: "obj-91", + algo: types.ChecksumAlgorithmXxhash128, + t: types.ChecksumTypeComposite, + }, + } { + key := el.obj + mp, err := createMp(s3client, bucket, key, withChecksum(el.algo), withChecksumType(el.t)) + if err != nil { + return err + } + + uploads = append(uploads, types.MultipartUpload{ + Key: &key, + UploadId: mp.UploadId, + StorageClass: types.StorageClassStandard, + ChecksumAlgorithm: el.algo, + ChecksumType: el.t, + }) + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + }) + cancel() + if err != nil { + return err + } + + if !compareMultipartUploads(res.Uploads, uploads) { + return fmt.Errorf("expected the final multipart uploads to be %v, instead got %v", + uploads, res.Uploads) + } + + return nil + }) +} diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index fe51f04..bd053a9 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -205,6 +205,8 @@ func TestPutObject(ts *TestState) { ts.Run(PutObject_with_metadata) } ts.Run(PutObject_success) + ts.Run(PutObject_sse_c_invalid_headers) + ts.Run(PutObject_unsupported_sse_not_implemented) ts.Run(PutObject_default_content_type) ts.Run(PutObject_overwrite_resets_attributes) ts.Run(PutObject_overwrite_multipart_object) @@ -238,6 +240,7 @@ func TestHeadObject(ts *TestState) { ts.Run(HeadObject_ranged_with_checksum_mode) } ts.Run(HeadObject_success) + ts.Run(HeadObject_sse_c_invalid_headers) ts.Run(HeadObject_overrides_success) ts.Run(HeadObject_overrides_presign_success) ts.Run(HeadObject_overrides_fail_public) @@ -417,6 +420,7 @@ func TestCopyObject(ts *TestState) { } ts.Run(CopyObject_with_special_characters) ts.Run(CopyObject_success) + ts.Run(CopyObject_sse_c_invalid_copy_source_headers) ts.Run(CopyObject_cross_bucket_server_side_copy) ts.Run(CopyObject_incorrect_source_bucket_expected_owner) } @@ -903,6 +907,28 @@ func TestNotImplementedActions(ts *TestState) { ts.Run(GetObjectAcl_not_implemented) } +// TestSSECNotImplemented is for backends that reject SSE-C rather than forward it. +func TestSSECNotImplemented(ts *TestState) { + ts.Run(PutObject_sse_c_not_implemented) + ts.Run(PostObject_sse_c_not_implemented) + ts.Run(GetObject_sse_c_not_implemented) + ts.Run(HeadObject_sse_c_not_implemented) + ts.Run(GetObjectAttributes_sse_c_not_implemented) + ts.Run(CopyObject_sse_c_not_implemented) + ts.Run(CreateMultipartUpload_sse_c_not_implemented) + ts.Run(UploadPart_sse_c_not_implemented) + ts.Run(UploadPartCopy_sse_c_not_implemented) + ts.Run(CompleteMultipartUpload_sse_c_not_implemented) +} + +func TestSSECTransport(ts *TestState) { + if ts.conf.tlsStatus { + TestSSECNotImplemented(ts) + return + } + ts.Run(PutObject_sse_c_requires_tls) +} + func TestWORMProtection(ts *TestState) { ts.Run(WORMProtection_bucket_object_lock_configuration_compliance_mode) ts.Run(WORMProtection_bucket_object_lock_configuration_governance_mode) @@ -1003,6 +1029,9 @@ func TestFullFlow(ts *TestState) { if ts.conf.versioningEnabled { TestVersioning(ts) } + if ts.conf.azureTests { + TestSSECTransport(ts) + } TestGatewayIAM(ts) TestServer(ts) } @@ -1033,6 +1062,7 @@ func TestPosix(ts *TestState) { if !ts.conf.versioningEnabled { TestVersioningDisabled(ts) } + TestSSECTransport(ts) } func TestScoutfs(ts *TestState) { @@ -1134,6 +1164,7 @@ func TestScoutfs(ts *TestState) { ts.Run(ObjectTagging_trailing_slash_counterpart) ts.Run(ObjectLock_trailing_slash_counterpart) ts.Run(DeleteObject_directory_not_empty) + TestSSECTransport(ts) } func TestGatewayIAM(ts *TestState) { @@ -2162,7 +2193,6 @@ func TestRouter(ts *TestState) { ts.Run(RouterObjectAnnotationNotImplemented) ts.Run(RouterRenameObjectNotImplemented) ts.Run(RouterObjectTorrentMethodNotAllowed) - ts.Run(RouterInvalidURLEscape) } func TestPostObject(ts *TestState) { @@ -3540,6 +3570,21 @@ func GetIntTests() IntTests { "PutObject_with_slashes": PutObject_with_slashes, "PutObject_race_with_delete": PutObject_race_with_delete, "CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj, + "PutObject_sse_c_not_implemented": PutObject_sse_c_not_implemented, + "PutObject_unsupported_sse_not_implemented": PutObject_unsupported_sse_not_implemented, + "PutObject_sse_c_requires_tls": PutObject_sse_c_requires_tls, + "PostObject_sse_c_not_implemented": PostObject_sse_c_not_implemented, + "GetObject_sse_c_not_implemented": GetObject_sse_c_not_implemented, + "HeadObject_sse_c_not_implemented": HeadObject_sse_c_not_implemented, + "GetObjectAttributes_sse_c_not_implemented": GetObjectAttributes_sse_c_not_implemented, + "CopyObject_sse_c_not_implemented": CopyObject_sse_c_not_implemented, + "CreateMultipartUpload_sse_c_not_implemented": CreateMultipartUpload_sse_c_not_implemented, + "UploadPart_sse_c_not_implemented": UploadPart_sse_c_not_implemented, + "UploadPartCopy_sse_c_not_implemented": UploadPartCopy_sse_c_not_implemented, + "CompleteMultipartUpload_sse_c_not_implemented": CompleteMultipartUpload_sse_c_not_implemented, + "PutObject_sse_c_invalid_headers": PutObject_sse_c_invalid_headers, + "HeadObject_sse_c_invalid_headers": HeadObject_sse_c_invalid_headers, + "CopyObject_sse_c_invalid_copy_source_headers": CopyObject_sse_c_invalid_copy_source_headers, "IAM_user_access_denied": IAM_user_access_denied, "IAM_userplus_access_denied": IAM_userplus_access_denied, "IAM_userplus_CreateBucket": IAM_userplus_CreateBucket, @@ -3750,7 +3795,6 @@ func GetIntTests() IntTests { "RouterObjectAnnotationNotImplemented": RouterObjectAnnotationNotImplemented, "RouterRenameObjectNotImplemented": RouterRenameObjectNotImplemented, "RouterObjectTorrentMethodNotAllowed": RouterObjectTorrentMethodNotAllowed, - "RouterInvalidURLEscape": RouterInvalidURLEscape, "UnsignedStreaminPayloadTrailer_malformed_trailer": UnsignedStreaminPayloadTrailer_malformed_trailer, "UnsignedStreamingPayloadTrailer_aborted_connection": UnsignedStreamingPayloadTrailer_aborted_connection, "UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length": UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length, diff --git a/tests/test_rest_list_multipart_uploads.sh b/tests/test_rest_list_multipart_uploads.sh new file mode 100644 index 0000000..6038dad --- /dev/null +++ b/tests/test_rest_list_multipart_uploads.sh @@ -0,0 +1,231 @@ +#!/usr/bin/env bats + +# Copyright 2026 Versity Software +# This file is licensed under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +load ./bats-support/load +load ./bats-assert/load + +source ./tests/drivers/create_bucket/create_bucket_rest.sh +source ./tests/drivers/list_multipart_uploads/list_multipart_uploads_rest.sh +source ./tests/drivers/file.sh +source ./tests/setup.sh + +# tags: curl,ListMultipartUploads,multipart +@test "REST - ListMultipartUploads - Initiator and Owner data shown" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2142" + fi + run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME" + assert_success + read -r bucket_name test_file <<< "$output" + + run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id" + assert_success + upload_id=$output + + local owner_id initiator_id + if [ "$DIRECT" == "true" ]; then + owner_id="$AWS_CANONICAL_ID" + initiator_id="arn:aws:iam::$DIRECT_AWS_USER_ID:user/$DIRECT_S3_ROOT_ACCOUNT_NAME" + else + owner_id="$AWS_ACCESS_KEY_ID" + initiator_id="$AWS_ACCESS_KEY_ID" + fi + + run list_multipart_uploads_check_user_data "$bucket_name" "$upload_id" "$owner_id" "$initiator_id" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,uploadId,next-key-marker,next-upload-id-marker +@test "REST - ListMultipartUploads - NextKeyMarker and NextUploadIdMarker set to last item" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2144" + fi + run setup_bucket_and_files_v3 "$BUCKET_ONE_NAME" 2 + assert_success + read -r bucket_name test_file_one test_file_two <<< "$output" + + local upload_id_one upload_id_two + run create_multipart_upload_rest "$bucket_name" "$test_file_one" "" "parse_upload_id" + assert_success + upload_id_one=$output + + run create_multipart_upload_rest "$bucket_name" "$test_file_two" "" "parse_upload_id" + assert_success + upload_id_two=$output + + local last_key last_upload_id + if [[ "$test_file_one" < "$test_file_two" ]]; then + last_key="$test_file_two" + last_upload_id="$upload_id_two" + else + last_key="$test_file_one" + last_upload_id="$upload_id_one" + fi + + run list_multipart_uploads_check_next_values "$bucket_name" "$last_key" "$last_upload_id" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,uploadId,key-marker +@test "REST - ListMultipartUploads - uploadId and key combo work" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2149" + fi + run setup_bucket_and_files_v3 "$BUCKET_ONE_NAME" 2 + assert_success + read -r bucket_name test_file_one test_file_two <<< "$output" + + local upload_id_one upload_id_two + run create_multipart_upload_rest "$bucket_name" "$test_file_one" "" "parse_upload_id" + assert_success + upload_id_one=$output + + run create_multipart_upload_rest "$bucket_name" "$test_file_two" "" "parse_upload_id" + assert_success + upload_id_two=$output + + local first_key first_upload_id second_key second_upload_id + if [[ "$test_file_one" < "$test_file_two" ]]; then + first_key="$test_file_one" + first_upload_id="$upload_id_one" + second_key="$test_file_two" + second_upload_id="$upload_id_two" + else + first_key="$test_file_two" + first_upload_id="$upload_id_two" + second_key="$test_file_one" + second_upload_id="$upload_id_one" + fi + + run list_multipart_uploads_get_check_next_values "$bucket_name" "$second_key" "$second_upload_id" "$first_key" "$first_upload_id" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,uploadId +@test "REST - ListMultipartUploads - uploadId without key should be ignored" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2150" + fi + run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME" + assert_success + read -r bucket_name test_file <<< "$output" + + run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id" + assert_success + upload_id_one=$output + + run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id" + assert_success + upload_id_two=$output + + local first_upload_id second_upload_id + if [[ "$upload_id_one" < "$upload_id_two" ]]; then + first_upload_id="$upload_id_one" + second_upload_id="$upload_id_two" + else + first_upload_id="$upload_id_two" + second_upload_id="$upload_id_one" + fi + + run list_multipart_uploads_check_no_upload_id_in_response "$bucket_name" "$second_upload_id" "$first_upload_id" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,prefix,delimiter +@test "REST - ListMultipartUploads - prefix and delimiter" { + file_names=("a-b-1.txt" "a-b-2.txt" "a-b/c-1.txt" "a-b/c-2.txt" "a-b/d.txt" "a/c.txt") + local prefix="a-" + run create_test_files_and_folders "${file_names[@]}" + assert_success + + run setup_bucket_v3 "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + for file_name in "${file_names[@]}"; do + run create_multipart_upload_rest "$bucket_name" "$file_name" "" "" + assert_success + done + + run list_multipart_uploads_rest "$bucket_name" + assert_success + log 5 "uploads: $output" + + run list_uploads_with_prefix_and_delimiter_check_results "$bucket_name" "$prefix" "/" "a-b/" "--" "a-b-1.txt" "a-b-2.txt" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,encoding-type +@test "REST - ListMultipartUploads - encoding type" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2156" + fi + run setup_bucket_v3 "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run create_test_files_with_prefix " " + assert_success + base_file_name="$output" + file_name="${base_file_name}(test)+" + log 5 "file name: '$file_name'" + + run send_rest_go_command_callback "200" "parse_upload_id" "-method" "POST" "-query" "uploads" "-bucketName" "$bucket_name" "-objectKey" "$file_name" + assert_success + upload_id=$output + + run list_multipart_uploads_check_encoding "$bucket_name" "&encoding-type=url" "${base_file_name/ /+}%28test%29%2B" + assert_success + + run list_multipart_uploads_check_encoding "$bucket_name" "" "$file_name" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,encoding-type,invalid-query +@test "REST - ListMultipartUploads - invalid encoding type" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2156" + fi + run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME" + assert_success + read -r bucket_name test_file <<< "$output" + + run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id" + assert_success + upload_id_one=$output + + local invalid_encoding="abc" + run send_rest_go_command_expect_error_with_arg_name_value "400" "InvalidArgument" "Invalid Encoding Method specified" \ + "encoding-type" "$invalid_encoding" "-bucketName" "$bucket_name" "-query" "uploads&encoding-type=$invalid_encoding" + assert_success +} + +# tags: curl,ListMultipartUploads,multipart,uploadId,invalid-query +@test "REST - ListMultipartUploads - uploads/uploadId combo" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/2155" + fi + run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME" + assert_success + read -r bucket_name test_file <<< "$output" + + run create_multipart_upload_rest "$bucket_name" "$test_file" "" "parse_upload_id" + assert_success + upload_id_one=$output + + run send_rest_go_command_expect_error "400" "InvalidArgument" "Conflicting query string parameters" "-query" "uploads&uploadId=$upload_id_one" "-bucketName" "$bucket_name" + assert_success +}