diff --git a/api-headers.go b/api-headers.go index 0e57cdf54..e8fab555c 100644 --- a/api-headers.go +++ b/api-headers.go @@ -62,20 +62,19 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, contentRange *h // set common headers setCommonHeaders(w) - // set object-related metadata headers + // Set content length. + w.Header().Set("Content-Length", strconv.FormatInt(objInfo.Size, 10)) + + // Set last modified time. lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat) w.Header().Set("Last-Modified", lastModified) - if objInfo.ContentType != "" { - w.Header().Set("Content-Type", objInfo.ContentType) - } + // Set Etag if available. if objInfo.MD5Sum != "" { w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") } - if objInfo.ContentEncoding != "" { - w.Header().Set("Content-Encoding", objInfo.ContentEncoding) - } - w.Header().Set("Content-Length", strconv.FormatInt(objInfo.Size, 10)) + + // Set all other user defined metadata. for k, v := range objInfo.UserDefined { w.Header().Set(k, v) } diff --git a/fs-v1.go b/fs-v1.go index 8bc2e1b67..d02c60c48 100644 --- a/fs-v1.go +++ b/fs-v1.go @@ -314,12 +314,15 @@ func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { return ObjectInfo{}, toObjectErr(err, bucket, object) } + if len(fsMeta.Meta) == 0 { + fsMeta.Meta = make(map[string]string) + } + // Guess content-type from the extension if possible. - contentType := fsMeta.Meta["content-type"] - if contentType == "" { + if fsMeta.Meta["content-type"] == "" { if objectExt := filepath.Ext(object); objectExt != "" { if content, ok := mimedb.DB[strings.ToLower(strings.TrimPrefix(objectExt, "."))]; ok { - contentType = content.ContentType + fsMeta.Meta["content-type"] = content.ContentType } } } @@ -332,7 +335,7 @@ func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) { Size: fi.Size, IsDir: fi.Mode.IsDir(), MD5Sum: fsMeta.Meta["md5Sum"], - ContentType: contentType, + ContentType: fsMeta.Meta["content-type"], ContentEncoding: fsMeta.Meta["content-encoding"], UserDefined: fsMeta.Meta, }, nil diff --git a/handler-utils.go b/handler-utils.go index 8c43181bf..085fb2850 100644 --- a/handler-utils.go +++ b/handler-utils.go @@ -19,6 +19,7 @@ package main import ( "io" "net/http" + "strings" ) // Validates location constraint in PutBucket request body. @@ -58,3 +59,39 @@ func isValidLocationConstraint(r *http.Request) (s3Error APIErrorCode) { } return s3Error } + +// Supported headers that needs to be extracted. +var supportedHeaders = []string{ + "content-type", + "cache-control", + "content-encoding", + "content-disposition", + // Add more supported headers here. +} + +// extractMetadataFromHeader extracts metadata from HTTP header. +func extractMetadataFromHeader(header http.Header) map[string]string { + metadata := make(map[string]string) + // Save standard supported headers. + for _, supportedHeader := range supportedHeaders { + canonicalHeader := http.CanonicalHeaderKey(supportedHeader) + // HTTP headers are case insensitive, look for both canonical + // and non canonical entries. + if _, ok := header[canonicalHeader]; ok { + metadata[supportedHeader] = header.Get(canonicalHeader) + } else if _, ok := header[supportedHeader]; ok { + metadata[supportedHeader] = header.Get(supportedHeader) + } + } + // Go through all other headers for any additional headers that needs to be saved. + for key := range header { + cKey := http.CanonicalHeaderKey(key) + if strings.HasPrefix(cKey, "X-Amz-Meta-") { + metadata[cKey] = header.Get(cKey) + } else if strings.HasPrefix(key, "X-Minio-Meta-") { + metadata[cKey] = header.Get(cKey) + } + } + // Return. + return metadata +} diff --git a/handler-utils_test.go b/handler-utils_test.go index d06d7c565..a52cdbf33 100644 --- a/handler-utils_test.go +++ b/handler-utils_test.go @@ -21,6 +21,7 @@ import ( "encoding/xml" "io/ioutil" "net/http" + "reflect" "testing" ) @@ -84,3 +85,36 @@ func TestIsValidLocationContraint(t *testing.T) { } } } + +// Tests validate metadata extraction from http headers. +func TestExtractMetadataHeaders(t *testing.T) { + testCases := []struct { + header http.Header + metadata map[string]string + }{ + // Validate if there a known 'content-type'. + { + header: http.Header{ + "Content-Type": []string{"image/png"}, + }, + metadata: map[string]string{ + "content-type": "image/png", + }, + }, + // Validate if there are no keys to extract. + { + header: http.Header{ + "test-1": []string{"123"}, + }, + metadata: map[string]string{}, + }, + } + + // Validate if the extracting headers. + for i, testCase := range testCases { + metadata := extractMetadataFromHeader(testCase.header) + if !reflect.DeepEqual(metadata, testCase.metadata) { + t.Fatalf("Test %d failed: Expected \"%#v\", got \"%#v\"", i+1, testCase.metadata, metadata) + } + } +} diff --git a/object-handlers.go b/object-handlers.go index 39e74e69c..1259389ac 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -325,12 +325,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // Save metadata. metadata := make(map[string]string) // Save other metadata if available. - if objInfo.ContentType != "" { - metadata["content-type"] = objInfo.ContentType - } - if objInfo.ContentEncoding != "" { - metadata["content-encoding"] = objInfo.ContentEncoding - } + metadata = objInfo.UserDefined + // Do not set `md5sum` as CopyObject will not keep the // same md5sum as the source. @@ -392,27 +388,10 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - // Save metadata. - metadata := make(map[string]string) + // Extract metadata to be saved from incoming HTTP header. + metadata := extractMetadataFromHeader(r.Header) // Make sure we hex encode md5sum here. metadata["md5Sum"] = hex.EncodeToString(md5Bytes) - // Save other metadata if available. - contentType := r.Header.Get("Content-Type") - if contentType != "" { - metadata["content-type"] = contentType - } - contentEncoding := r.Header.Get("Content-Encoding") - if contentEncoding != "" { - metadata["content-encoding"] = contentEncoding - } - for key := range r.Header { - cKey := http.CanonicalHeaderKey(key) - if strings.HasPrefix(cKey, "X-Amz-Meta-") { - metadata[cKey] = r.Header.Get(cKey) - } else if strings.HasPrefix(key, "X-Minio-Meta-") { - metadata[cKey] = r.Header.Get(cKey) - } - } var md5Sum string switch getRequestAuthType(r) { @@ -472,25 +451,8 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r } } - // Save metadata. - metadata := make(map[string]string) - // Save other metadata if available. - contentType := r.Header.Get("Content-Type") - if contentType != "" { - metadata["content-type"] = contentType - } - contentEncoding := r.Header.Get("Content-Encoding") - if contentEncoding != "" { - metadata["content-encoding"] = contentEncoding - } - for key := range r.Header { - cKey := http.CanonicalHeaderKey(key) - if strings.HasPrefix(cKey, "X-Amz-Meta-") { - metadata[cKey] = r.Header.Get(cKey) - } else if strings.HasPrefix(key, "X-Minio-Meta-") { - metadata[cKey] = r.Header.Get(cKey) - } - } + // Extract metadata that needs to be saved. + metadata := extractMetadataFromHeader(r.Header) uploadID, err := api.ObjectAPI.NewMultipartUpload(bucket, object, metadata) if err != nil {