From 9f31da5d5710a2d9f1210ffe46435b69ce2d1753 Mon Sep 17 00:00:00 2001 From: wd256 Date: Thu, 19 Apr 2018 02:10:48 +1000 Subject: [PATCH] Fix PutObject/CopyObject with metadata for GCS gateway (#5828) Make sure to apply standard headers such as Content-Type, Content-Disposition and Content-Language to the correct GCS object attributes during object upload and copy operations. Fixes: #5800 --- cmd/gateway/gcs/gateway-gcs.go | 56 ++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/cmd/gateway/gcs/gateway-gcs.go b/cmd/gateway/gcs/gateway-gcs.go index b36a7c1ba..c16743a13 100644 --- a/cmd/gateway/gcs/gateway-gcs.go +++ b/cmd/gateway/gcs/gateway-gcs.go @@ -758,18 +758,57 @@ func (l *gcsGateway) GetObject(ctx context.Context, bucket string, key string, s func fromGCSAttrsToObjectInfo(attrs *storage.ObjectAttrs) minio.ObjectInfo { // All google cloud storage objects have a CRC32c hash, whereas composite objects may not have a MD5 hash // Refer https://cloud.google.com/storage/docs/hashes-etags. Use CRC32C for ETag + metadata := make(map[string]string) + for k, v := range attrs.Metadata { + metadata[k] = v + } + if attrs.ContentType != "" { + metadata["content-type"] = attrs.ContentType + } + if attrs.ContentEncoding != "" { + metadata["content-encoding"] = attrs.ContentEncoding + } + if attrs.CacheControl != "" { + metadata["cache-control"] = attrs.CacheControl + } + if attrs.ContentDisposition != "" { + metadata["content-disposition"] = attrs.ContentDisposition + } + if attrs.ContentLanguage != "" { + metadata["content-language"] = attrs.ContentLanguage + } return minio.ObjectInfo{ Name: attrs.Name, Bucket: attrs.Bucket, ModTime: attrs.Updated, Size: attrs.Size, ETag: minio.ToS3ETag(fmt.Sprintf("%d", attrs.CRC32C)), - UserDefined: attrs.Metadata, + UserDefined: metadata, ContentType: attrs.ContentType, ContentEncoding: attrs.ContentEncoding, } } +// applyMetadataToGCSAttrs applies metadata to a GCS ObjectAttrs instance +func applyMetadataToGCSAttrs(metadata map[string]string, attrs *storage.ObjectAttrs) { + attrs.ContentType = metadata["content-type"] + attrs.ContentEncoding = metadata["content-encoding"] + attrs.CacheControl = metadata["cache-control"] + attrs.ContentDisposition = metadata["content-disposition"] + attrs.ContentLanguage = metadata["content-language"] + + attrs.Metadata = make(map[string]string) + for k, v := range metadata { + attrs.Metadata[k] = v + } + // Filter metadata which is stored as a unique attribute + for _, key := range []string{ + "content-type", "content-encoding", "cache-control", "content-disposition", "content-language", + } { + delete(attrs.Metadata, key) + } +} + // GetObjectInfo - reads object info and replies back ObjectInfo func (l *gcsGateway) GetObjectInfo(ctx context.Context, bucket string, object string) (minio.ObjectInfo, error) { // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, @@ -800,10 +839,7 @@ func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, d object := l.client.Bucket(bucket).Object(key) w := object.NewWriter(l.ctx) - - w.ContentType = metadata["content-type"] - w.ContentEncoding = metadata["content-encoding"] - w.Metadata = metadata + applyMetadataToGCSAttrs(metadata, &w.ObjectAttrs) if _, err := io.Copy(w, data); err != nil { // Close the object writer upon error. @@ -832,7 +868,7 @@ func (l *gcsGateway) CopyObject(ctx context.Context, srcBucket string, srcObject dst := l.client.Bucket(destBucket).Object(destObject) copier := dst.CopierFrom(src) - copier.ObjectAttrs.Metadata = srcInfo.UserDefined + applyMetadataToGCSAttrs(srcInfo.UserDefined, &copier.ObjectAttrs) attrs, err := copier.Run(l.ctx) if err != nil { @@ -865,9 +901,7 @@ func (l *gcsGateway) NewMultipartUpload(ctx context.Context, bucket string, key w := l.client.Bucket(bucket).Object(meta).NewWriter(l.ctx) defer w.Close() - w.ContentType = metadata["content-type"] - w.ContentEncoding = metadata["content-encoding"] - w.Metadata = metadata + applyMetadataToGCSAttrs(metadata, &w.ObjectAttrs) if err = json.NewEncoder(w).Encode(gcsMultipartMetaV1{ gcsMinioMultipartMetaCurrentVersion, @@ -1076,6 +1110,10 @@ func (l *gcsGateway) CompleteMultipartUpload(ctx context.Context, bucket string, composer := l.client.Bucket(bucket).Object(key).ComposerFrom(parts...) composer.ContentType = partZeroAttrs.ContentType + composer.ContentEncoding = partZeroAttrs.ContentEncoding + composer.CacheControl = partZeroAttrs.CacheControl + composer.ContentDisposition = partZeroAttrs.ContentDisposition + composer.ContentLanguage = partZeroAttrs.ContentLanguage composer.Metadata = partZeroAttrs.Metadata attrs, err := composer.Run(l.ctx) if err != nil {