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, &copy{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, &copy{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 &copy{source: srcRef, sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef}
   756  }