From 232d40e41ba89c7cbce183add2327109a1165c83 Mon Sep 17 00:00:00 2001 From: Andrew Gaul Date: Tue, 7 Jan 2025 04:03:42 +0900 Subject: [PATCH] Add disposition and language to ComposeObject (#1865) --- fakestorage/object.go | 2 +- fakestorage/object_test.go | 80 ++++++++++++++++++++++++------------- internal/backend/fs.go | 14 ++++--- internal/backend/memory.go | 14 ++++--- internal/backend/storage.go | 2 +- internal/grpc/server.go | 4 +- 6 files changed, 72 insertions(+), 44 deletions(-) diff --git a/fakestorage/object.go b/fakestorage/object.go index 441c4ceace..664751ee44 100644 --- a/fakestorage/object.go +++ b/fakestorage/object.go @@ -1262,7 +1262,7 @@ func (s *Server) composeObject(r *http.Request) jsonResponse { sourceNames = append(sourceNames, n.Name) } - backendObj, err := s.backend.ComposeObject(bucketName, sourceNames, destinationObject, composeRequest.Destination.Metadata, composeRequest.Destination.ContentType) + backendObj, err := s.backend.ComposeObject(bucketName, sourceNames, destinationObject, composeRequest.Destination.Metadata, composeRequest.Destination.ContentType, composeRequest.Destination.ContentDisposition, composeRequest.Destination.ContentLanguage) if err != nil { return jsonResponse{ status: http.StatusInternalServerError, diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go index 59f7a8a40f..60fcd448bc 100644 --- a/fakestorage/object_test.go +++ b/fakestorage/object_test.go @@ -2123,10 +2123,12 @@ func TestParseRangeRequest(t *testing.T) { func TestServiceClientComposeObject(t *testing.T) { const ( - source1Content = "some content" - source2Content = "other content" - source3Content = "third test" - contentType = "text/plain; charset=utf-8" + source1Content = "some content" + source2Content = "other content" + source3Content = "third test" + contentDisposition = "attachment; filename=\"replaced.txt\"" + contentLanguage = "fr" + contentType = "text/plain; charset=utf-8" ) u32Checksum := uint32Checksum([]byte(source1Content)) hash := checksum.MD5Hash([]byte(source1Content)) @@ -2135,45 +2137,53 @@ func TestServiceClientComposeObject(t *testing.T) { objs := []Object{ { ObjectAttrs: ObjectAttrs{ - BucketName: "first-bucket", - Name: "files/source1.txt", - ContentType: contentType, - Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), - Md5Hash: checksum.EncodedHash(hash), - Metadata: map[string]string{"foo": "bar"}, + BucketName: "first-bucket", + Name: "files/source1.txt", + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + ContentType: contentType, + Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), + Md5Hash: checksum.EncodedHash(hash), + Metadata: map[string]string{"foo": "bar"}, }, Content: []byte(source1Content), }, { ObjectAttrs: ObjectAttrs{ - BucketName: "first-bucket", - Name: "files/source2.txt", - ContentType: contentType, - Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), - Md5Hash: checksum.EncodedHash(hash), - Metadata: map[string]string{"foo": "bar"}, + BucketName: "first-bucket", + Name: "files/source2.txt", + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + ContentType: contentType, + Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), + Md5Hash: checksum.EncodedHash(hash), + Metadata: map[string]string{"foo": "bar"}, }, Content: []byte(source2Content), }, { ObjectAttrs: ObjectAttrs{ - BucketName: "first-bucket", - Name: "files/source3.txt", - ContentType: contentType, - Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), - Md5Hash: checksum.EncodedHash(hash), - Metadata: map[string]string{"foo": "bar"}, + BucketName: "first-bucket", + Name: "files/source3.txt", + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + ContentType: contentType, + Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), + Md5Hash: checksum.EncodedHash(hash), + Metadata: map[string]string{"foo": "bar"}, }, Content: []byte(source3Content), }, { ObjectAttrs: ObjectAttrs{ - BucketName: "first-bucket", - Name: "files/destination.txt", - ContentType: contentType, - Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), - Md5Hash: checksum.EncodedHash(hash), - Metadata: map[string]string{"foo": "bar"}, + BucketName: "first-bucket", + Name: "files/destination.txt", + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + ContentType: contentType, + Crc32c: checksum.EncodedChecksum(uint32ToBytes(u32Checksum)), + Md5Hash: checksum.EncodedHash(hash), + Metadata: map[string]string{"foo": "bar"}, }, Content: []byte("test"), }, @@ -2236,6 +2246,8 @@ func TestServiceClientComposeObject(t *testing.T) { dstObject := client.Bucket(test.bucketName).Object(test.destObjectName) composer := dstObject.ComposerFrom(sourceObjects...) + composer.ContentDisposition = contentDisposition + composer.ContentLanguage = contentLanguage composer.ContentType = contentType composer.Metadata = map[string]string{"baz": "qux"} attrs, err := composer.Run(context.TODO()) @@ -2261,6 +2273,12 @@ func TestServiceClientComposeObject(t *testing.T) { if attrs.CRC32C != expectedChecksum { t.Errorf("wrong checksum in compose object attrs\nwant %d\ngot %d", u32Checksum, attrs.CRC32C) } + if attrs.ContentDisposition != contentDisposition { + t.Errorf("wrong content disposition\nwant %q\ngot %q", contentDisposition, attrs.ContentDisposition) + } + if attrs.ContentLanguage != contentLanguage { + t.Errorf("wrong content language\nwant %q\ngot %q", contentLanguage, attrs.ContentLanguage) + } if attrs.ContentType != contentType { t.Errorf("wrong content type\nwant %q\ngot %q", contentType, attrs.ContentType) } @@ -2286,6 +2304,12 @@ func TestServiceClientComposeObject(t *testing.T) { if expect := checksum.EncodedHash(expectedHash); expect != obj.Md5Hash { t.Errorf("wrong hash on object\nwant %s\ngot %s", expect, obj.Md5Hash) } + if obj.ContentDisposition != contentDisposition { + t.Errorf("wrong content disposition\nwant %q\ngot %q", contentDisposition, obj.ContentDisposition) + } + if obj.ContentLanguage != contentLanguage { + t.Errorf("wrong content language\nwant %q\ngot %q", contentLanguage, obj.ContentLanguage) + } if obj.ContentType != contentType { t.Errorf("wrong content type\nwant %q\ngot %q", contentType, obj.ContentType) } diff --git a/internal/backend/fs.go b/internal/backend/fs.go index 52cbcd1859..b8f226d213 100644 --- a/internal/backend/fs.go +++ b/internal/backend/fs.go @@ -437,7 +437,7 @@ func concatObjectReaders(objects []StreamingObject) io.ReadSeekCloser { return concatenatedContent{io.MultiReader(readers...)} } -func (s *storageFS) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) (StreamingObject, error) { +func (s *storageFS) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string, contentDisposition string, contentLanguage string) (StreamingObject, error) { var sourceObjects []StreamingObject for _, n := range objectNames { obj, err := s.GetObject(bucketName, n) @@ -451,11 +451,13 @@ func (s *storageFS) ComposeObject(bucketName string, objectNames []string, desti now := time.Now().Format(timestampFormat) dest := StreamingObject{ ObjectAttrs: ObjectAttrs{ - BucketName: bucketName, - Name: destinationName, - ContentType: contentType, - Created: now, - Updated: now, + BucketName: bucketName, + Name: destinationName, + ContentType: contentType, + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + Created: now, + Updated: now, }, } diff --git a/internal/backend/memory.go b/internal/backend/memory.go index f202ff3694..17b6480ea1 100644 --- a/internal/backend/memory.go +++ b/internal/backend/memory.go @@ -344,7 +344,7 @@ func (s *storageMemory) UpdateObject(bucketName, objectName string, attrsToUpdat return obj, nil } -func (s *storageMemory) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) (StreamingObject, error) { +func (s *storageMemory) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string, contentDisposition string, contentLanguage string) (StreamingObject, error) { var data []byte for _, n := range objectNames { obj, err := s.GetObject(bucketName, n) @@ -364,11 +364,13 @@ func (s *storageMemory) ComposeObject(bucketName string, objectNames []string, d now := time.Now().Format(timestampFormat) dest = Object{ ObjectAttrs: ObjectAttrs{ - BucketName: bucketName, - Name: destinationName, - ContentType: contentType, - Created: now, - Updated: now, + BucketName: bucketName, + Name: destinationName, + ContentType: contentType, + ContentDisposition: contentDisposition, + ContentLanguage: contentLanguage, + Created: now, + Updated: now, }, } } else { diff --git a/internal/backend/storage.go b/internal/backend/storage.go index fd382ae983..c428fcc8d2 100644 --- a/internal/backend/storage.go +++ b/internal/backend/storage.go @@ -30,7 +30,7 @@ type Storage interface { DeleteObject(bucketName, objectName string) error PatchObject(bucketName, objectName string, attrsToUpdate ObjectAttrs) (StreamingObject, error) UpdateObject(bucketName, objectName string, attrsToUpdate ObjectAttrs) (StreamingObject, error) - ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) (StreamingObject, error) + ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string, contentDisposition string, contentLanguage string) (StreamingObject, error) DeleteAllFiles() error } diff --git a/internal/grpc/server.go b/internal/grpc/server.go index 928344538d..42df543470 100644 --- a/internal/grpc/server.go +++ b/internal/grpc/server.go @@ -144,13 +144,13 @@ func (g *Server) PatchObject(ctx context.Context, req *pb.PatchObjectRequest) (* return makeObject(obj), err } -// ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) +// ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string, contentDisposition string, contentLanguage string) func (g *Server) ComposeObject(ctx context.Context, req *pb.ComposeObjectRequest) (*pb.Object, error) { sourceObjNames := make([]string, 2) for i := 0; i < len(req.SourceObjects); i++ { sourceObjNames[i] = req.SourceObjects[i].Name } - obj, err := g.backend.ComposeObject(req.DestinationBucket, sourceObjNames, req.DestinationObject, map[string]string{}, "") + obj, err := g.backend.ComposeObject(req.DestinationBucket, sourceObjNames, req.DestinationObject, map[string]string{}, "", "", "") return makeObject(obj), err }