github.com/cs3org/reva/v2@v2.27.7/pkg/rhttp/datatx/manager/spaces/spaces.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package spaces 20 21 import ( 22 "net/http" 23 "path" 24 "strings" 25 "time" 26 27 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 28 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 29 "github.com/mitchellh/mapstructure" 30 "github.com/pkg/errors" 31 "github.com/rs/zerolog" 32 33 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" 34 "github.com/cs3org/reva/v2/pkg/appctx" 35 "github.com/cs3org/reva/v2/pkg/errtypes" 36 "github.com/cs3org/reva/v2/pkg/events" 37 "github.com/cs3org/reva/v2/pkg/rhttp/datatx" 38 "github.com/cs3org/reva/v2/pkg/rhttp/datatx/manager/registry" 39 "github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics" 40 "github.com/cs3org/reva/v2/pkg/rhttp/datatx/utils/download" 41 "github.com/cs3org/reva/v2/pkg/rhttp/router" 42 "github.com/cs3org/reva/v2/pkg/storage" 43 "github.com/cs3org/reva/v2/pkg/storage/cache" 44 "github.com/cs3org/reva/v2/pkg/storagespace" 45 "github.com/cs3org/reva/v2/pkg/utils" 46 ) 47 48 func init() { 49 registry.Register("spaces", New) 50 } 51 52 type manager struct { 53 conf *cache.Config 54 publisher events.Publisher 55 log *zerolog.Logger 56 } 57 58 func parseConfig(m map[string]interface{}) (*cache.Config, error) { 59 c := &cache.Config{} 60 if err := mapstructure.Decode(m, c); err != nil { 61 err = errors.Wrap(err, "error decoding conf") 62 return nil, err 63 } 64 return c, nil 65 } 66 67 // New returns a datatx manager implementation that relies on HTTP PUT/GET. 68 func New(m map[string]interface{}, publisher events.Publisher, log *zerolog.Logger) (datatx.DataTX, error) { 69 c, err := parseConfig(m) 70 if err != nil { 71 return nil, err 72 } 73 74 l := log.With().Str("datatx", "spaces").Logger() 75 76 return &manager{ 77 conf: c, 78 publisher: publisher, 79 log: &l, 80 }, nil 81 } 82 83 func (m *manager) Handler(fs storage.FS) (http.Handler, error) { 84 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 85 var spaceID string 86 spaceID, r.URL.Path = router.ShiftPath(r.URL.Path) 87 88 sublog := m.log.With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() 89 r = r.WithContext(appctx.WithLogger(r.Context(), &sublog)) 90 ctx := r.Context() 91 92 switch r.Method { 93 case "GET", "HEAD": 94 if r.Method == "GET" { 95 metrics.DownloadsActive.Add(1) 96 defer func() { 97 metrics.DownloadsActive.Sub(1) 98 }() 99 } 100 download.GetOrHeadFile(w, r, fs, spaceID) 101 case "PUT": 102 metrics.UploadsActive.Add(1) 103 defer func() { 104 metrics.UploadsActive.Sub(1) 105 }() 106 107 // make a clean relative path 108 fn := path.Clean(strings.TrimLeft(r.URL.Path, "/")) 109 defer r.Body.Close() 110 111 rid, err := storagespace.ParseID(spaceID) 112 if err != nil { 113 sublog.Error().Err(err).Msg("failed to parse resourceID") 114 } 115 ref := &provider.Reference{ 116 ResourceId: &rid, 117 Path: fn, 118 } 119 var info *provider.ResourceInfo 120 info, err = fs.Upload(ctx, storage.UploadRequest{ 121 Ref: ref, 122 Body: r.Body, 123 Length: r.ContentLength, 124 }, func(spaceOwner, owner *userpb.UserId, ref *provider.Reference) { 125 if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil { 126 sublog.Error().Err(err).Msg("failed to publish FileUploaded event") 127 } 128 }) 129 switch v := err.(type) { 130 case nil: 131 // set etag, mtime and file id 132 w.Header().Set(net.HeaderETag, info.Etag) 133 w.Header().Set(net.HeaderOCETag, info.Etag) 134 if info.Id != nil { 135 w.Header().Set(net.HeaderOCFileID, storagespace.FormatResourceID(info.Id)) 136 } 137 if info.Mtime != nil { 138 t := utils.TSToTime(info.Mtime).UTC() 139 lastModifiedString := t.Format(time.RFC1123Z) 140 w.Header().Set(net.HeaderLastModified, lastModifiedString) 141 } 142 w.WriteHeader(http.StatusOK) 143 case errtypes.PartialContent: 144 w.WriteHeader(http.StatusPartialContent) 145 case errtypes.ChecksumMismatch: 146 w.WriteHeader(errtypes.StatusChecksumMismatch) 147 case errtypes.NotFound: 148 w.WriteHeader(http.StatusNotFound) 149 case errtypes.PermissionDenied: 150 w.WriteHeader(http.StatusForbidden) 151 case errtypes.InvalidCredentials: 152 w.WriteHeader(http.StatusUnauthorized) 153 case errtypes.InsufficientStorage: 154 w.WriteHeader(http.StatusInsufficientStorage) 155 case errtypes.PreconditionFailed, errtypes.Aborted, errtypes.AlreadyExists: 156 w.WriteHeader(http.StatusPreconditionFailed) 157 default: 158 sublog.Error().Err(v).Msg("error uploading file") 159 w.WriteHeader(http.StatusInternalServerError) 160 } 161 return 162 default: 163 w.WriteHeader(http.StatusNotImplemented) 164 } 165 }) 166 return h, nil 167 }