mirror of
https://github.com/versity/versitygw.git
synced 2026-08-17 20:56:21 +00:00
Merge pull request #1865 from versity/sis/obj-default-content-type
fix: fixes object default Content-Type
This commit is contained in:
+1
-10
@@ -352,10 +352,6 @@ func (az *Azure) PutObject(ctx context.Context, po s3response.PutObjectInput) (s
|
||||
opts.HTTPHeaders.BlobContentType = po.ContentType
|
||||
}
|
||||
|
||||
if opts.HTTPHeaders.BlobContentType == nil {
|
||||
opts.HTTPHeaders.BlobContentType = backend.GetPtrFromString(backend.DefaultContentType)
|
||||
}
|
||||
|
||||
uploadResp, err := az.client.UploadStream(ctx, *po.Bucket, *po.Key, po.Body, opts)
|
||||
if err != nil {
|
||||
return s3response.PutObjectOutput{}, azureErrToS3Err(err)
|
||||
@@ -487,16 +483,11 @@ func (az *Azure) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.G
|
||||
tagcount = int32(*blobDownloadResponse.TagCount)
|
||||
}
|
||||
|
||||
contentType := blobDownloadResponse.ContentType
|
||||
if contentType == nil {
|
||||
contentType = backend.GetPtrFromString(backend.DefaultContentType)
|
||||
}
|
||||
|
||||
return &s3.GetObjectOutput{
|
||||
AcceptRanges: backend.GetPtrFromString("bytes"),
|
||||
ContentLength: blobDownloadResponse.ContentLength,
|
||||
ContentEncoding: blobDownloadResponse.ContentEncoding,
|
||||
ContentType: contentType,
|
||||
ContentType: blobDownloadResponse.ContentType,
|
||||
ContentDisposition: blobDownloadResponse.ContentDisposition,
|
||||
ContentLanguage: blobDownloadResponse.ContentLanguage,
|
||||
CacheControl: blobDownloadResponse.CacheControl,
|
||||
|
||||
+1
-2
@@ -38,8 +38,7 @@ import (
|
||||
|
||||
const (
|
||||
// this is the media type for directories in AWS and Nextcloud
|
||||
DirContentType = "application/x-directory"
|
||||
DefaultContentType = "binary/octet-stream"
|
||||
DirContentType = "application/x-directory"
|
||||
|
||||
// this is the minimum allowed size for mp parts
|
||||
MinPartSize = 5 * 1024 * 1024
|
||||
|
||||
@@ -51,7 +51,8 @@ const (
|
||||
minPartNumber = 1
|
||||
maxPartNumber = 10000
|
||||
|
||||
defaultRegion = "us-east-1"
|
||||
defaultRegion = "us-east-1"
|
||||
defaultContentType = "binary/octet-stream"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -145,7 +145,7 @@ func (c S3ApiController) SelectObjectContent(ctx *fiber.Ctx) (*Response, error)
|
||||
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")
|
||||
contentType := ctx.Get("Content-Type", defaultContentType)
|
||||
contentDisposition := ctx.Get("Content-Disposition")
|
||||
contentLanguage := ctx.Get("Content-Language")
|
||||
cacheControl := ctx.Get("Cache-Control")
|
||||
|
||||
@@ -497,7 +497,7 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
copySource := strings.TrimPrefix(ctx.Get("X-Amz-Copy-Source"), "/")
|
||||
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")
|
||||
contentType := ctx.Get("Content-Type", defaultContentType)
|
||||
contentEncoding := ctx.Get("Content-Encoding")
|
||||
contentDisposition := ctx.Get("Content-Disposition")
|
||||
contentLanguage := ctx.Get("Content-Language")
|
||||
@@ -655,7 +655,7 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
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")
|
||||
contentType := ctx.Get("Content-Type", defaultContentType)
|
||||
contentEncoding := ctx.Get("Content-Encoding")
|
||||
contentDisposition := ctx.Get("Content-Disposition")
|
||||
contentLanguage := ctx.Get("Content-Language")
|
||||
|
||||
@@ -1607,6 +1607,57 @@ func CompleteMultipartUpload_invalid_part_number(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func CompleteMultipartUpload_defualt_content_type(s *S3Conf) error {
|
||||
testName := "CompleteMultipartUpload_defualt_content_type"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-object"
|
||||
mp, err := createMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, _, err := uploadParts(s3client, 10, 1, bucket, obj, *mp.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: mp.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: []types.CompletedPart{
|
||||
{
|
||||
ETag: parts[0].ETag,
|
||||
PartNumber: parts[0].PartNumber,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if getString(res.ContentType) != defaultContentType {
|
||||
return fmt.Errorf("expected default %s Content-Type, instead got %s", defaultContentType, getString(res.ContentType))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CompleteMultipartUpload_success(s *S3Conf) error {
|
||||
testName := "CompleteMultipartUpload_success"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -746,6 +746,49 @@ func CopyObject_should_replace_meta_props(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func CopyObject_default_content_type_with_replace_metadata(s *S3Conf) error {
|
||||
testName := "CopyObject_default_content_type_with_replace_metadata"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
srcObj, dstObj := "source-object", "destination-object"
|
||||
_, err := putObjectWithData(1, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &srcObj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &dstObj,
|
||||
CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)),
|
||||
// with metadata directive REPLACE, Content-Type should default to binary/octet-stream
|
||||
MetadataDirective: types.MetadataDirectiveReplace,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &dstObj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if getString(res.ContentType) != defaultContentType {
|
||||
return fmt.Errorf("expected default %s Content-Type, instead got %s", defaultContentType, getString(res.ContentType))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CopyObject_missing_bucket_lock(s *S3Conf) error {
|
||||
testName := "CopyObject_missing_bucket_lock"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -808,6 +851,7 @@ func CopyObject_invalid_legal_hold(s *S3Conf) error {
|
||||
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLegalHoldStatus))
|
||||
}, withLock())
|
||||
}
|
||||
|
||||
func CopyObject_invalid_object_lock_mode(s *S3Conf) error {
|
||||
testName := "CopyObject_invalid_object_lock_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -913,6 +913,37 @@ func PutObject_success(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func PutObject_default_content_type(s *S3Conf) error {
|
||||
testName := "PutObject_default_content_type"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
key := "my-object"
|
||||
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ContentType: nil,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if getString(res.ContentType) != defaultContentType {
|
||||
return fmt.Errorf("expected default %s Content-Type, instead got %s", defaultContentType, getString(res.ContentType))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func PutObject_invalid_credentials(s *S3Conf) error {
|
||||
testName := "PutObject_invalid_credentials"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -183,6 +183,7 @@ func TestPutObject(ts *TestState) {
|
||||
ts.Run(PutObject_with_metadata)
|
||||
}
|
||||
ts.Run(PutObject_success)
|
||||
ts.Run(PutObject_default_content_type)
|
||||
if !ts.conf.versioningEnabled {
|
||||
ts.Run(PutObject_racey_success)
|
||||
}
|
||||
@@ -330,6 +331,7 @@ func TestCopyObject(ts *TestState) {
|
||||
ts.Run(CopyObject_non_existing_dir_object)
|
||||
ts.Run(CopyObject_should_copy_meta_props)
|
||||
ts.Run(CopyObject_should_replace_meta_props)
|
||||
ts.Run(CopyObject_default_content_type_with_replace_metadata)
|
||||
ts.Run(CopyObject_missing_bucket_lock)
|
||||
ts.Run(CopyObject_invalid_legal_hold)
|
||||
ts.Run(CopyObject_invalid_object_lock_mode)
|
||||
@@ -487,6 +489,7 @@ func TestCompleteMultipartUpload(ts *TestState) {
|
||||
ts.Run(CompletedMultipartUpload_non_existing_bucket)
|
||||
ts.Run(CompleteMultipartUpload_incorrect_part_number)
|
||||
ts.Run(CompleteMultipartUpload_invalid_part_number)
|
||||
ts.Run(CompleteMultipartUpload_defualt_content_type)
|
||||
ts.Run(CompleteMultipartUpload_invalid_ETag)
|
||||
ts.Run(CompleteMultipartUpload_small_upload_size)
|
||||
ts.Run(CompleteMultipartUpload_empty_parts)
|
||||
@@ -1292,6 +1295,7 @@ func GetIntTests() IntTests {
|
||||
"PutObject_special_chars": PutObject_special_chars,
|
||||
"PutObject_tagging": PutObject_tagging,
|
||||
"PutObject_success": PutObject_success,
|
||||
"PutObject_default_content_type": PutObject_default_content_type,
|
||||
"PutObject_invalid_object_names": PutObject_invalid_object_names,
|
||||
"PutObject_object_acl_not_supported": PutObject_object_acl_not_supported,
|
||||
"PutObject_false_negative_object_names": PutObject_false_negative_object_names,
|
||||
@@ -1392,6 +1396,7 @@ func GetIntTests() IntTests {
|
||||
"CopyObject_non_existing_dir_object": CopyObject_non_existing_dir_object,
|
||||
"CopyObject_should_copy_meta_props": CopyObject_should_copy_meta_props,
|
||||
"CopyObject_should_replace_meta_props": CopyObject_should_replace_meta_props,
|
||||
"CopyObject_default_content_type_with_replace_metadata": CopyObject_default_content_type_with_replace_metadata,
|
||||
"CopyObject_missing_bucket_lock": CopyObject_missing_bucket_lock,
|
||||
"CopyObject_invalid_legal_hold": CopyObject_invalid_legal_hold,
|
||||
"CopyObject_invalid_object_lock_mode": CopyObject_invalid_object_lock_mode,
|
||||
@@ -1497,6 +1502,7 @@ func GetIntTests() IntTests {
|
||||
"AbortMultipartUpload_if_match_initiated_time": AbortMultipartUpload_if_match_initiated_time,
|
||||
"CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket,
|
||||
"CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number,
|
||||
"CompleteMultipartUpload_defualt_content_type": CompleteMultipartUpload_defualt_content_type,
|
||||
"CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag,
|
||||
"CompleteMultipartUpload_small_upload_size": CompleteMultipartUpload_small_upload_size,
|
||||
"CompleteMultipartUpload_empty_parts": CompleteMultipartUpload_empty_parts,
|
||||
|
||||
@@ -49,6 +49,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/aws/smithy-go"
|
||||
"github.com/aws/smithy-go/middleware"
|
||||
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
@@ -560,7 +562,29 @@ func putObjectWithData(lgth int64, input *s3.PutObjectInput, client *s3.Client)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
|
||||
res, err := client.PutObject(ctx, input)
|
||||
res, err := client.PutObject(ctx, input, func(o *s3.Options) {
|
||||
// if input.Body is not nil, aws sdk hardcodes Content-Type: application/octet-stream
|
||||
// this adds a new middleware in the stack to remove the Content-Type header, if
|
||||
// it isn't explicitly provided as 'PutObject' input. Place the middleware
|
||||
// right before "Signing" middleware to avoid incorrect request signature calculation
|
||||
if input.ContentType == nil {
|
||||
o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
|
||||
return stack.Finalize.Insert(
|
||||
middleware.FinalizeMiddlewareFunc("UnsetContentType",
|
||||
func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
|
||||
out middleware.FinalizeOutput, md middleware.Metadata, err error,
|
||||
) {
|
||||
if req, ok := in.Request.(*smithyhttp.Request); ok {
|
||||
req.Header.Del("Content-Type")
|
||||
}
|
||||
return next.HandleFinalize(ctx, in)
|
||||
}),
|
||||
"Signing",
|
||||
middleware.Before,
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user