github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/versions.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 ocdav 20 21 import ( 22 "context" 23 "net/http" 24 "path" 25 26 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config" 27 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" 28 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" 29 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" 30 "github.com/cs3org/reva/v2/pkg/storagespace" 31 "github.com/cs3org/reva/v2/pkg/utils" 32 33 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 34 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 35 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 36 "github.com/cs3org/reva/v2/pkg/appctx" 37 "github.com/cs3org/reva/v2/pkg/rhttp/router" 38 ) 39 40 // VersionsHandler handles version requests 41 type VersionsHandler struct { 42 } 43 44 func (h *VersionsHandler) init(c *config.Config) error { 45 return nil 46 } 47 48 // Handler handles requests 49 // versions can be listed with a PROPFIND to /remote.php/dav/meta/<fileid>/v 50 // a version is identified by a timestamp, eg. /remote.php/dav/meta/<fileid>/v/1561410426 51 func (h *VersionsHandler) Handler(s *svc, rid *provider.ResourceId) http.Handler { 52 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 53 ctx := r.Context() 54 55 if rid == nil { 56 http.Error(w, "404 Not Found", http.StatusNotFound) 57 return 58 } 59 60 // baseURI is encoded as part of the response payload in href field 61 baseURI := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), storagespace.FormatResourceID(rid)) 62 ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) 63 r = r.WithContext(ctx) 64 65 var key string 66 key, r.URL.Path = router.ShiftPath(r.URL.Path) 67 if r.Method == http.MethodOptions { 68 s.handleOptions(w, r) 69 return 70 } 71 if key == "" && r.Method == MethodPropfind { 72 h.doListVersions(w, r, s, rid) 73 return 74 } 75 if key != "" { 76 switch r.Method { 77 case MethodCopy: 78 // TODO(jfd) cs3api has no delete file version call 79 // TODO(jfd) restore version to given Destination, but cs3api has no destination 80 h.doRestore(w, r, s, rid, key) 81 return 82 case http.MethodHead: 83 log := appctx.GetLogger(ctx) 84 ref := &provider.Reference{ 85 ResourceId: &provider.ResourceId{ 86 StorageId: rid.StorageId, 87 SpaceId: rid.SpaceId, 88 OpaqueId: key, 89 }, 90 Path: utils.MakeRelativePath(r.URL.Path), 91 } 92 s.handleHead(ctx, w, r, ref, *log) 93 return 94 case http.MethodGet: 95 log := appctx.GetLogger(ctx) 96 ref := &provider.Reference{ 97 ResourceId: &provider.ResourceId{ 98 StorageId: rid.StorageId, 99 SpaceId: rid.SpaceId, 100 OpaqueId: key, 101 }, 102 Path: utils.MakeRelativePath(r.URL.Path), 103 } 104 s.handleGet(ctx, w, r, ref, "spaces", *log) 105 return 106 } 107 } 108 109 http.Error(w, "501 Forbidden", http.StatusNotImplemented) 110 }) 111 } 112 113 func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, s *svc, rid *provider.ResourceId) { 114 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "listVersions") 115 defer span.End() 116 117 sublog := appctx.GetLogger(ctx).With().Interface("resourceid", rid).Logger() 118 119 pf, status, err := propfind.ReadPropfind(r.Body) 120 if err != nil { 121 sublog.Debug().Err(err).Msg("error reading propfind request") 122 w.WriteHeader(status) 123 return 124 } 125 126 client, err := s.gatewaySelector.Next() 127 if err != nil { 128 sublog.Error().Err(err).Msg("error selecting next gateway client") 129 w.WriteHeader(http.StatusInternalServerError) 130 return 131 } 132 ref := &provider.Reference{ResourceId: rid} 133 res, err := client.Stat(ctx, &provider.StatRequest{Ref: ref}) 134 if err != nil { 135 sublog.Error().Err(err).Msg("error sending a grpc stat request") 136 w.WriteHeader(http.StatusInternalServerError) 137 return 138 } 139 if res.Status.Code != rpc.Code_CODE_OK { 140 if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED || res.Status.Code == rpc.Code_CODE_NOT_FOUND { 141 w.WriteHeader(http.StatusNotFound) 142 b, err := errors.Marshal(http.StatusNotFound, "Resource not found", "", "") 143 errors.HandleWebdavError(&sublog, w, b, err) 144 return 145 } 146 errors.HandleErrorStatus(&sublog, w, res.Status) 147 return 148 } 149 150 info := res.Info 151 152 lvRes, err := client.ListFileVersions(ctx, &provider.ListFileVersionsRequest{Ref: ref}) 153 if err != nil { 154 sublog.Error().Err(err).Msg("error sending list container grpc request") 155 w.WriteHeader(http.StatusInternalServerError) 156 return 157 } 158 if lvRes.Status.Code != rpc.Code_CODE_OK { 159 if lvRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 160 w.WriteHeader(http.StatusForbidden) 161 b, err := errors.Marshal(http.StatusForbidden, "You have no permission to list file versions on this resource", "", "") 162 errors.HandleWebdavError(&sublog, w, b, err) 163 return 164 } 165 errors.HandleErrorStatus(&sublog, w, lvRes.Status) 166 return 167 } 168 169 versions := lvRes.GetVersions() 170 infos := make([]*provider.ResourceInfo, 0, len(versions)+1) 171 // add version dir . entry, derived from file info 172 infos = append(infos, &provider.ResourceInfo{ 173 Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, 174 }) 175 176 for i := range versions { 177 vi := &provider.ResourceInfo{ 178 // TODO(jfd) we cannot access version content, this will be a problem when trying to fetch version thumbnails 179 // Opaque 180 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 181 Id: &provider.ResourceId{ 182 StorageId: "versions", 183 OpaqueId: info.Id.OpaqueId + "@" + versions[i].GetKey(), 184 }, 185 // Checksum 186 Etag: versions[i].Etag, 187 // MimeType 188 Mtime: &types.Timestamp{ 189 Seconds: versions[i].Mtime, 190 // TODO cs3apis FileVersion should use types.Timestamp instead of uint64 191 }, 192 Path: path.Join("v", versions[i].Key), 193 // PermissionSet 194 Size: versions[i].Size, 195 Owner: info.Owner, 196 } 197 infos = append(infos, vi) 198 } 199 200 prefer := net.ParsePrefer(r.Header.Get("prefer")) 201 returnMinimal := prefer[net.HeaderPreferReturn] == "minimal" 202 203 propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, "", nil, returnMinimal) 204 if err != nil { 205 sublog.Error().Err(err).Msg("error formatting propfind") 206 w.WriteHeader(http.StatusInternalServerError) 207 return 208 } 209 w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") 210 w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") 211 w.Header().Set(net.HeaderVary, net.HeaderPrefer) 212 if returnMinimal { 213 w.Header().Set(net.HeaderPreferenceApplied, "return=minimal") 214 } 215 w.WriteHeader(http.StatusMultiStatus) 216 _, err = w.Write(propRes) 217 if err != nil { 218 sublog.Error().Err(err).Msg("error writing body") 219 return 220 } 221 222 } 223 224 func (h *VersionsHandler) doRestore(w http.ResponseWriter, r *http.Request, s *svc, rid *provider.ResourceId, key string) { 225 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "restore") 226 defer span.End() 227 228 sublog := appctx.GetLogger(ctx).With().Interface("resourceid", rid).Str("key", key).Logger() 229 230 req := &provider.RestoreFileVersionRequest{ 231 Ref: &provider.Reference{ResourceId: rid}, 232 Key: key, 233 } 234 235 client, err := s.gatewaySelector.Next() 236 if err != nil { 237 sublog.Error().Err(err).Msg("error selecting next gateway client") 238 w.WriteHeader(http.StatusInternalServerError) 239 return 240 } 241 res, err := client.RestoreFileVersion(ctx, req) 242 if err != nil { 243 sublog.Error().Err(err).Msg("error sending a grpc restore version request") 244 w.WriteHeader(http.StatusInternalServerError) 245 return 246 } 247 if res.Status.Code != rpc.Code_CODE_OK { 248 if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 249 w.WriteHeader(http.StatusForbidden) 250 b, err := errors.Marshal(http.StatusForbidden, "You have no permission to restore versions on this resource", "", "") 251 errors.HandleWebdavError(&sublog, w, b, err) 252 return 253 } 254 errors.HandleErrorStatus(&sublog, w, res.Status) 255 return 256 } 257 w.WriteHeader(http.StatusNoContent) 258 }