From e42ff58a80c3a56a65235ad98399b1076b9a7b0c Mon Sep 17 00:00:00 2001 From: MacKinley Smith Date: Tue, 19 Nov 2024 14:57:19 -0700 Subject: [PATCH] feat: pass along the contentType metadata field if supplied This change causes tusd for Go to match the NodeJS version's behavior, discussed here: https://stackoverflow.com/questions/74148196/how-to-resolve-application-octet-stream-in-s3-using-tus-node-tusd-uppy-or-net --- pkg/s3store/s3store.go | 8 +++++-- pkg/s3store/s3store_test.go | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkg/s3store/s3store.go b/pkg/s3store/s3store.go index 7b5acafff..738cad8ea 100644 --- a/pkg/s3store/s3store.go +++ b/pkg/s3store/s3store.go @@ -326,11 +326,15 @@ func (store S3Store) NewUpload(ctx context.Context, info handler.FileInfo) (hand // Create the actual multipart upload t := time.Now() - res, err := store.Service.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + multipartUploadInput := &s3.CreateMultipartUploadInput{ Bucket: aws.String(store.Bucket), Key: store.keyWithPrefix(objectId), Metadata: metadata, - }) + } + if contentType, found := info.MetaData["contentType"]; found { + multipartUploadInput.ContentType = aws.String(contentType) + } + res, err := store.Service.CreateMultipartUpload(ctx, multipartUploadInput) store.observeRequestDuration(t, metricCreateMultipartUpload) if err != nil { return nil, fmt.Errorf("s3store: unable to create multipart upload:\n%s", err) diff --git a/pkg/s3store/s3store_test.go b/pkg/s3store/s3store_test.go index e69ba2e3f..b2c3e5991 100644 --- a/pkg/s3store/s3store_test.go +++ b/pkg/s3store/s3store_test.go @@ -164,6 +164,53 @@ func TestNewUploadWithMetadataObjectPrefix(t *testing.T) { assert.NotNil(upload) } +func TestNewUploadWithContentType(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + assert := assert.New(t) + + s3obj := NewMockS3API(mockCtrl) + store := New("bucket", s3obj) + + assert.Equal("bucket", store.Bucket) + assert.Equal(s3obj, store.Service) + + gomock.InOrder( + s3obj.EXPECT().CreateMultipartUpload(context.Background(), &s3.CreateMultipartUploadInput{ + Bucket: aws.String("bucket"), + Key: aws.String("uploadId"), + ContentType: aws.String("application/pdf"), + Metadata: map[string]string{ + "foo": "hello", + "bar": "men???hi", + "contentType": "application/pdf", + }, + }).Return(&s3.CreateMultipartUploadOutput{ + UploadId: aws.String("multipartId"), + }, nil), + s3obj.EXPECT().PutObject(context.Background(), &s3.PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("uploadId.info"), + Body: bytes.NewReader([]byte(`{"ID":"uploadId+multipartId","Size":500,"SizeIsDeferred":false,"Offset":0,"MetaData":{"bar":"menĂ¼\r\nhi","contentType":"application/pdf","foo":"hello"},"IsPartial":false,"IsFinal":false,"PartialUploads":null,"Storage":{"Bucket":"bucket","Key":"uploadId","Type":"s3store"}}`)), + ContentLength: aws.Int64(273), + }), + ) + + info := handler.FileInfo{ + ID: "uploadId", + Size: 500, + MetaData: map[string]string{ + "foo": "hello", + "bar": "menĂ¼\r\nhi", + "contentType": "application/pdf", + }, + } + + upload, err := store.NewUpload(context.Background(), info) + assert.Nil(err) + assert.NotNil(upload) +} + // This test ensures that an newly created upload without any chunks can be // directly finished. There are no calls to ListPart or HeadObject because // the upload is not fetched from S3 first.