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  }