Merge branch '19088-s3-properties-tags'
[arvados.git] / services / keep-web / s3.go
index 4117dafbc6f248e14cafa41cbfdd0193d1408fc9..90b75f8a306019c2b646d15228da0c1c54a62956 100644 (file)
@@ -14,6 +14,7 @@ import (
        "fmt"
        "hash"
        "io"
+       "mime"
        "net/http"
        "net/textproto"
        "net/url"
@@ -387,7 +388,11 @@ func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
                if r.Method == "HEAD" && !objectNameGiven {
                        // HeadBucket
                        if err == nil && fi.IsDir() {
-                               setFileInfoHeaders(w.Header(), fs, fspath)
+                               err = setFileInfoHeaders(w.Header(), fs, fspath)
+                               if err != nil {
+                                       s3ErrorResponse(w, InternalError, err.Error(), r.URL.Path, http.StatusBadGateway)
+                                       return true
+                               }
                                w.WriteHeader(http.StatusOK)
                        } else if os.IsNotExist(err) {
                                s3ErrorResponse(w, NoSuchBucket, "The specified bucket does not exist.", r.URL.Path, http.StatusNotFound)
@@ -397,7 +402,11 @@ func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
                        return true
                }
                if err == nil && fi.IsDir() && objectNameGiven && strings.HasSuffix(fspath, "/") && h.Cluster.Collections.S3FolderObjects {
-                       setFileInfoHeaders(w.Header(), fs, fspath)
+                       err = setFileInfoHeaders(w.Header(), fs, fspath)
+                       if err != nil {
+                               s3ErrorResponse(w, InternalError, err.Error(), r.URL.Path, http.StatusBadGateway)
+                               return true
+                       }
                        w.Header().Set("Content-Type", "application/x-directory")
                        w.WriteHeader(http.StatusOK)
                        return true
@@ -419,7 +428,11 @@ func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
                // shallow copy r, and change URL path
                r := *r
                r.URL.Path = fspath
-               setFileInfoHeaders(w.Header(), fs, fspath)
+               err = setFileInfoHeaders(w.Header(), fs, fspath)
+               if err != nil {
+                       s3ErrorResponse(w, InternalError, err.Error(), r.URL.Path, http.StatusBadGateway)
+                       return true
+               }
                http.FileServer(fs).ServeHTTP(w, &r)
                return true
        case r.Method == http.MethodPut:
@@ -591,13 +604,21 @@ func (h *handler) serveS3(w http.ResponseWriter, r *http.Request) bool {
        }
 }
 
-func setFileInfoHeaders(header http.Header, fs arvados.CustomFileSystem, path string) {
+func setFileInfoHeaders(header http.Header, fs arvados.CustomFileSystem, path string) error {
+       maybeEncode := func(s string) string {
+               for _, c := range s {
+                       if c > '\u007f' {
+                               return mime.BEncoding.Encode("UTF-8", s)
+                       }
+               }
+               return s
+       }
        path = strings.TrimSuffix(path, "/")
        var props map[string]interface{}
        for {
                fi, err := fs.Stat(path)
                if err != nil {
-                       return
+                       return err
                }
                switch src := fi.Sys().(type) {
                case *arvados.Collection:
@@ -605,10 +626,13 @@ func setFileInfoHeaders(header http.Header, fs arvados.CustomFileSystem, path st
                case *arvados.Group:
                        props = src.Properties
                default:
+                       if err, ok := src.(error); ok {
+                               return err
+                       }
                        // Try parent
                        cut := strings.LastIndexByte(path, '/')
                        if cut < 0 {
-                               return
+                               return nil
                        }
                        path = path[:cut]
                        continue
@@ -621,11 +645,12 @@ func setFileInfoHeaders(header http.Header, fs arvados.CustomFileSystem, path st
                }
                k = "x-amz-meta-" + k
                if s, ok := v.(string); ok {
-                       header.Set(k, s)
+                       header.Set(k, maybeEncode(s))
                } else if j, err := json.Marshal(v); err == nil {
-                       header.Set(k, string(j))
+                       header.Set(k, maybeEncode(string(j)))
                }
        }
+       return nil
 }
 
 func validMIMEHeaderKey(k string) bool {