github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/move.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 "fmt" 24 "net/http" 25 "path" 26 27 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 28 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 29 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" 30 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" 31 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" 32 "github.com/cs3org/reva/v2/pkg/appctx" 33 "github.com/cs3org/reva/v2/pkg/errtypes" 34 rstatus "github.com/cs3org/reva/v2/pkg/rgrpc/status" 35 "github.com/cs3org/reva/v2/pkg/rhttp/router" 36 "github.com/cs3org/reva/v2/pkg/storagespace" 37 "github.com/cs3org/reva/v2/pkg/utils" 38 "github.com/rs/zerolog" 39 ) 40 41 func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) { 42 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "move") 43 defer span.End() 44 45 if r.Body != http.NoBody { 46 w.WriteHeader(http.StatusUnsupportedMediaType) 47 b, err := errors.Marshal(http.StatusUnsupportedMediaType, "body must be empty", "", "") 48 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 49 return 50 } 51 52 srcPath := path.Join(ns, r.URL.Path) 53 dh := r.Header.Get(net.HeaderDestination) 54 baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) 55 dstPath, err := net.ParseDestination(baseURI, dh) 56 if err != nil { 57 w.WriteHeader(http.StatusBadRequest) 58 b, err := errors.Marshal(http.StatusBadRequest, "failed to extract destination", "", "") 59 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 60 return 61 } 62 63 if err := ValidateName(filename(srcPath), s.nameValidators); err != nil { 64 w.WriteHeader(http.StatusBadRequest) 65 b, err := errors.Marshal(http.StatusBadRequest, "source failed naming rules", "", "") 66 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 67 return 68 } 69 70 if err := ValidateDestination(filename(dstPath), s.nameValidators); err != nil { 71 w.WriteHeader(http.StatusBadRequest) 72 b, err := errors.Marshal(http.StatusBadRequest, "destination naming rules", "", "") 73 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 74 return 75 } 76 77 dstPath = path.Join(ns, dstPath) 78 79 sublog := appctx.GetLogger(ctx).With().Str("src", srcPath).Str("dst", dstPath).Logger() 80 81 srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, srcPath) 82 if err != nil { 83 sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up source storage space") 84 w.WriteHeader(http.StatusInternalServerError) 85 return 86 } 87 if status.Code != rpc.Code_CODE_OK { 88 errors.HandleErrorStatus(&sublog, w, status) 89 return 90 } 91 dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, dstPath) 92 if err != nil { 93 sublog.Error().Err(err).Str("path", dstPath).Msg("failed to look up destination storage space") 94 w.WriteHeader(http.StatusInternalServerError) 95 return 96 } 97 if status.Code != rpc.Code_CODE_OK { 98 errors.HandleErrorStatus(&sublog, w, status) 99 return 100 } 101 102 s.handleMove(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, srcPath, false), spacelookup.MakeRelativeReference(dstSpace, dstPath, false), sublog) 103 } 104 105 func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceID string) { 106 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "spaces_move") 107 defer span.End() 108 109 if r.Body != http.NoBody { 110 w.WriteHeader(http.StatusUnsupportedMediaType) 111 b, err := errors.Marshal(http.StatusUnsupportedMediaType, "body must be empty", "", "") 112 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 113 return 114 } 115 116 dh := r.Header.Get(net.HeaderDestination) 117 baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) 118 dst, err := net.ParseDestination(baseURI, dh) 119 if err != nil { 120 w.WriteHeader(http.StatusBadRequest) 121 return 122 } 123 124 sublog := appctx.GetLogger(ctx).With().Str("spaceid", srcSpaceID).Str("path", r.URL.Path).Logger() 125 126 srcRef, err := spacelookup.MakeStorageSpaceReference(srcSpaceID, r.URL.Path) 127 if err != nil { 128 w.WriteHeader(http.StatusBadRequest) 129 return 130 } 131 132 dstSpaceID, dstRelPath := router.ShiftPath(dst) 133 134 dstRef, err := spacelookup.MakeStorageSpaceReference(dstSpaceID, dstRelPath) 135 if err != nil { 136 w.WriteHeader(http.StatusBadRequest) 137 return 138 } 139 140 s.handleMove(ctx, w, r, &srcRef, &dstRef, sublog) 141 } 142 143 func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, log zerolog.Logger) { 144 isChild, err := s.referenceIsChildOf(ctx, s.gatewaySelector, dst, src) 145 if err != nil { 146 switch err.(type) { 147 case errtypes.IsNotFound: 148 w.WriteHeader(http.StatusNotFound) 149 case errtypes.IsNotSupported: 150 log.Error().Err(err).Msg("can not detect recursive move operation. missing machine auth configuration?") 151 w.WriteHeader(http.StatusForbidden) 152 default: 153 log.Error().Err(err).Msg("error while trying to detect recursive move operation") 154 w.WriteHeader(http.StatusInternalServerError) 155 } 156 return 157 } 158 if isChild { 159 w.WriteHeader(http.StatusConflict) 160 b, err := errors.Marshal(http.StatusBadRequest, "can not move a folder into one of its children", "", "") 161 errors.HandleWebdavError(&log, w, b, err) 162 return 163 } 164 165 isParent, err := s.referenceIsChildOf(ctx, s.gatewaySelector, src, dst) 166 if err != nil { 167 switch err.(type) { 168 case errtypes.IsNotFound: 169 isParent = false 170 case errtypes.IsNotSupported: 171 log.Error().Err(err).Msg("can not detect recursive move operation. missing machine auth configuration?") 172 w.WriteHeader(http.StatusForbidden) 173 return 174 default: 175 log.Error().Err(err).Msg("error while trying to detect recursive move operation") 176 w.WriteHeader(http.StatusInternalServerError) 177 return 178 } 179 } 180 if isParent { 181 w.WriteHeader(http.StatusConflict) 182 b, err := errors.Marshal(http.StatusBadRequest, "can not move a folder into its parent", "", "") 183 errors.HandleWebdavError(&log, w, b, err) 184 return 185 186 } 187 188 oh := r.Header.Get(net.HeaderOverwrite) 189 log.Debug().Str("overwrite", oh).Msg("move") 190 191 overwrite, err := net.ParseOverwrite(oh) 192 if err != nil { 193 w.WriteHeader(http.StatusBadRequest) 194 return 195 } 196 197 client, err := s.gatewaySelector.Next() 198 if err != nil { 199 log.Error().Err(err).Msg("error selecting next client") 200 w.WriteHeader(http.StatusInternalServerError) 201 return 202 } 203 204 // check src exists 205 srcStatReq := &provider.StatRequest{Ref: src} 206 srcStatRes, err := client.Stat(ctx, srcStatReq) 207 if err != nil { 208 log.Error().Err(err).Msg("error sending grpc stat request") 209 w.WriteHeader(http.StatusInternalServerError) 210 return 211 } 212 if srcStatRes.Status.Code != rpc.Code_CODE_OK { 213 if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { 214 w.WriteHeader(http.StatusNotFound) 215 m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) 216 b, err := errors.Marshal(http.StatusNotFound, m, "", "") 217 errors.HandleWebdavError(&log, w, b, err) 218 } 219 errors.HandleErrorStatus(&log, w, srcStatRes.Status) 220 return 221 } 222 if utils.IsSpaceRoot(srcStatRes.GetInfo()) { 223 log.Error().Msg("the source is disallowed") 224 w.WriteHeader(http.StatusBadRequest) 225 return 226 } 227 228 // check dst exists 229 dstStatReq := &provider.StatRequest{Ref: dst} 230 dstStatRes, err := client.Stat(ctx, dstStatReq) 231 if err != nil { 232 log.Error().Err(err).Msg("error sending grpc stat request") 233 w.WriteHeader(http.StatusInternalServerError) 234 return 235 } 236 if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { 237 errors.HandleErrorStatus(&log, w, dstStatRes.Status) 238 return 239 } 240 241 successCode := http.StatusCreated // 201 if new resource was created, see https://tools.ietf.org/html/rfc4918#section-9.9.4 242 if dstStatRes.Status.Code == rpc.Code_CODE_OK { 243 successCode = http.StatusNoContent // 204 if target already existed, see https://tools.ietf.org/html/rfc4918#section-9.9.4 244 245 if utils.IsSpaceRoot(dstStatRes.GetInfo()) { 246 log.Error().Msg("overwriting is not allowed") 247 w.WriteHeader(http.StatusBadRequest) 248 return 249 } 250 if !overwrite { 251 log.Warn().Bool("overwrite", overwrite).Msg("dst already exists") 252 w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.9.4 253 return 254 } 255 // delete existing tree 256 delReq := &provider.DeleteRequest{Ref: dst} 257 delRes, err := client.Delete(ctx, delReq) 258 if err != nil { 259 log.Error().Err(err).Msg("error sending grpc delete request") 260 w.WriteHeader(http.StatusInternalServerError) 261 return 262 } 263 264 if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { 265 errors.HandleErrorStatus(&log, w, delRes.Status) 266 return 267 } 268 } else { 269 // check if an intermediate path / the parent exists 270 intStatReq := &provider.StatRequest{Ref: &provider.Reference{ 271 ResourceId: dst.ResourceId, 272 Path: utils.MakeRelativePath(path.Dir(dst.Path)), 273 }} 274 intStatRes, err := client.Stat(ctx, intStatReq) 275 if err != nil { 276 log.Error().Err(err).Msg("error sending grpc stat request") 277 w.WriteHeader(http.StatusInternalServerError) 278 return 279 } 280 if intStatRes.Status.Code != rpc.Code_CODE_OK { 281 if intStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { 282 // 409 if intermediate dir is missing, see https://tools.ietf.org/html/rfc4918#section-9.8.5 283 log.Debug().Interface("parent", dst).Interface("status", intStatRes.Status).Msg("conflict") 284 w.WriteHeader(http.StatusConflict) 285 } else { 286 errors.HandleErrorStatus(&log, w, intStatRes.Status) 287 } 288 return 289 } 290 // TODO what if intermediate is a file? 291 } 292 // resolve the destination path 293 if dst.Path == "." { 294 dst.Path = utils.MakeRelativePath(dstStatRes.GetInfo().GetName()) 295 dst.ResourceId = dstStatRes.GetInfo().GetParentId() 296 } 297 mReq := &provider.MoveRequest{ 298 Source: src, 299 Destination: dst, 300 LockId: requestLockToken(r), 301 } 302 mRes, err := client.Move(ctx, mReq) 303 if err != nil { 304 log.Error().Err(err).Msg("error sending move grpc request") 305 w.WriteHeader(http.StatusInternalServerError) 306 return 307 } 308 309 if mRes.Status.Code != rpc.Code_CODE_OK { 310 status := rstatus.HTTPStatusFromCode(mRes.Status.Code) 311 m := mRes.Status.Message 312 switch mRes.Status.Code { 313 case rpc.Code_CODE_ABORTED: 314 status = http.StatusPreconditionFailed 315 case rpc.Code_CODE_PERMISSION_DENIED: 316 status = http.StatusForbidden 317 case rpc.Code_CODE_UNIMPLEMENTED: 318 // We translate this into a Bad Gateway error as per https://www.rfc-editor.org/rfc/rfc4918#section-9.9.4 319 // > 502 (Bad Gateway) - This may occur when the destination is on another 320 // > server and the destination server refuses to accept the resource. 321 // > This could also occur when the destination is on another sub-section 322 // > of the same server namespace. 323 status = http.StatusBadGateway 324 } 325 326 w.WriteHeader(status) 327 328 b, err := errors.Marshal(status, m, "", "") 329 errors.HandleWebdavError(&log, w, b, err) 330 return 331 } 332 333 dstStatRes, err = client.Stat(ctx, dstStatReq) 334 if err != nil { 335 log.Error().Err(err).Msg("error sending grpc stat request") 336 w.WriteHeader(http.StatusInternalServerError) 337 return 338 } 339 340 if dstStatRes.Status.Code != rpc.Code_CODE_OK { 341 errors.HandleErrorStatus(&log, w, dstStatRes.Status) 342 return 343 } 344 345 info := dstStatRes.Info 346 w.Header().Set(net.HeaderContentType, info.MimeType) 347 w.Header().Set(net.HeaderETag, info.Etag) 348 w.Header().Set(net.HeaderOCFileID, storagespace.FormatResourceID(info.Id)) 349 w.Header().Set(net.HeaderOCETag, info.Etag) 350 w.WriteHeader(successCode) 351 }