diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go
index fae7a415..f3487983 100644
--- a/s3api/controllers/base.go
+++ b/s3api/controllers/base.go
@@ -1938,385 +1938,6 @@ const (
timefmt = "Mon, 02 Jan 2006 15:04:05 GMT"
)
-func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
- bucket := ctx.Params("bucket")
- key := ctx.Params("key")
- keyEnd := ctx.Params("*1")
- uploadId := ctx.Query("uploadId")
- acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
- isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
- IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
- parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
- contentType := ctx.Get("Content-Type")
- contentDisposition := ctx.Get("Content-Disposition")
- contentLanguage := ctx.Get("Content-Language")
- cacheControl := ctx.Get("Cache-Control")
- contentEncoding := ctx.Get("Content-Encoding")
- tagging := ctx.Get("X-Amz-Tagging")
-
- if keyEnd != "" {
- key = strings.Join([]string{key, keyEnd}, "/")
- }
-
- path := ctx.Path()
- if path[len(path)-1:] == "/" && key[len(key)-1:] != "/" {
- key = key + "/"
- }
-
- if ctx.Request().URI().QueryArgs().Has("restore") {
- var restoreRequest types.RestoreRequest
- if err := xml.Unmarshal(ctx.Body(), &restoreRequest); err != nil {
- if !errors.Is(err, io.EOF) {
- if c.debug {
- debuglogger.Logf("failed to parse the request body: %v", err)
- }
- return SendResponse(ctx, s3err.GetAPIError(s3err.ErrMalformedXML),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionRestoreObject,
- BucketOwner: parsedAcl.Owner,
- })
- }
- }
- err := auth.VerifyAccess(ctx.Context(), c.be,
- auth.AccessOptions{
- Readonly: c.readonly,
- Acl: parsedAcl,
- AclPermission: auth.PermissionWrite,
- IsRoot: isRoot,
- Acc: acct,
- Bucket: bucket,
- Object: key,
- Action: auth.RestoreObjectAction,
- IsBucketPublic: IsBucketPublic,
- })
- if err != nil {
- return SendResponse(ctx, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionRestoreObject,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- err = c.be.RestoreObject(ctx.Context(), &s3.RestoreObjectInput{
- Bucket: &bucket,
- Key: &key,
- RestoreRequest: &restoreRequest,
- })
- return SendResponse(ctx, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- EvSender: c.evSender,
- Action: metrics.ActionRestoreObject,
- BucketOwner: parsedAcl.Owner,
- EventName: s3event.EventObjectRestoreCompleted,
- })
- }
-
- if ctx.Request().URI().QueryArgs().Has("select") && ctx.Query("select-type") == "2" {
- var payload s3response.SelectObjectContentPayload
-
- err := xml.Unmarshal(ctx.Body(), &payload)
- if err != nil {
- if c.debug {
- debuglogger.Logf("error unmarshalling select object content: %v", err)
- }
- return SendXMLResponse(ctx, nil,
- s3err.GetAPIError(s3err.ErrMalformedXML),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionSelectObjectContent,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- err = auth.VerifyAccess(ctx.Context(), c.be,
- auth.AccessOptions{
- Readonly: c.readonly,
- Acl: parsedAcl,
- AclPermission: auth.PermissionRead,
- IsRoot: isRoot,
- Acc: acct,
- Bucket: bucket,
- Object: key,
- Action: auth.GetObjectAction,
- IsBucketPublic: IsBucketPublic,
- })
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionSelectObjectContent,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- sw := c.be.SelectObjectContent(ctx.Context(),
- &s3.SelectObjectContentInput{
- Bucket: &bucket,
- Key: &key,
- Expression: payload.Expression,
- ExpressionType: payload.ExpressionType,
- InputSerialization: payload.InputSerialization,
- OutputSerialization: payload.OutputSerialization,
- RequestProgress: payload.RequestProgress,
- ScanRange: payload.ScanRange,
- })
-
- ctx.Context().SetBodyStreamWriter(sw)
-
- return nil
- }
-
- if uploadId != "" {
- data := struct {
- Parts []types.CompletedPart `xml:"Part"`
- }{}
-
- err := xml.Unmarshal(ctx.Body(), &data)
- if err != nil {
- if c.debug {
- debuglogger.Logf("error unmarshalling complete multipart upload: %v", err)
- }
- return SendXMLResponse(ctx, nil,
- s3err.GetAPIError(s3err.ErrMalformedXML),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- if len(data.Parts) == 0 {
- if c.debug {
- debuglogger.Logf("empty parts provided for complete multipart upload")
- }
- return SendXMLResponse(ctx, nil,
- s3err.GetAPIError(s3err.ErrEmptyParts),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- var mpuObjectSize *int64
- mpuObjSizeHdr := ctx.Get("X-Amz-Mp-Object-Size")
- if mpuObjSizeHdr != "" {
- val, err := strconv.ParseInt(mpuObjSizeHdr, 10, 64)
- //TODO: Not sure if invalid request should be returned
- if err != nil {
- if c.debug {
- debuglogger.Logf("invalid value for 'x-amz-mp-objects-size' header: %v", err)
- }
- return SendXMLResponse(ctx, nil,
- s3err.GetAPIError(s3err.ErrInvalidRequest),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- if val < 0 {
- debuglogger.Logf("value for 'x-amz-mp-objects-size' header is less than 0: %v", val)
- return SendXMLResponse(ctx, nil,
- s3err.GetInvalidMpObjectSizeErr(val),
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- mpuObjectSize = &val
- }
-
- err = auth.VerifyAccess(ctx.Context(), c.be,
- auth.AccessOptions{
- Readonly: c.readonly,
- Acl: parsedAcl,
- AclPermission: auth.PermissionWrite,
- IsRoot: isRoot,
- Acc: acct,
- Bucket: bucket,
- Object: key,
- Action: auth.PutObjectAction,
- IsBucketPublic: IsBucketPublic,
- })
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- _, checksums, err := utils.ParseChecksumHeaders(ctx)
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- checksumType := types.ChecksumType(ctx.Get("x-amz-checksum-type"))
- err = utils.IsChecksumTypeValid(checksumType)
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- res, versid, err := c.be.CompleteMultipartUpload(ctx.Context(),
- &s3.CompleteMultipartUploadInput{
- Bucket: &bucket,
- Key: &key,
- UploadId: &uploadId,
- MultipartUpload: &types.CompletedMultipartUpload{
- Parts: data.Parts,
- },
- MpuObjectSize: mpuObjectSize,
- ChecksumCRC32: backend.GetPtrFromString(checksums[types.ChecksumAlgorithmCrc32]),
- ChecksumCRC32C: backend.GetPtrFromString(checksums[types.ChecksumAlgorithmCrc32c]),
- ChecksumSHA1: backend.GetPtrFromString(checksums[types.ChecksumAlgorithmSha1]),
- ChecksumSHA256: backend.GetPtrFromString(checksums[types.ChecksumAlgorithmSha256]),
- ChecksumCRC64NVME: backend.GetPtrFromString(checksums[types.ChecksumAlgorithmCrc64nvme]),
- ChecksumType: checksumType,
- })
- if err == nil {
- if versid != "" {
- utils.SetResponseHeaders(ctx, []utils.CustomHeader{
- {
- Key: "x-amz-version-id",
- Value: versid,
- },
- })
- }
- return SendXMLResponse(ctx, res, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- EvSender: c.evSender,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- ObjectETag: res.ETag,
- EventName: s3event.EventCompleteMultipartUpload,
- VersionId: backend.GetPtrFromString(versid),
- })
- }
- return SendXMLResponse(ctx, res, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCompleteMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- err := auth.VerifyAccess(ctx.Context(), c.be,
- auth.AccessOptions{
- Readonly: c.readonly,
- Acl: parsedAcl,
- AclPermission: auth.PermissionWrite,
- IsRoot: isRoot,
- Acc: acct,
- Bucket: bucket,
- Object: key,
- Action: auth.PutObjectAction,
- })
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCreateMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- objLockState, err := utils.ParsObjectLockHdrs(ctx)
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCreateMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- metadata := utils.GetUserMetaData(&ctx.Request().Header)
-
- checksumAlgorithm, checksumType, err := utils.ParseCreateMpChecksumHeaders(ctx)
- if err != nil {
- return SendXMLResponse(ctx, nil, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCreateMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
- }
-
- expires := ctx.Get("Expires")
-
- res, err := c.be.CreateMultipartUpload(ctx.Context(),
- s3response.CreateMultipartUploadInput{
- Bucket: &bucket,
- Key: &key,
- Tagging: &tagging,
- ContentType: &contentType,
- ContentEncoding: &contentEncoding,
- ContentDisposition: &contentDisposition,
- ContentLanguage: &contentLanguage,
- CacheControl: &cacheControl,
- Expires: &expires,
- ObjectLockRetainUntilDate: &objLockState.RetainUntilDate,
- ObjectLockMode: objLockState.ObjectLockMode,
- ObjectLockLegalHoldStatus: objLockState.LegalHoldStatus,
- Metadata: metadata,
- ChecksumAlgorithm: checksumAlgorithm,
- ChecksumType: checksumType,
- })
- if err == nil {
- if checksumAlgorithm != "" {
- utils.SetResponseHeaders(ctx, []utils.CustomHeader{
- {
- Key: "x-amz-checksum-algorithm",
- Value: string(checksumAlgorithm),
- },
- })
- }
- }
- return SendXMLResponse(ctx, res, err,
- &MetaOpts{
- Logger: c.logger,
- MetricsMng: c.mm,
- Action: metrics.ActionCreateMultipartUpload,
- BucketOwner: parsedAcl.Owner,
- })
-}
-
type MetaOpts struct {
Logger s3log.AuditLogger
EvSender s3event.S3EventSender
diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go
index 652e07e6..65eb7e4e 100644
--- a/s3api/controllers/base_test.go
+++ b/s3api/controllers/base_test.go
@@ -15,7 +15,6 @@
package controllers
import (
- "bufio"
"context"
"encoding/json"
"fmt"
@@ -837,269 +836,6 @@ func TestS3ApiController_DeleteObjects(t *testing.T) {
}
}
-func TestS3ApiController_DeleteActions(t *testing.T) {
- type args struct {
- req *http.Request
- }
-
- app := fiber.New()
- s3ApiController := S3ApiController{
- be: &BackendMock{
- GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
- return acldata, nil
- },
- DeleteObjectFunc: func(contextMoqParam context.Context, deleteObjectInput *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
- return &s3.DeleteObjectOutput{}, nil
- },
- AbortMultipartUploadFunc: func(context.Context, *s3.AbortMultipartUploadInput) error {
- return nil
- },
- DeleteObjectTaggingFunc: func(_ context.Context, bucket, object string) error {
- return nil
- },
- GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
- return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
- },
- },
- }
-
- app.Use(func(ctx *fiber.Ctx) error {
- utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"})
- utils.ContextKeyIsRoot.Set(ctx, true)
- utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{})
- return ctx.Next()
- })
- app.Delete("/:bucket/:key/*", s3ApiController.DeleteActions)
-
- // Error case
- appErr := fiber.New()
-
- s3ApiControllerErr := S3ApiController{be: &BackendMock{
- GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
- return acldata, nil
- },
- DeleteObjectFunc: func(contextMoqParam context.Context, deleteObjectInput *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
- return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
- },
- GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
- return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
- },
- }}
-
- appErr.Use(func(ctx *fiber.Ctx) error {
- utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"})
- utils.ContextKeyIsRoot.Set(ctx, true)
- utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{})
- return ctx.Next()
- })
- appErr.Delete("/:bucket/:key/*", s3ApiControllerErr.DeleteActions)
-
- tests := []struct {
- name string
- app *fiber.App
- args args
- wantErr bool
- statusCode int
- }{
- {
- name: "Abort-multipart-upload-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key?uploadId=324234", nil),
- },
- wantErr: false,
- statusCode: 204,
- },
- {
- name: "Remove-object-tagging-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key/key2?tagging", nil),
- },
- wantErr: false,
- statusCode: 204,
- },
- {
- name: "Delete-object-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key", nil),
- },
- wantErr: false,
- statusCode: 204,
- },
- {
- name: "Delete-object-error",
- app: appErr,
- args: args{
- req: httptest.NewRequest(http.MethodDelete, "/my-bucket/invalid-key", nil),
- },
- wantErr: false,
- statusCode: 404,
- },
- }
- for _, tt := range tests {
- resp, err := tt.app.Test(tt.args.req)
-
- if (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.DeleteActions() error = %v, wantErr %v", err, tt.wantErr)
- }
-
- if resp.StatusCode != tt.statusCode {
- t.Errorf("S3ApiController.DeleteActions() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
- }
- }
-}
-
-func TestS3ApiController_CreateActions(t *testing.T) {
- type args struct {
- req *http.Request
- }
- app := fiber.New()
- s3ApiController := S3ApiController{
- be: &BackendMock{
- GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
- return acldata, nil
- },
- RestoreObjectFunc: func(context.Context, *s3.RestoreObjectInput) error {
- return nil
- },
- CompleteMultipartUploadFunc: func(context.Context, *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) {
- return s3response.CompleteMultipartUploadResult{}, "", nil
- },
- CreateMultipartUploadFunc: func(context.Context, s3response.CreateMultipartUploadInput) (s3response.InitiateMultipartUploadResult, error) {
- return s3response.InitiateMultipartUploadResult{}, nil
- },
- SelectObjectContentFunc: func(context.Context, *s3.SelectObjectContentInput) func(w *bufio.Writer) {
- return func(w *bufio.Writer) {}
- },
- },
- }
-
- bdy := `
-
- string
- string
-
- `
-
- completMpBody := `
-
-
- etag
- 1
-
-
- `
-
- completMpEmptyBody := `
-
- `
-
- app.Use(func(ctx *fiber.Ctx) error {
- utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"})
- utils.ContextKeyIsRoot.Set(ctx, true)
- utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{})
- return ctx.Next()
- })
- app.Post("/:bucket/:key/*", s3ApiController.CreateActions)
-
- invChecksumAlgo := httptest.NewRequest(http.MethodPost, "/my-bucket/my-key", nil)
- invChecksumAlgo.Header.Set("X-Amz-Checksum-Algorithm", "invalid_checksum_algorithm")
-
- tests := []struct {
- name string
- app *fiber.App
- args args
- wantErr bool
- statusCode int
- }{
- {
- name: "Restore-object-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?restore", nil),
- },
- wantErr: false,
- statusCode: 200,
- },
- {
- name: "Select-object-content-invalid-body",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?select&select-type=2", nil),
- },
- wantErr: false,
- statusCode: 400,
- },
- {
- name: "Select-object-content-invalid-body",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?select&select-type=2", strings.NewReader(bdy)),
- },
- wantErr: false,
- statusCode: 200,
- },
- {
- name: "Complete-multipart-upload-error",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", nil),
- },
- wantErr: false,
- statusCode: 400,
- },
- {
- name: "Complete-multipart-upload-empty-parts",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(completMpEmptyBody)),
- },
- wantErr: false,
- statusCode: 400,
- },
- {
- name: "Complete-multipart-upload-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(completMpBody)),
- },
- wantErr: false,
- statusCode: 200,
- },
- {
- name: "Create-multipart-upload-invalid-checksum-algorithm",
- app: app,
- args: args{
- req: invChecksumAlgo,
- },
- wantErr: false,
- statusCode: 400,
- },
- {
- name: "Create-multipart-upload-success",
- app: app,
- args: args{
- req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key", nil),
- },
- wantErr: false,
- statusCode: 200,
- },
- }
- for _, tt := range tests {
- resp, err := tt.app.Test(tt.args.req)
-
- if (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.CreateActions() error = %v, wantErr %v", err, tt.wantErr)
- }
-
- if resp.StatusCode != tt.statusCode {
- t.Errorf("S3ApiController.CreateActions() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
- }
- }
-}
-
func Test_XMLresponse(t *testing.T) {
type args struct {
ctx *fiber.Ctx
diff --git a/s3api/controllers/object-delete.go b/s3api/controllers/object-delete.go
new file mode 100644
index 00000000..2cbebae2
--- /dev/null
+++ b/s3api/controllers/object-delete.go
@@ -0,0 +1,207 @@
+// 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 controllers
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/gofiber/fiber/v2"
+ "github.com/versity/versitygw/auth"
+ "github.com/versity/versitygw/metrics"
+ "github.com/versity/versitygw/s3api/utils"
+ "github.com/versity/versitygw/s3event"
+)
+
+func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.DeleteObjectTaggingAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionDeleteObjectTagging,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ err = c.be.DeleteObjectTagging(ctx.Context(), bucket, key)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Status: http.StatusNoContent,
+ Action: metrics.ActionDeleteObjectTagging,
+ BucketOwner: parsedAcl.Owner,
+ EventName: s3event.EventObjectTaggingDelete,
+ },
+ }, err
+}
+
+func (c S3ApiController) AbortMultipartUplaod(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ uploadId := ctx.Query("uploadId")
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.AbortMultipartUploadAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionAbortMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ err = c.be.AbortMultipartUpload(ctx.Context(),
+ &s3.AbortMultipartUploadInput{
+ UploadId: &uploadId,
+ Bucket: &bucket,
+ Key: &key,
+ })
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionAbortMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ Status: http.StatusNoContent,
+ },
+ }, err
+}
+
+func (c S3ApiController) DeleteObject(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ versionId := ctx.Query("versionId")
+ bypass := strings.EqualFold(ctx.Get("X-Amz-Bypass-Governance-Retention"), "true")
+ // context locals
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ //TODO: check s3:DeleteObjectVersion policy in case a use tries to delete a version of an object
+
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.DeleteObjectAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionDeleteObject,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ err = auth.CheckObjectAccess(
+ ctx.Context(),
+ bucket,
+ acct.Access,
+ []types.ObjectIdentifier{
+ {
+ Key: &key,
+ VersionId: &versionId,
+ },
+ },
+ bypass,
+ IsBucketPublic,
+ c.be,
+ )
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionDeleteObject,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ res, err := c.be.DeleteObject(ctx.Context(),
+ &s3.DeleteObjectInput{
+ Bucket: &bucket,
+ Key: &key,
+ VersionId: &versionId,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionDeleteObject,
+ BucketOwner: parsedAcl.Owner,
+ EventName: s3event.EventObjectRemovedDelete,
+ Status: http.StatusNoContent,
+ },
+ }, err
+ }
+
+ headers := map[string]*string{
+ "x-amz-version-id": res.VersionId,
+ }
+
+ if res.DeleteMarker != nil && *res.DeleteMarker {
+ headers["x-amz-delete-marker"] = utils.GetStringPtr("true")
+ }
+
+ return &Response{
+ Headers: headers,
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionDeleteObject,
+ BucketOwner: parsedAcl.Owner,
+ EventName: s3event.EventObjectRemovedDelete,
+ Status: http.StatusNoContent,
+ },
+ }, nil
+}
diff --git a/s3api/controllers/object-post.go b/s3api/controllers/object-post.go
new file mode 100644
index 00000000..bbe96643
--- /dev/null
+++ b/s3api/controllers/object-post.go
@@ -0,0 +1,376 @@
+// 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 controllers
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/gofiber/fiber/v2"
+ "github.com/versity/versitygw/auth"
+ "github.com/versity/versitygw/metrics"
+ "github.com/versity/versitygw/s3api/debuglogger"
+ "github.com/versity/versitygw/s3api/utils"
+ "github.com/versity/versitygw/s3err"
+ "github.com/versity/versitygw/s3event"
+ "github.com/versity/versitygw/s3response"
+)
+
+func (c S3ApiController) RestoreObject(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ var restoreRequest types.RestoreRequest
+ if err := xml.Unmarshal(ctx.Body(), &restoreRequest); err != nil {
+ debuglogger.Logf("failed to parse the request body: %v", err)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionRestoreObject,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetAPIError(s3err.ErrMalformedXML)
+ }
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.RestoreObjectAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionRestoreObject,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ err = c.be.RestoreObject(ctx.Context(), &s3.RestoreObjectInput{
+ Bucket: &bucket,
+ Key: &key,
+ RestoreRequest: &restoreRequest,
+ })
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionRestoreObject,
+ BucketOwner: parsedAcl.Owner,
+ EventName: s3event.EventObjectRestoreCompleted,
+ },
+ }, err
+}
+
+func (c S3ApiController) SelectObjectContent(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ var payload s3response.SelectObjectContentPayload
+
+ err := xml.Unmarshal(ctx.Body(), &payload)
+ if err != nil {
+ debuglogger.Logf("error unmarshalling select object content: %v", err)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionSelectObjectContent,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetAPIError(s3err.ErrMalformedXML)
+ }
+
+ err = auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionRead,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.GetObjectAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionSelectObjectContent,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ sw := c.be.SelectObjectContent(ctx.Context(),
+ &s3.SelectObjectContentInput{
+ Bucket: &bucket,
+ Key: &key,
+ Expression: payload.Expression,
+ ExpressionType: payload.ExpressionType,
+ InputSerialization: payload.InputSerialization,
+ OutputSerialization: payload.OutputSerialization,
+ RequestProgress: payload.RequestProgress,
+ ScanRange: payload.ScanRange,
+ })
+
+ ctx.Context().SetBodyStreamWriter(sw)
+
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionSelectObjectContent,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, nil
+}
+
+func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ contentType := ctx.Get("Content-Type")
+ contentDisposition := ctx.Get("Content-Disposition")
+ contentLanguage := ctx.Get("Content-Language")
+ cacheControl := ctx.Get("Cache-Control")
+ contentEncoding := ctx.Get("Content-Encoding")
+ tagging := ctx.Get("X-Amz-Tagging")
+ expires := ctx.Get("Expires")
+ metadata := utils.GetUserMetaData(&ctx.Request().Header)
+ // context locals
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.PutObjectAction,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCreateMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ objLockState, err := utils.ParsObjectLockHdrs(ctx)
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCreateMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ checksumAlgorithm, checksumType, err := utils.ParseCreateMpChecksumHeaders(ctx)
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCreateMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ res, err := c.be.CreateMultipartUpload(ctx.Context(),
+ s3response.CreateMultipartUploadInput{
+ Bucket: &bucket,
+ Key: &key,
+ Tagging: &tagging,
+ ContentType: &contentType,
+ ContentEncoding: &contentEncoding,
+ ContentDisposition: &contentDisposition,
+ ContentLanguage: &contentLanguage,
+ CacheControl: &cacheControl,
+ Expires: &expires,
+ ObjectLockRetainUntilDate: &objLockState.RetainUntilDate,
+ ObjectLockMode: objLockState.ObjectLockMode,
+ ObjectLockLegalHoldStatus: objLockState.LegalHoldStatus,
+ Metadata: metadata,
+ ChecksumAlgorithm: checksumAlgorithm,
+ ChecksumType: checksumType,
+ })
+ var headers map[string]*string
+ if err == nil {
+ headers = map[string]*string{
+ "x-amz-checksum-algorithm": utils.ConvertToStringPtr(checksumAlgorithm),
+ }
+ }
+ return &Response{
+ Headers: headers,
+ Data: res,
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCreateMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+}
+
+func (c S3ApiController) CompleteMultipartUpload(ctx *fiber.Ctx) (*Response, error) {
+ bucket := ctx.Params("bucket")
+ key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
+ uploadId := ctx.Query("uploadId")
+ mpuObjSizeHdr := ctx.Get("X-Amz-Mp-Object-Size")
+ checksumType := types.ChecksumType(ctx.Get("x-amz-checksum-type"))
+ // context locals
+ acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
+ isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
+ IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
+ parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
+
+ err := auth.VerifyAccess(ctx.Context(), c.be,
+ auth.AccessOptions{
+ Readonly: c.readonly,
+ Acl: parsedAcl,
+ AclPermission: auth.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.PutObjectAction,
+ IsBucketPublic: IsBucketPublic,
+ })
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ data := struct {
+ Parts []types.CompletedPart `xml:"Part"`
+ }{}
+
+ err = xml.Unmarshal(ctx.Body(), &data)
+ if err != nil {
+ debuglogger.Logf("error unmarshalling complete multipart upload: %v", err)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetAPIError(s3err.ErrMalformedXML)
+ }
+
+ if len(data.Parts) == 0 {
+ debuglogger.Logf("empty parts provided for complete multipart upload")
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetAPIError(s3err.ErrEmptyParts)
+ }
+
+ var mpuObjectSize *int64
+ if mpuObjSizeHdr != "" {
+ val, err := strconv.ParseInt(mpuObjSizeHdr, 10, 64)
+ //TODO: Not sure if invalid request should be returned
+ if err != nil {
+ debuglogger.Logf("invalid value for 'x-amz-mp-objects-size' header: %v", err)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ if val < 0 {
+ debuglogger.Logf("value for 'x-amz-mp-objects-size' header is less than 0: %v", val)
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, s3err.GetInvalidMpObjectSizeErr(val)
+ }
+
+ mpuObjectSize = &val
+ }
+
+ _, checksums, err := utils.ParseChecksumHeaders(ctx)
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ err = utils.IsChecksumTypeValid(checksumType)
+ if err != nil {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ },
+ }, err
+ }
+
+ res, versid, err := c.be.CompleteMultipartUpload(ctx.Context(),
+ &s3.CompleteMultipartUploadInput{
+ Bucket: &bucket,
+ Key: &key,
+ UploadId: &uploadId,
+ MultipartUpload: &types.CompletedMultipartUpload{
+ Parts: data.Parts,
+ },
+ MpuObjectSize: mpuObjectSize,
+ ChecksumCRC32: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32]),
+ ChecksumCRC32C: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32c]),
+ ChecksumSHA1: utils.GetStringPtr(checksums[types.ChecksumAlgorithmSha1]),
+ ChecksumSHA256: utils.GetStringPtr(checksums[types.ChecksumAlgorithmSha256]),
+ ChecksumCRC64NVME: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc64nvme]),
+ ChecksumType: checksumType,
+ })
+ return &Response{
+ Data: res,
+ Headers: map[string]*string{
+ "x-amz-version-id": &versid,
+ },
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionCompleteMultipartUpload,
+ BucketOwner: parsedAcl.Owner,
+ ObjectETag: res.ETag,
+ EventName: s3event.EventCompleteMultipartUpload,
+ VersionId: &versid,
+ },
+ }, err
+}
diff --git a/s3api/controllers/utilities.go b/s3api/controllers/utilities.go
index f7b579ec..097b747b 100644
--- a/s3api/controllers/utilities.go
+++ b/s3api/controllers/utilities.go
@@ -183,3 +183,12 @@ func SetResponseHeaders(ctx *fiber.Ctx, headers map[string]*string) {
ctx.Response().Header.Add(key, *val)
}
}
+
+// Returns MethodNotAllowed for unmatched routes
+func (c S3ApiController) HandleUnmatch(ctx *fiber.Ctx) (*Response, error) {
+ return &Response{
+ MetaOpts: &MetaOptions{
+ Action: metrics.ActionUndetected,
+ },
+ }, s3err.GetAPIError(s3err.ErrMethodNotAllowed)
+}
diff --git a/s3api/router.go b/s3api/router.go
index bc3187e9..8014e951 100644
--- a/s3api/router.go
+++ b/s3api/router.go
@@ -54,67 +54,69 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
app.Patch("/list-buckets", middlewares.IsAdmin(logger), adminController.ListBuckets)
}
- // ListBuckets
+ // ListBuckets action
app.Get("/", controllers.ProcessResponse(ctrl.ListBuckets, logger, evs, mm))
+ bucketRouter := app.Group("/:bucket")
+ objectRouter := app.Group("/:bucket/*")
+
// PUT bucket operations
- app.Put("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.PutBucketTagging, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.PutBucketOwnershipControls, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(ctrl.PutBucketVersioning, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(ctrl.PutObjectLockConfiguration, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.PutBucketCors, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.PutBucketPolicy, logger, evs, mm))
- app.Put("/:bucket", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.PutBucketAcl, logger, evs, mm))
- app.Put("/:bucket", controllers.ProcessResponse(ctrl.CreateBucket, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.PutBucketTagging, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.PutBucketOwnershipControls, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(ctrl.PutBucketVersioning, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(ctrl.PutObjectLockConfiguration, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.PutBucketCors, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.PutBucketPolicy, logger, evs, mm))
+ bucketRouter.Put("", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.PutBucketAcl, logger, evs, mm))
+ bucketRouter.Put("", controllers.ProcessResponse(ctrl.CreateBucket, logger, evs, mm))
- // HeadBucket
- app.Head("/:bucket", controllers.ProcessResponse(ctrl.HeadBucket, logger, evs, mm))
+ // HeadBucket action
+ bucketRouter.Head("", controllers.ProcessResponse(ctrl.HeadBucket, logger, evs, mm))
- // Delete bucket operations
- app.Delete("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.DeleteBucketTagging, logger, evs, mm))
- app.Delete("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.DeleteBucketOwnershipControls, logger, evs, mm))
- app.Delete("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.DeleteBucketPolicy, logger, evs, mm))
- app.Delete("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.DeleteBucketCors, logger, evs, mm))
- app.Delete("/:bucket", controllers.ProcessResponse(ctrl.DeleteBucket, logger, evs, mm))
+ // DELETE bucket operations
+ bucketRouter.Delete("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.DeleteBucketTagging, logger, evs, mm))
+ bucketRouter.Delete("", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.DeleteBucketOwnershipControls, logger, evs, mm))
+ bucketRouter.Delete("", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.DeleteBucketPolicy, logger, evs, mm))
+ bucketRouter.Delete("", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.DeleteBucketCors, logger, evs, mm))
+ bucketRouter.Delete("", controllers.ProcessResponse(ctrl.DeleteBucket, logger, evs, mm))
// GET bucket operations
- app.Get("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.GetBucketTagging, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.GetBucketOwnershipControls, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(ctrl.GetBucketVersioning, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.GetBucketPolicy, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.GetBucketCors, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(ctrl.GetObjectLockConfiguration, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.GetBucketAcl, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("uploads"), controllers.ProcessResponse(ctrl.ListMultipartUploads, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgs("versions"), controllers.ProcessResponse(ctrl.ListObjectVersions, logger, evs, mm))
- app.Get("/:bucket", middlewares.MatchQueryArgWithValue("list-type", "2"), controllers.ProcessResponse(ctrl.ListObjectsV2, logger, evs, mm))
- app.Get("/:bucket", controllers.ProcessResponse(ctrl.ListObjects, logger, evs, mm))
-
- // HeadObject
- app.Head("/:bucket/*", controllers.ProcessResponse(ctrl.HeadObject, logger, evs, mm))
-
- // GET object operations
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.GetObjectTagging, logger, evs, mm))
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("retention"), controllers.ProcessResponse(ctrl.GetObjectRetention, logger, evs, mm))
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("legal-hold"), controllers.ProcessResponse(ctrl.GetObjectLegalHold, logger, evs, mm))
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.GetObjectAcl, logger, evs, mm))
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("attributes"), controllers.ProcessResponse(ctrl.GetObjectAttributes, logger, evs, mm))
- app.Get("/:bucket/*", middlewares.MatchQueryArgs("uploadId"), controllers.ProcessResponse(ctrl.ListParts, logger, evs, mm))
- app.Get("/:bucket/*", controllers.ProcessResponse(ctrl.GetObject, logger, evs, mm))
-
- // DeleteObject action
- // AbortMultipartUpload action
- // DeleteObjectTagging action
- app.Delete("/:bucket/:key/*", ctrl.DeleteActions)
+ bucketRouter.Get("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.GetBucketTagging, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.GetBucketOwnershipControls, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(ctrl.GetBucketVersioning, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.GetBucketPolicy, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.GetBucketCors, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(ctrl.GetObjectLockConfiguration, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.GetBucketAcl, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("uploads"), controllers.ProcessResponse(ctrl.ListMultipartUploads, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgs("versions"), controllers.ProcessResponse(ctrl.ListObjectVersions, logger, evs, mm))
+ bucketRouter.Get("", middlewares.MatchQueryArgWithValue("list-type", "2"), controllers.ProcessResponse(ctrl.ListObjectsV2, logger, evs, mm))
+ bucketRouter.Get("", controllers.ProcessResponse(ctrl.ListObjects, logger, evs, mm))
// DeleteObjects action
- app.Post("/:bucket", ctrl.DeleteObjects)
+ bucketRouter.Post("", ctrl.DeleteObjects)
- // CompleteMultipartUpload action
- // CreateMultipartUpload
- // RestoreObject action
- // SelectObjectContent action
- app.Post("/:bucket/:key/*", ctrl.CreateActions)
+ // HeadObject
+ objectRouter.Head("", controllers.ProcessResponse(ctrl.HeadObject, logger, evs, mm))
+
+ // GET object operations
+ objectRouter.Get("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.GetObjectTagging, logger, evs, mm))
+ objectRouter.Get("", middlewares.MatchQueryArgs("retention"), controllers.ProcessResponse(ctrl.GetObjectRetention, logger, evs, mm))
+ objectRouter.Get("", middlewares.MatchQueryArgs("legal-hold"), controllers.ProcessResponse(ctrl.GetObjectLegalHold, logger, evs, mm))
+ objectRouter.Get("", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.GetObjectAcl, logger, evs, mm))
+ objectRouter.Get("", middlewares.MatchQueryArgs("attributes"), controllers.ProcessResponse(ctrl.GetObjectAttributes, logger, evs, mm))
+ objectRouter.Get("", middlewares.MatchQueryArgs("uploadId"), controllers.ProcessResponse(ctrl.ListParts, logger, evs, mm))
+ objectRouter.Get("", controllers.ProcessResponse(ctrl.GetObject, logger, evs, mm))
+
+ // DELETE object operations
+ objectRouter.Delete("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.DeleteObjectTagging, logger, evs, mm))
+ objectRouter.Delete("", middlewares.MatchQueryArgs("uploadId"), controllers.ProcessResponse(ctrl.AbortMultipartUplaod, logger, evs, mm))
+ objectRouter.Delete("", controllers.ProcessResponse(ctrl.DeleteObject, logger, evs, mm))
+
+ objectRouter.Post("", middlewares.MatchQueryArgs("restore"), controllers.ProcessResponse(ctrl.RestoreObject, logger, evs, mm))
+ objectRouter.Post("", middlewares.MatchQueryArgs("list-type"), middlewares.MatchQueryArgWithValue("list-type", "2"), controllers.ProcessResponse(ctrl.RestoreObject, logger, evs, mm))
+ objectRouter.Post("", middlewares.MatchQueryArgs("uploadId"), controllers.ProcessResponse(ctrl.CompleteMultipartUpload, logger, evs, mm))
+ objectRouter.Post("", middlewares.MatchQueryArgs("uploads"), controllers.ProcessResponse(ctrl.CreateMultipartUpload, logger, evs, mm))
// CopyObject action
// PutObject action
@@ -123,4 +125,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
// PutObjectTagging action
// PutObjectAcl action
app.Put("/:bucket/:key/*", ctrl.PutActions)
+
+ // Return MethodNotAllowed for all the unmatched routes
+ app.All("*", controllers.ProcessResponse(ctrl.HandleUnmatch, logger, evs, mm))
}