mirror of
https://github.com/versity/versitygw.git
synced 2026-07-31 12:33:00 +00:00
feat: implements advanced routing for bucket POST and object PUT operations.
Fixes #1036 Fixes the issue when calling a non-existing root endpoint(POST /) the gateway returns `NoSuchBucket`. Now it returns the correct `MethodNotAllowed` error.
This commit is contained in:
@@ -2419,6 +2419,10 @@ func (p *Posix) UploadPart(ctx context.Context, input *s3.UploadPartInput) (*s3.
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
// Return the error itself, if it's an 's3err.APIError'
|
||||
if _, ok := err.(s3err.APIError); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("write part data: %w", err)
|
||||
}
|
||||
|
||||
@@ -2853,6 +2857,10 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
// Return the error itself, if it's an 's3err.APIError'
|
||||
if _, ok := err.(s3err.APIError); ok {
|
||||
return s3response.PutObjectOutput{}, err
|
||||
}
|
||||
return s3response.PutObjectOutput{}, fmt.Errorf("write object data: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,13 +72,6 @@ func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs
|
||||
}
|
||||
}
|
||||
|
||||
func getstring(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
func getint64(i *int64) int64 {
|
||||
if i == nil {
|
||||
return 0
|
||||
@@ -1974,7 +1967,6 @@ func SendResponse(ctx *fiber.Ctx, err error, l *MetaOpts) error {
|
||||
ctx.Status(apierr.HTTPStatusCode)
|
||||
return ctx.Send(s3err.GetAPIErrorResponse(apierr, "", "", ""))
|
||||
}
|
||||
fmt.Println(err, "------------")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Internal Error, %v\n", err)
|
||||
ctx.Status(http.StatusInternalServerError)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
// 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"
|
||||
"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) DeleteObjects(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
bypass := strings.EqualFold(ctx.Get("X-Amz-Bypass-Governance-Retention"), "true")
|
||||
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
|
||||
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
|
||||
|
||||
var dObj s3response.DeleteObjects
|
||||
|
||||
err := xml.Unmarshal(ctx.Body(), &dObj)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error unmarshalling delete objects: %v", err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionDeleteObjects,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
err = auth.VerifyAccess(ctx.Context(), c.be,
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.DeleteObjectAction,
|
||||
IsBucketPublic: IsBucketPublic,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionDeleteObjects,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, dObj.Objects, bypass, IsBucketPublic, c.be)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionDeleteObjects,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
res, err := c.be.DeleteObjects(ctx.Context(),
|
||||
&s3.DeleteObjectsInput{
|
||||
Bucket: &bucket,
|
||||
Delete: &types.Delete{
|
||||
Objects: dObj.Objects,
|
||||
},
|
||||
})
|
||||
return &Response{
|
||||
Data: res,
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionDeleteObjects,
|
||||
ObjectCount: int64(len(dObj.Objects)),
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
EventName: s3event.EventObjectRemovedDeleteObjects,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
// 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"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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) PutObjectTagging(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)
|
||||
|
||||
tagging, err := utils.ParseTagging(ctx.Body(), utils.TagLimitObject)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectTagging,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
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.PutBucketTaggingAction,
|
||||
IsBucketPublic: IsBucketPublic,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectTagging,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
err = c.be.PutObjectTagging(ctx.Context(), bucket, key, tagging)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectTagging,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
EventName: s3event.EventObjectTaggingPut,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) PutObjectRetention(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")
|
||||
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)
|
||||
|
||||
if 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.PutObjectRetentionAction,
|
||||
IsBucketPublic: IsBucketPublic,
|
||||
}); err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectRetention,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
if bypass {
|
||||
policy, err := c.be.GetBucketPolicy(ctx.Context(), bucket)
|
||||
if err != nil {
|
||||
bypass = false
|
||||
} else {
|
||||
if err := auth.VerifyBucketPolicy(policy, acct.Access, bucket, key, auth.BypassGovernanceRetentionAction); err != nil {
|
||||
bypass = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retention, err := auth.ParseObjectLockRetentionInput(ctx.Body())
|
||||
if err != nil {
|
||||
debuglogger.Logf("failed to parse object lock configuration input: %v", err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectRetention,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
err = c.be.PutObjectRetention(ctx.Context(), bucket, key, versionId, bypass, retention)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectRetention,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) PutObjectLegalHold(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
versionId := ctx.Query("versionId")
|
||||
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 legalHold types.ObjectLockLegalHold
|
||||
if err := xml.Unmarshal(ctx.Body(), &legalHold); err != nil {
|
||||
debuglogger.Logf("failed to parse request body: %v", err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectLegalHold,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrMalformedXML)
|
||||
}
|
||||
|
||||
if legalHold.Status != types.ObjectLockLegalHoldStatusOff && legalHold.Status != types.ObjectLockLegalHoldStatusOn {
|
||||
debuglogger.Logf("invalid legal hold status: %v", legalHold.Status)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectLegalHold,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrMalformedXML)
|
||||
}
|
||||
|
||||
if 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.PutObjectLegalHoldAction,
|
||||
IsBucketPublic: IsBucketPublic,
|
||||
}); err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectLegalHold,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
err := c.be.PutObjectLegalHold(ctx.Context(), bucket, key, versionId, legalHold.Status == types.ObjectLockLegalHoldStatusOn)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectLegalHold,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) UploadPart(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
partNumber := int32(ctx.QueryInt("partNumber", -1))
|
||||
uploadId := ctx.Query("uploadId")
|
||||
// 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)
|
||||
|
||||
contentLengthStr := ctx.Get("Content-Length")
|
||||
if contentLengthStr == "" {
|
||||
contentLengthStr = "0"
|
||||
}
|
||||
// Use decoded content length if available because the
|
||||
// middleware will decode the chunked transfer encoding
|
||||
decodedLength := ctx.Get("X-Amz-Decoded-Content-Length")
|
||||
if decodedLength != "" {
|
||||
contentLengthStr = decodedLength
|
||||
}
|
||||
|
||||
if partNumber < 1 || partNumber > 10000 {
|
||||
debuglogger.Logf("invalid part number: %d", partNumber)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPart,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidPartNumber)
|
||||
}
|
||||
|
||||
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.ActionUploadPart,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error parsing content length %q: %v", contentLengthStr, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPart,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
algorithm, checksums, err := utils.ParseChecksumHeaders(ctx)
|
||||
if err != nil {
|
||||
debuglogger.Logf("err parsing checksum headers: %v", err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPart,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
bodyi := utils.ContextKeyBodyReader.Get(ctx)
|
||||
if bodyi != nil {
|
||||
body = bodyi.(io.Reader)
|
||||
} else {
|
||||
body = ctx.Request().BodyStream()
|
||||
}
|
||||
|
||||
res, err := c.be.UploadPart(ctx.Context(),
|
||||
&s3.UploadPartInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
UploadId: &uploadId,
|
||||
PartNumber: &partNumber,
|
||||
ContentLength: &contentLength,
|
||||
Body: body,
|
||||
ChecksumAlgorithm: algorithm,
|
||||
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]),
|
||||
})
|
||||
var headers map[string]*string
|
||||
if err == nil {
|
||||
headers = map[string]*string{
|
||||
"ETag": res.ETag,
|
||||
"x-amz-checksum-crc32": res.ChecksumCRC32,
|
||||
"x-amz-checksum-crc32c": res.ChecksumCRC32C,
|
||||
"x-amz-checksum-crc64nvme": res.ChecksumCRC64NVME,
|
||||
"x-amz-checksum-sha1": res.ChecksumSHA1,
|
||||
"x-amz-checksum-sha256": res.ChecksumSHA256,
|
||||
}
|
||||
}
|
||||
return &Response{
|
||||
Headers: headers,
|
||||
MetaOpts: &MetaOptions{
|
||||
ContentLength: contentLength,
|
||||
Action: metrics.ActionUploadPart,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
|
||||
}
|
||||
|
||||
func (c S3ApiController) UploadPartCopy(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
copySource := strings.TrimPrefix(ctx.Get("X-Amz-Copy-Source"), "/")
|
||||
copySrcRange := ctx.Get("X-Amz-Copy-Source-Range")
|
||||
partNumber := int32(ctx.QueryInt("partNumber", -1))
|
||||
uploadId := ctx.Query("uploadId")
|
||||
// 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)
|
||||
|
||||
cs := copySource
|
||||
copySource, err := url.QueryUnescape(copySource)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error unescaping copy source %q: %v", cs, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPartCopy,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidCopySource)
|
||||
}
|
||||
|
||||
if partNumber < 1 || partNumber > 10000 {
|
||||
debuglogger.Logf("invalid part number: %d", partNumber)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPartCopy,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidPartNumber)
|
||||
}
|
||||
|
||||
err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource,
|
||||
auth.AccessOptions{
|
||||
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.ActionUploadPartCopy,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
resp, err := c.be.UploadPartCopy(ctx.Context(),
|
||||
&s3.UploadPartCopyInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
CopySource: ©Source,
|
||||
PartNumber: &partNumber,
|
||||
UploadId: &uploadId,
|
||||
CopySourceRange: ©SrcRange,
|
||||
})
|
||||
var headers map[string]*string
|
||||
if err == nil && resp.CopySourceVersionId != "" {
|
||||
headers = map[string]*string{
|
||||
"x-amz-copy-source-version-id": &resp.CopySourceVersionId,
|
||||
}
|
||||
}
|
||||
return &Response{
|
||||
Headers: headers,
|
||||
Data: resp,
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionUploadPartCopy,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) PutObjectAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
acl := ctx.Get("X-Amz-Acl")
|
||||
grantFullControl := ctx.Get("X-Amz-Grant-Full-Control")
|
||||
grantRead := ctx.Get("X-Amz-Grant-Read")
|
||||
grantReadACP := ctx.Get("X-Amz-Grant-Read-Acp")
|
||||
granWrite := ctx.Get("X-Amz-Grant-Write")
|
||||
grantWriteACP := ctx.Get("X-Amz-Grant-Write-Acp")
|
||||
grants := grantFullControl + grantRead + grantReadACP + granWrite + grantWriteACP
|
||||
// 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.PutObjectAclAction,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
var input *s3.PutObjectAclInput
|
||||
if len(ctx.Body()) > 0 {
|
||||
if grants+acl != "" {
|
||||
debuglogger.Logf("invalid request: %q (grants) %q (acl)", grants, acl)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
var accessControlPolicy auth.AccessControlPolicy
|
||||
err := xml.Unmarshal(ctx.Body(), &accessControlPolicy)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error unmarshalling access control policy: %v", err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
//TODO: This part will be changed when object acls are implemented
|
||||
|
||||
grants := []types.Grant{}
|
||||
for _, grt := range accessControlPolicy.AccessControlList.Grants {
|
||||
grants = append(grants, types.Grant{
|
||||
Grantee: &types.Grantee{
|
||||
ID: &grt.Grantee.ID,
|
||||
Type: grt.Grantee.Type,
|
||||
},
|
||||
Permission: types.Permission(grt.Permission),
|
||||
})
|
||||
}
|
||||
|
||||
input = &s3.PutObjectAclInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ACL: "",
|
||||
AccessControlPolicy: &types.AccessControlPolicy{
|
||||
Owner: accessControlPolicy.Owner,
|
||||
Grants: grants,
|
||||
},
|
||||
}
|
||||
}
|
||||
if acl != "" {
|
||||
if acl != "private" && acl != "public-read" && acl != "public-read-write" {
|
||||
debuglogger.Logf("invalid acl: %q", acl)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
if len(ctx.Body()) > 0 || grants != "" {
|
||||
debuglogger.Logf("invalid request: %q (grants) %q (acl) %v (body len)", grants, acl, len(ctx.Body()))
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
input = &s3.PutObjectAclInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ACL: types.ObjectCannedACL(acl),
|
||||
AccessControlPolicy: &types.AccessControlPolicy{
|
||||
Owner: &types.Owner{ID: &parsedAcl.Owner},
|
||||
},
|
||||
}
|
||||
}
|
||||
if grants != "" {
|
||||
input = &s3.PutObjectAclInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
GrantFullControl: &grantFullControl,
|
||||
GrantRead: &grantRead,
|
||||
GrantReadACP: &grantReadACP,
|
||||
GrantWrite: &granWrite,
|
||||
GrantWriteACP: &grantWriteACP,
|
||||
AccessControlPolicy: &types.AccessControlPolicy{
|
||||
Owner: &types.Owner{ID: &parsedAcl.Owner},
|
||||
},
|
||||
ACL: "",
|
||||
}
|
||||
}
|
||||
|
||||
err = c.be.PutObjectAcl(ctx.Context(), input)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObjectAcl,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
EventName: s3event.EventObjectAclPut,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
copySource := strings.TrimPrefix(ctx.Get("X-Amz-Copy-Source"), "/")
|
||||
copySrcIfMatch := ctx.Get("X-Amz-Copy-Source-If-Match")
|
||||
copySrcIfNoneMatch := ctx.Get("X-Amz-Copy-Source-If-None-Match")
|
||||
copySrcModifSince := ctx.Get("X-Amz-Copy-Source-If-Modified-Since")
|
||||
copySrcUnmodifSince := ctx.Get("X-Amz-Copy-Source-If-Unmodified-Since")
|
||||
metaDirective := types.MetadataDirective(ctx.Get("X-Amz-Metadata-Directive", string(types.MetadataDirectiveCopy)))
|
||||
taggingDirective := types.TaggingDirective(ctx.Get("X-Amz-Tagging-Directive", string(types.TaggingDirectiveCopy)))
|
||||
contentType := ctx.Get("Content-Type")
|
||||
contentEncoding := ctx.Get("Content-Encoding")
|
||||
contentDisposition := ctx.Get("Content-Disposition")
|
||||
contentLanguage := ctx.Get("Content-Language")
|
||||
cacheControl := ctx.Get("Cache-Control")
|
||||
expires := ctx.Get("Expires")
|
||||
tagging := ctx.Get("x-amz-tagging")
|
||||
storageClass := ctx.Get("X-Amz-Storage-Class")
|
||||
// context locals
|
||||
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
|
||||
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
|
||||
cs := copySource
|
||||
copySource, err := url.QueryUnescape(copySource)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error unescaping copy source %q: %v", cs, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidCopySource)
|
||||
}
|
||||
|
||||
err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource,
|
||||
auth.AccessOptions{
|
||||
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.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
var mtime *time.Time
|
||||
if copySrcModifSince != "" {
|
||||
tm, err := time.Parse(iso8601Format, copySrcModifSince)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error parsing copy source modified since %q: %v", copySrcModifSince, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidCopySource)
|
||||
}
|
||||
mtime = &tm
|
||||
}
|
||||
var umtime *time.Time
|
||||
if copySrcUnmodifSince != "" {
|
||||
tm, err := time.Parse(iso8601Format, copySrcUnmodifSince)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error parsing copy source unmodified since %q: %v", copySrcUnmodifSince, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidCopySource)
|
||||
}
|
||||
umtime = &tm
|
||||
}
|
||||
|
||||
metadata := utils.GetUserMetaData(&ctx.Request().Header)
|
||||
|
||||
if metaDirective != "" && metaDirective != types.MetadataDirectiveCopy && metaDirective != types.MetadataDirectiveReplace {
|
||||
debuglogger.Logf("invalid metadata directive: %v", metaDirective)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidMetadataDirective)
|
||||
}
|
||||
|
||||
if taggingDirective != "" && taggingDirective != types.TaggingDirectiveCopy && taggingDirective != types.TaggingDirectiveReplace {
|
||||
debuglogger.Logf("invalid tagging direcrive: %v", taggingDirective)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidTaggingDirective)
|
||||
}
|
||||
|
||||
checksumAlgorithm := types.ChecksumAlgorithm(ctx.Get("x-amz-checksum-algorithm"))
|
||||
err = utils.IsChecksumAlgorithmValid(checksumAlgorithm)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
objLock, err := utils.ParsObjectLockHdrs(ctx)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
res, err := c.be.CopyObject(ctx.Context(),
|
||||
s3response.CopyObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ContentType: &contentType,
|
||||
ContentDisposition: &contentDisposition,
|
||||
ContentEncoding: &contentEncoding,
|
||||
ContentLanguage: &contentLanguage,
|
||||
CacheControl: &cacheControl,
|
||||
Expires: &expires,
|
||||
Tagging: &tagging,
|
||||
TaggingDirective: taggingDirective,
|
||||
CopySource: ©Source,
|
||||
CopySourceIfMatch: ©SrcIfMatch,
|
||||
CopySourceIfNoneMatch: ©SrcIfNoneMatch,
|
||||
CopySourceIfModifiedSince: mtime,
|
||||
CopySourceIfUnmodifiedSince: umtime,
|
||||
ExpectedBucketOwner: &acct.Access,
|
||||
Metadata: metadata,
|
||||
MetadataDirective: metaDirective,
|
||||
StorageClass: types.StorageClass(storageClass),
|
||||
ChecksumAlgorithm: checksumAlgorithm,
|
||||
ObjectLockRetainUntilDate: &objLock.RetainUntilDate,
|
||||
ObjectLockLegalHoldStatus: objLock.LegalHoldStatus,
|
||||
ObjectLockMode: objLock.ObjectLockMode,
|
||||
})
|
||||
|
||||
var etag *string
|
||||
if err == nil {
|
||||
etag = res.CopyObjectResult.ETag
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Headers: map[string]*string{
|
||||
"x-amz-version-id": res.VersionId,
|
||||
"x-amz-copy-source-version-id": res.CopySourceVersionId,
|
||||
},
|
||||
Data: res.CopyObjectResult,
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionCopyObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
ObjectETag: etag,
|
||||
VersionId: res.VersionId,
|
||||
EventName: s3event.EventObjectCreatedCopy,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
|
||||
contentType := ctx.Get("Content-Type")
|
||||
contentEncoding := ctx.Get("Content-Encoding")
|
||||
contentDisposition := ctx.Get("Content-Disposition")
|
||||
contentLanguage := ctx.Get("Content-Language")
|
||||
cacheControl := ctx.Get("Cache-Control")
|
||||
expires := ctx.Get("Expires")
|
||||
tagging := ctx.Get("x-amz-tagging")
|
||||
// context locals
|
||||
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
|
||||
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
|
||||
|
||||
// Content Length
|
||||
contentLengthStr := ctx.Get("Content-Length")
|
||||
if contentLengthStr == "" {
|
||||
contentLengthStr = "0"
|
||||
}
|
||||
// Use decoded content length if available because the
|
||||
// middleware will decode the chunked transfer encoding
|
||||
decodedLength := ctx.Get("X-Amz-Decoded-Content-Length")
|
||||
if decodedLength != "" {
|
||||
contentLengthStr = decodedLength
|
||||
}
|
||||
|
||||
// load the meta headers
|
||||
metadata := utils.GetUserMetaData(&ctx.Request().Header)
|
||||
|
||||
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.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []types.ObjectIdentifier{{Key: &key}}, true, IsBucketPublic, c.be)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
|
||||
if err != nil {
|
||||
debuglogger.Logf("error parsing content length %q: %v", contentLengthStr, err)
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
objLock, err := utils.ParsObjectLockHdrs(ctx)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
algorithm, checksums, err := utils.ParseChecksumHeaders(ctx)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
Action: metrics.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
bodyi := utils.ContextKeyBodyReader.Get(ctx)
|
||||
if bodyi != nil {
|
||||
body = bodyi.(io.Reader)
|
||||
} else {
|
||||
body = ctx.Request().BodyStream()
|
||||
}
|
||||
|
||||
res, err := c.be.PutObject(ctx.Context(),
|
||||
s3response.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ContentLength: &contentLength,
|
||||
ContentType: &contentType,
|
||||
ContentEncoding: &contentEncoding,
|
||||
ContentDisposition: &contentDisposition,
|
||||
ContentLanguage: &contentLanguage,
|
||||
CacheControl: &cacheControl,
|
||||
Expires: &expires,
|
||||
Metadata: metadata,
|
||||
Body: body,
|
||||
Tagging: &tagging,
|
||||
ObjectLockRetainUntilDate: &objLock.RetainUntilDate,
|
||||
ObjectLockMode: objLock.ObjectLockMode,
|
||||
ObjectLockLegalHoldStatus: objLock.LegalHoldStatus,
|
||||
ChecksumAlgorithm: algorithm,
|
||||
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]),
|
||||
})
|
||||
return &Response{
|
||||
Headers: map[string]*string{
|
||||
"ETag": &res.ETag,
|
||||
"x-amz-checksum-crc32": res.ChecksumCRC32,
|
||||
"x-amz-checksum-crc32c": res.ChecksumCRC32C,
|
||||
"x-amz-checksum-crc64nvme": res.ChecksumCRC64NVME,
|
||||
"x-amz-checksum-sha1": res.ChecksumSHA1,
|
||||
"x-amz-checksum-sha256": res.ChecksumSHA256,
|
||||
"x-amz-checksum-type": utils.ConvertToStringPtr(res.ChecksumType),
|
||||
"x-amz-version-id": &res.VersionID,
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
ContentLength: contentLength,
|
||||
Action: metrics.ActionPutObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
ObjectETag: &res.ETag,
|
||||
ObjectSize: contentLength,
|
||||
EventName: s3event.EventObjectCreatedPut,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
@@ -172,6 +172,7 @@ func ProcessResponse(handler Handler, s3logger s3log.AuditLogger, s3evnt s3event
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the response headers
|
||||
func SetResponseHeaders(ctx *fiber.Ctx, headers map[string]*string) {
|
||||
if headers == nil {
|
||||
return
|
||||
|
||||
@@ -38,7 +38,7 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger, readonly bool) fibe
|
||||
path := ctx.Path()
|
||||
pathParts := strings.Split(path, "/")
|
||||
bucket := pathParts[1]
|
||||
if path == "/" && ctx.Method() == http.MethodGet {
|
||||
if path == "/" {
|
||||
return ctx.Next()
|
||||
}
|
||||
if ctx.Method() == http.MethodPatch {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/versity/versitygw/s3api/utils"
|
||||
)
|
||||
|
||||
// Evaluates/Matches the provided requst query params
|
||||
func MatchQueryArgs(args ...string) fiber.Handler {
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
if utils.ContextKeySkip.IsSet(ctx) {
|
||||
@@ -34,6 +35,23 @@ func MatchQueryArgs(args ...string) fiber.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluates/Matches the requst header
|
||||
func MatchHeader(key string) fiber.Handler {
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
if utils.ContextKeySkip.IsSet(ctx) {
|
||||
return ctx.Next()
|
||||
}
|
||||
|
||||
val := ctx.Get(key)
|
||||
if val == "" {
|
||||
utils.ContextKeySkip.Set(ctx, true)
|
||||
}
|
||||
|
||||
return ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluates/Matches the requst query param and value
|
||||
func MatchQueryArgWithValue(key, val string) fiber.Handler {
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
if utils.ContextKeySkip.IsSet(ctx) {
|
||||
|
||||
+10
-8
@@ -94,7 +94,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
bucketRouter.Get("", controllers.ProcessResponse(ctrl.ListObjects, logger, evs, mm))
|
||||
|
||||
// DeleteObjects action
|
||||
bucketRouter.Post("", ctrl.DeleteObjects)
|
||||
bucketRouter.Post("", middlewares.MatchQueryArgs("delete"), controllers.ProcessResponse(ctrl.DeleteObjects, logger, evs, mm))
|
||||
|
||||
// HeadObject
|
||||
objectRouter.Head("", controllers.ProcessResponse(ctrl.HeadObject, logger, evs, mm))
|
||||
@@ -118,13 +118,15 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
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
|
||||
// UploadPart action
|
||||
// UploadPartCopy action
|
||||
// PutObjectTagging action
|
||||
// PutObjectAcl action
|
||||
app.Put("/:bucket/:key/*", ctrl.PutActions)
|
||||
// PUT object operations
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.PutObjectTagging, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("retention"), controllers.ProcessResponse(ctrl.PutObjectRetention, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("legal-hold"), controllers.ProcessResponse(ctrl.PutObjectLegalHold, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.PutObjectAcl, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("uploadId", "partNumber"), middlewares.MatchHeader("X-Amz-Copy-Source"), controllers.ProcessResponse(ctrl.UploadPartCopy, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchQueryArgs("uploadId", "partNumber"), controllers.ProcessResponse(ctrl.UploadPart, logger, evs, mm))
|
||||
objectRouter.Put("", middlewares.MatchHeader("X-Amz-Copy-Source"), controllers.ProcessResponse(ctrl.CopyObject, logger, evs, mm))
|
||||
objectRouter.Put("", controllers.ProcessResponse(ctrl.PutObject, logger, evs, mm))
|
||||
|
||||
// Return MethodNotAllowed for all the unmatched routes
|
||||
app.All("*", controllers.ProcessResponse(ctrl.HandleUnmatch, logger, evs, mm))
|
||||
|
||||
@@ -917,6 +917,7 @@ func GetIntTests() IntTests {
|
||||
"PutObject_with_object_lock": PutObject_with_object_lock,
|
||||
"PutObject_invalid_legal_hold": PutObject_invalid_legal_hold,
|
||||
"PutObject_invalid_object_lock_mode": PutObject_invalid_object_lock_mode,
|
||||
"PutObject_invalid_credentials": PutObject_invalid_credentials,
|
||||
"PutObject_checksum_algorithm_and_header_mismatch": PutObject_checksum_algorithm_and_header_mismatch,
|
||||
"PutObject_multiple_checksum_headers": PutObject_multiple_checksum_headers,
|
||||
"PutObject_invalid_checksum_header": PutObject_invalid_checksum_header,
|
||||
|
||||
Reference in New Issue
Block a user