github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/copy.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 "path/filepath" 27 "strconv" 28 29 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 30 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 31 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 32 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 33 "github.com/cs3org/reva/v2/internal/http/services/datagateway" 34 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" 35 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" 36 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" 37 "github.com/cs3org/reva/v2/pkg/appctx" 38 "github.com/cs3org/reva/v2/pkg/errtypes" 39 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 40 "github.com/cs3org/reva/v2/pkg/rhttp" 41 "github.com/cs3org/reva/v2/pkg/rhttp/router" 42 "github.com/cs3org/reva/v2/pkg/storagespace" 43 "github.com/cs3org/reva/v2/pkg/utils" 44 "github.com/rs/zerolog" 45 ) 46 47 type copy struct { 48 source *provider.Reference 49 sourceInfo *provider.ResourceInfo 50 destination *provider.Reference 51 depth net.Depth 52 successCode int 53 } 54 55 func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) { 56 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "copy") 57 defer span.End() 58 59 if r.Body != http.NoBody { 60 w.WriteHeader(http.StatusUnsupportedMediaType) 61 b, err := errors.Marshal(http.StatusUnsupportedMediaType, "body must be empty", "", "") 62 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 63 return 64 } 65 66 if s.c.EnableHTTPTpc { 67 if r.Header.Get("Source") != "" { 68 // HTTP Third-Party Copy Pull mode 69 s.handleTPCPull(ctx, w, r, ns) 70 return 71 } else if r.Header.Get("Destination") != "" { 72 // HTTP Third-Party Copy Push mode 73 s.handleTPCPush(ctx, w, r, ns) 74 return 75 } 76 } 77 78 // Local copy: in this case Destination is mandatory 79 src := path.Join(ns, r.URL.Path) 80 81 dh := r.Header.Get(net.HeaderDestination) 82 baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) 83 dst, err := net.ParseDestination(baseURI, dh) 84 if err != nil { 85 w.WriteHeader(http.StatusBadRequest) 86 b, err := errors.Marshal(http.StatusBadRequest, "failed to extract destination", "", "") 87 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 88 return 89 } 90 91 if err := ValidateName(filename(src), s.nameValidators); err != nil { 92 w.WriteHeader(http.StatusBadRequest) 93 b, err := errors.Marshal(http.StatusBadRequest, "source failed naming rules", "", "") 94 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 95 return 96 } 97 98 if err := ValidateDestination(filename(dst), s.nameValidators); err != nil { 99 w.WriteHeader(http.StatusBadRequest) 100 b, err := errors.Marshal(http.StatusBadRequest, "destination failed naming rules", "", "") 101 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 102 return 103 } 104 105 dst = path.Join(ns, dst) 106 107 sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() 108 109 srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, src) 110 if err != nil { 111 sublog.Error().Err(err).Str("path", src).Msg("failed to look up storage space") 112 w.WriteHeader(http.StatusInternalServerError) 113 return 114 } 115 if status.Code != rpc.Code_CODE_OK { 116 errors.HandleErrorStatus(&sublog, w, status) 117 return 118 } 119 dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, dst) 120 if err != nil { 121 sublog.Error().Err(err).Str("path", dst).Msg("failed to look up storage space") 122 w.WriteHeader(http.StatusInternalServerError) 123 return 124 } 125 if status.Code != rpc.Code_CODE_OK { 126 errors.HandleErrorStatus(&sublog, w, status) 127 return 128 } 129 130 cp := s.prepareCopy(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, src, false), spacelookup.MakeRelativeReference(dstSpace, dst, false), &sublog, dstSpace.GetRoot().GetStorageId() == utils.ShareStorageProviderID) 131 if cp == nil { 132 return 133 } 134 135 if err := s.executePathCopy(ctx, s.gatewaySelector, w, r, cp); err != nil { 136 sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error executing path copy") 137 w.WriteHeader(http.StatusInternalServerError) 138 } 139 w.WriteHeader(cp.successCode) 140 } 141 142 func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], w http.ResponseWriter, r *http.Request, cp *copy) error { 143 log := appctx.GetLogger(ctx) 144 log.Debug().Str("src", cp.sourceInfo.Path).Str("dst", cp.destination.Path).Msg("descending") 145 146 client, err := selector.Next() 147 if err != nil { 148 return err 149 } 150 151 var fileid string 152 if cp.sourceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { 153 // create dir 154 createReq := &provider.CreateContainerRequest{ 155 Ref: cp.destination, 156 } 157 createRes, err := client.CreateContainer(ctx, createReq) 158 if err != nil { 159 log.Error().Err(err).Msg("error performing create container grpc request") 160 return err 161 } 162 if createRes.Status.Code != rpc.Code_CODE_OK { 163 if createRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 164 w.WriteHeader(http.StatusForbidden) 165 m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) 166 b, err := errors.Marshal(http.StatusForbidden, m, "", "") 167 errors.HandleWebdavError(log, w, b, err) 168 } 169 return nil 170 } 171 172 // TODO: also copy properties: https://tools.ietf.org/html/rfc4918#section-9.8.2 173 174 if cp.depth != net.DepthInfinity { 175 return nil 176 } 177 178 // descend for children 179 listReq := &provider.ListContainerRequest{ 180 Ref: cp.source, 181 } 182 res, err := client.ListContainer(ctx, listReq) 183 if err != nil { 184 return err 185 } 186 if res.Status.Code != rpc.Code_CODE_OK { 187 w.WriteHeader(http.StatusInternalServerError) 188 return nil 189 } 190 191 for i := range res.Infos { 192 child := filepath.Base(res.Infos[i].Path) 193 src := &provider.Reference{ 194 ResourceId: cp.source.ResourceId, 195 Path: utils.MakeRelativePath(filepath.Join(cp.source.Path, child)), 196 } 197 childDst := &provider.Reference{ 198 ResourceId: cp.destination.ResourceId, 199 Path: utils.MakeRelativePath(filepath.Join(cp.destination.Path, child)), 200 } 201 err := s.executePathCopy(ctx, selector, w, r, ©{source: src, sourceInfo: res.Infos[i], destination: childDst, depth: cp.depth, successCode: cp.successCode}) 202 if err != nil { 203 return err 204 } 205 } 206 207 // we need to stat again to get the fileid 208 r, err := client.Stat(ctx, &provider.StatRequest{Ref: cp.destination}) 209 if err != nil { 210 return err 211 } 212 213 if r.GetStatus().GetCode() != rpc.Code_CODE_OK { 214 return fmt.Errorf("status code %d", r.GetStatus().GetCode()) 215 } 216 217 fileid = storagespace.FormatResourceID(r.GetInfo().GetId()) 218 } else { 219 // copy file 220 221 // 1. get download url 222 223 dReq := &provider.InitiateFileDownloadRequest{ 224 Ref: cp.source, 225 } 226 227 dRes, err := client.InitiateFileDownload(ctx, dReq) 228 if err != nil { 229 return err 230 } 231 232 if dRes.Status.Code != rpc.Code_CODE_OK { 233 return fmt.Errorf("status code %d", dRes.Status.Code) 234 } 235 236 var downloadEP, downloadToken string 237 for _, p := range dRes.Protocols { 238 if p.Protocol == "spaces" { 239 downloadEP, downloadToken = p.DownloadEndpoint, p.Token 240 } 241 } 242 243 // 2. get upload url 244 245 uReq := &provider.InitiateFileUploadRequest{ 246 Ref: cp.destination, 247 Opaque: &typespb.Opaque{ 248 Map: map[string]*typespb.OpaqueEntry{ 249 "Upload-Length": { 250 Decoder: "plain", 251 // TODO: handle case where size is not known in advance 252 Value: []byte(strconv.FormatUint(cp.sourceInfo.GetSize(), 10)), 253 }, 254 }, 255 }, 256 } 257 258 uRes, err := client.InitiateFileUpload(ctx, uReq) 259 if err != nil { 260 return err 261 } 262 263 if uRes.Status.Code != rpc.Code_CODE_OK { 264 if uRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 265 w.WriteHeader(http.StatusForbidden) 266 m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) 267 b, err := errors.Marshal(http.StatusForbidden, m, "", "") 268 errors.HandleWebdavError(log, w, b, err) 269 return nil 270 } 271 errors.HandleErrorStatus(log, w, uRes.Status) 272 return nil 273 } 274 275 var uploadEP, uploadToken string 276 for _, p := range uRes.Protocols { 277 if p.Protocol == "simple" { 278 uploadEP, uploadToken = p.UploadEndpoint, p.Token 279 } 280 } 281 282 // 3. do download 283 284 httpDownloadReq, err := rhttp.NewRequest(ctx, "GET", downloadEP, nil) 285 if err != nil { 286 return err 287 } 288 httpDownloadReq.Header.Set(datagateway.TokenTransportHeader, downloadToken) 289 290 httpDownloadRes, err := s.client.Do(httpDownloadReq) 291 if err != nil { 292 return err 293 } 294 defer httpDownloadRes.Body.Close() 295 if httpDownloadRes.StatusCode == http.StatusForbidden { 296 w.WriteHeader(http.StatusForbidden) 297 b, err := errors.Marshal(http.StatusForbidden, http.StatusText(http.StatusForbidden), "", strconv.Itoa(http.StatusForbidden)) 298 errors.HandleWebdavError(log, w, b, err) 299 return nil 300 } 301 if httpDownloadRes.StatusCode != http.StatusOK { 302 return fmt.Errorf("status code %d", httpDownloadRes.StatusCode) 303 } 304 305 // 4. do upload 306 307 httpUploadReq, err := rhttp.NewRequest(ctx, "PUT", uploadEP, httpDownloadRes.Body) 308 if err != nil { 309 return err 310 } 311 httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken) 312 httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize()) 313 314 httpUploadRes, err := s.client.Do(httpUploadReq) 315 if err != nil { 316 return err 317 } 318 defer httpUploadRes.Body.Close() 319 if httpUploadRes.StatusCode != http.StatusOK { 320 return err 321 } 322 323 fileid = httpUploadRes.Header.Get(net.HeaderOCFileID) 324 } 325 326 w.Header().Set(net.HeaderOCFileID, fileid) 327 return nil 328 } 329 330 func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID string) { 331 ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "spaces_copy") 332 defer span.End() 333 334 if r.Body != http.NoBody { 335 w.WriteHeader(http.StatusUnsupportedMediaType) 336 b, err := errors.Marshal(http.StatusUnsupportedMediaType, "body must be empty", "", "") 337 errors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) 338 return 339 } 340 341 dh := r.Header.Get(net.HeaderDestination) 342 baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) 343 dst, err := net.ParseDestination(baseURI, dh) 344 if err != nil { 345 w.WriteHeader(http.StatusBadRequest) 346 return 347 } 348 349 sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Str("destination", dst).Logger() 350 351 srcRef, err := spacelookup.MakeStorageSpaceReference(spaceID, r.URL.Path) 352 if err != nil { 353 w.WriteHeader(http.StatusBadRequest) 354 return 355 } 356 357 dstSpaceID, dstRelPath := router.ShiftPath(dst) 358 359 dstRef, err := spacelookup.MakeStorageSpaceReference(dstSpaceID, dstRelPath) 360 if err != nil { 361 w.WriteHeader(http.StatusBadRequest) 362 return 363 } 364 365 cp := s.prepareCopy(ctx, w, r, &srcRef, &dstRef, &sublog, dstRef.GetResourceId().GetStorageId() == utils.ShareStorageProviderID) 366 if cp == nil { 367 return 368 } 369 370 err = s.executeSpacesCopy(ctx, w, s.gatewaySelector, cp) 371 if err != nil { 372 sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error descending directory") 373 w.WriteHeader(http.StatusInternalServerError) 374 } 375 w.WriteHeader(cp.successCode) 376 } 377 378 func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, selector pool.Selectable[gateway.GatewayAPIClient], cp *copy) error { 379 log := appctx.GetLogger(ctx) 380 log.Debug().Interface("src", cp.sourceInfo).Interface("dst", cp.destination).Msg("descending") 381 382 client, err := selector.Next() 383 if err != nil { 384 return err 385 } 386 387 var fileid string 388 if cp.sourceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { 389 // create dir 390 createReq := &provider.CreateContainerRequest{ 391 Ref: cp.destination, 392 } 393 createRes, err := client.CreateContainer(ctx, createReq) 394 if err != nil { 395 log.Error().Err(err).Msg("error performing create container grpc request") 396 return err 397 } 398 if createRes.Status.Code != rpc.Code_CODE_OK { 399 if createRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 400 w.WriteHeader(http.StatusForbidden) 401 // TODO path could be empty or relative... 402 m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) 403 b, err := errors.Marshal(http.StatusForbidden, m, "", "") 404 errors.HandleWebdavError(log, w, b, err) 405 } 406 return nil 407 } 408 409 // TODO: also copy properties: https://tools.ietf.org/html/rfc4918#section-9.8.2 410 411 if cp.depth != net.DepthInfinity { 412 return nil 413 } 414 415 // descend for children 416 listReq := &provider.ListContainerRequest{Ref: &provider.Reference{ResourceId: cp.sourceInfo.Id, Path: "."}} 417 res, err := client.ListContainer(ctx, listReq) 418 if err != nil { 419 return err 420 } 421 if res.Status.Code != rpc.Code_CODE_OK { 422 w.WriteHeader(http.StatusInternalServerError) 423 return nil 424 } 425 426 for i := range res.Infos { 427 childRef := &provider.Reference{ 428 ResourceId: cp.destination.ResourceId, 429 Path: utils.MakeRelativePath(path.Join(cp.destination.Path, res.Infos[i].Path)), 430 } 431 err := s.executeSpacesCopy(ctx, w, selector, ©{sourceInfo: res.Infos[i], destination: childRef, depth: cp.depth, successCode: cp.successCode}) 432 if err != nil { 433 return err 434 } 435 } 436 437 // we need to stat again to get the fileid 438 r, err := client.Stat(ctx, &provider.StatRequest{Ref: cp.destination}) 439 if err != nil { 440 return err 441 } 442 443 if r.GetStatus().GetCode() != rpc.Code_CODE_OK { 444 return fmt.Errorf("stat: status code %d", r.GetStatus().GetCode()) 445 } 446 447 fileid = storagespace.FormatResourceID(r.GetInfo().GetId()) 448 } else { 449 // copy file 450 // 1. get download url 451 dReq := &provider.InitiateFileDownloadRequest{Ref: &provider.Reference{ResourceId: cp.sourceInfo.Id, Path: "."}} 452 dRes, err := client.InitiateFileDownload(ctx, dReq) 453 if err != nil { 454 return err 455 } 456 457 if dRes.Status.Code != rpc.Code_CODE_OK { 458 return fmt.Errorf("status code %d", dRes.Status.Code) 459 } 460 461 var downloadEP, downloadToken string 462 for _, p := range dRes.Protocols { 463 if p.Protocol == "spaces" { 464 downloadEP, downloadToken = p.DownloadEndpoint, p.Token 465 } 466 } 467 // 2. get upload url 468 uReq := &provider.InitiateFileUploadRequest{ 469 Ref: cp.destination, 470 Opaque: &typespb.Opaque{ 471 Map: map[string]*typespb.OpaqueEntry{ 472 net.HeaderUploadLength: { 473 Decoder: "plain", 474 // TODO: handle case where size is not known in advance 475 Value: []byte(strconv.FormatUint(cp.sourceInfo.GetSize(), 10)), 476 }, 477 }, 478 }, 479 } 480 481 uRes, err := client.InitiateFileUpload(ctx, uReq) 482 if err != nil { 483 return err 484 } 485 486 if uRes.Status.Code != rpc.Code_CODE_OK { 487 if uRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { 488 w.WriteHeader(http.StatusForbidden) 489 // TODO path can be empty or relative 490 m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) 491 b, err := errors.Marshal(http.StatusForbidden, m, "", "") 492 errors.HandleWebdavError(log, w, b, err) 493 return nil 494 } 495 errors.HandleErrorStatus(log, w, uRes.Status) 496 return nil 497 } 498 499 var uploadEP, uploadToken string 500 for _, p := range uRes.Protocols { 501 if p.Protocol == "simple" { 502 uploadEP, uploadToken = p.UploadEndpoint, p.Token 503 } 504 } 505 506 // 3. do download 507 httpDownloadReq, err := rhttp.NewRequest(ctx, http.MethodGet, downloadEP, nil) 508 if err != nil { 509 return err 510 } 511 if downloadToken != "" { 512 httpDownloadReq.Header.Set(datagateway.TokenTransportHeader, downloadToken) 513 } 514 515 httpDownloadRes, err := s.client.Do(httpDownloadReq) 516 if err != nil { 517 return err 518 } 519 defer httpDownloadRes.Body.Close() 520 if httpDownloadRes.StatusCode == http.StatusForbidden { 521 w.WriteHeader(http.StatusForbidden) 522 b, err := errors.Marshal(http.StatusForbidden, http.StatusText(http.StatusForbidden), "", strconv.Itoa(http.StatusForbidden)) 523 errors.HandleWebdavError(log, w, b, err) 524 return nil 525 } 526 if httpDownloadRes.StatusCode != http.StatusOK { 527 return fmt.Errorf("status code %d", httpDownloadRes.StatusCode) 528 } 529 530 // 4. do upload 531 532 httpUploadReq, err := rhttp.NewRequest(ctx, http.MethodPut, uploadEP, httpDownloadRes.Body) 533 if err != nil { 534 return err 535 } 536 httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken) 537 httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize()) 538 539 httpUploadRes, err := s.client.Do(httpUploadReq) 540 if err != nil { 541 return err 542 } 543 defer httpUploadRes.Body.Close() 544 if httpUploadRes.StatusCode != http.StatusOK { 545 return err 546 } 547 548 fileid = httpUploadRes.Header.Get(net.HeaderOCFileID) 549 } 550 551 w.Header().Set(net.HeaderOCFileID, fileid) 552 return nil 553 } 554 555 func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, log *zerolog.Logger, destInShareJail bool) *copy { 556 isChild, err := s.referenceIsChildOf(ctx, s.gatewaySelector, dstRef, srcRef) 557 if err != nil { 558 switch err.(type) { 559 case errtypes.IsNotSupported: 560 log.Error().Err(err).Msg("can not detect recursive copy operation. missing machine auth configuration?") 561 w.WriteHeader(http.StatusForbidden) 562 default: 563 log.Error().Err(err).Msg("error while trying to detect recursive copy operation") 564 w.WriteHeader(http.StatusInternalServerError) 565 } 566 } 567 if isChild { 568 w.WriteHeader(http.StatusConflict) 569 b, err := errors.Marshal(http.StatusBadRequest, "can not copy a folder into one of its children", "", "") 570 errors.HandleWebdavError(log, w, b, err) 571 return nil 572 } 573 574 isParent, err := s.referenceIsChildOf(ctx, s.gatewaySelector, srcRef, dstRef) 575 if err != nil { 576 switch err.(type) { 577 case errtypes.IsNotFound: 578 isParent = false 579 case errtypes.IsNotSupported: 580 log.Error().Err(err).Msg("can not detect recursive copy operation. missing machine auth configuration?") 581 w.WriteHeader(http.StatusForbidden) 582 return nil 583 default: 584 log.Error().Err(err).Msg("error while trying to detect recursive copy operation") 585 w.WriteHeader(http.StatusInternalServerError) 586 return nil 587 } 588 } 589 590 if isParent { 591 w.WriteHeader(http.StatusConflict) 592 b, err := errors.Marshal(http.StatusBadRequest, "can not copy a folder into its parent", "", "") 593 errors.HandleWebdavError(log, w, b, err) 594 return nil 595 596 } 597 598 if srcRef.Path == dstRef.Path && srcRef.ResourceId == dstRef.ResourceId { 599 w.WriteHeader(http.StatusConflict) 600 b, err := errors.Marshal(http.StatusBadRequest, "source and destination are the same", "", "") 601 errors.HandleWebdavError(log, w, b, err) 602 return nil 603 } 604 605 oh := r.Header.Get(net.HeaderOverwrite) 606 overwrite, err := net.ParseOverwrite(oh) 607 if err != nil { 608 w.WriteHeader(http.StatusBadRequest) 609 m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) 610 b, err := errors.Marshal(http.StatusBadRequest, m, "", "") 611 errors.HandleWebdavError(log, w, b, err) 612 return nil 613 } 614 dh := r.Header.Get(net.HeaderDepth) 615 depth, err := net.ParseDepth(dh) 616 617 if err != nil { 618 w.WriteHeader(http.StatusBadRequest) 619 m := fmt.Sprintf("Depth header is set to incorrect value %v", dh) 620 b, err := errors.Marshal(http.StatusBadRequest, m, "", "") 621 errors.HandleWebdavError(log, w, b, err) 622 return nil 623 } 624 if dh == "" { 625 // net.ParseDepth returns "1" for an empty value but copy expects "infinity" 626 // so we overwrite it here 627 depth = net.DepthInfinity 628 } 629 630 log.Debug().Bool("overwrite", overwrite).Str("depth", depth.String()).Msg("copy") 631 632 client, err := s.gatewaySelector.Next() 633 if err != nil { 634 log.Error().Err(err).Msg("error selecting next client") 635 w.WriteHeader(http.StatusInternalServerError) 636 return nil 637 } 638 639 srcStatReq := &provider.StatRequest{Ref: srcRef} 640 srcStatRes, err := client.Stat(ctx, srcStatReq) 641 switch { 642 case err != nil: 643 log.Error().Err(err).Msg("error sending grpc stat request") 644 w.WriteHeader(http.StatusInternalServerError) 645 return nil 646 case srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND: 647 errors.HandleErrorStatus(log, w, srcStatRes.Status) 648 m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) 649 b, err := errors.Marshal(http.StatusNotFound, m, "", "") 650 errors.HandleWebdavError(log, w, b, err) 651 return nil 652 case srcStatRes.Status.Code != rpc.Code_CODE_OK: 653 errors.HandleErrorStatus(log, w, srcStatRes.Status) 654 return nil 655 } 656 if utils.IsSpaceRoot(srcStatRes.GetInfo()) { 657 log.Error().Msg("the source is disallowed") 658 w.WriteHeader(http.StatusBadRequest) 659 return nil 660 } 661 662 dstStatReq := &provider.StatRequest{Ref: dstRef} 663 dstStatRes, err := client.Stat(ctx, dstStatReq) 664 switch { 665 case err != nil: 666 log.Error().Err(err).Msg("error sending grpc stat request") 667 w.WriteHeader(http.StatusInternalServerError) 668 return nil 669 case dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND: 670 errors.HandleErrorStatus(log, w, dstStatRes.Status) 671 return nil 672 } 673 674 successCode := http.StatusCreated // 201 if new resource was created, see https://tools.ietf.org/html/rfc4918#section-9.8.5 675 if dstStatRes.Status.Code == rpc.Code_CODE_OK { 676 successCode = http.StatusNoContent // 204 if target already existed, see https://tools.ietf.org/html/rfc4918#section-9.8.5 677 678 if !overwrite { 679 log.Warn().Bool("overwrite", overwrite).Msg("dst already exists") 680 w.WriteHeader(http.StatusPreconditionFailed) 681 m := fmt.Sprintf("Could not overwrite Resource %v", dstRef.Path) 682 b, err := errors.Marshal(http.StatusPreconditionFailed, m, "", "") 683 errors.HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 684 return nil 685 } 686 687 if utils.IsSpaceRoot(dstStatRes.GetInfo()) { 688 log.Error().Msg("overwriting is not allowed") 689 w.WriteHeader(http.StatusBadRequest) 690 return nil 691 } 692 693 // delete existing tree when overwriting a directory or replacing a file with a directory 694 if dstStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER || 695 (dstStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_FILE && 696 srcStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER) { 697 698 // we must not allow to override mountpoints - so we check if we have access to the parent. If not this is a mountpoint 699 if destInShareJail { 700 res, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: dstStatRes.GetInfo().GetId()}) 701 if err != nil || res.GetStatus().GetCode() != rpc.Code_CODE_OK { 702 log.Error().Err(err).Msg("error sending grpc get path request") 703 w.WriteHeader(http.StatusInternalServerError) 704 return nil 705 } 706 707 dir, file := filepath.Split(filepath.Clean(res.GetPath())) 708 if dir == "/" || dir == "" || file == "" { 709 log.Error().Msg("must not overwrite mount points") 710 w.WriteHeader(http.StatusBadRequest) 711 _, _ = w.Write([]byte("must not overwrite mount points")) 712 return nil 713 } 714 } 715 716 delReq := &provider.DeleteRequest{Ref: dstRef} 717 delRes, err := client.Delete(ctx, delReq) 718 if err != nil { 719 log.Error().Err(err).Msg("error sending grpc delete request") 720 w.WriteHeader(http.StatusInternalServerError) 721 return nil 722 } 723 724 if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { 725 errors.HandleErrorStatus(log, w, delRes.Status) 726 return nil 727 } 728 } 729 } else if p := path.Dir(dstRef.Path); p != "" { 730 // check if an intermediate path / the parent exists 731 pRef := &provider.Reference{ 732 ResourceId: dstRef.ResourceId, 733 Path: utils.MakeRelativePath(p), 734 } 735 intStatReq := &provider.StatRequest{Ref: pRef} 736 intStatRes, err := client.Stat(ctx, intStatReq) 737 if err != nil { 738 log.Error().Err(err).Msg("error sending grpc stat request") 739 w.WriteHeader(http.StatusInternalServerError) 740 return nil 741 } 742 if intStatRes.Status.Code != rpc.Code_CODE_OK { 743 if intStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { 744 // 409 if intermediate dir is missing, see https://tools.ietf.org/html/rfc4918#section-9.8.5 745 log.Debug().Interface("parent", pRef).Interface("status", intStatRes.Status).Msg("conflict") 746 w.WriteHeader(http.StatusConflict) 747 } else { 748 errors.HandleErrorStatus(log, w, intStatRes.Status) 749 } 750 return nil 751 } 752 // TODO what if intermediate is a file? 753 } 754 755 return ©{source: srcRef, sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef} 756 }