github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/mkcol.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  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"path"
    27  
    28  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    29  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    30  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
    31  	"github.com/cs3org/reva/v2/pkg/appctx"
    32  	"github.com/cs3org/reva/v2/pkg/errtypes"
    33  	rstatus "github.com/cs3org/reva/v2/pkg/rgrpc/status"
    34  	"github.com/cs3org/reva/v2/pkg/utils"
    35  	"github.com/rs/zerolog"
    36  )
    37  
    38  func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) (status int, err error) {
    39  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "mkcol")
    40  	defer span.End()
    41  
    42  	if err := ValidateName(filename(r.URL.Path), s.nameValidators); err != nil {
    43  		return http.StatusBadRequest, err
    44  	}
    45  	fn := path.Join(ns, r.URL.Path)
    46  	sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger()
    47  
    48  	client, err := s.gatewaySelector.Next()
    49  	if err != nil {
    50  		return http.StatusInternalServerError, errtypes.InternalError(err.Error())
    51  	}
    52  
    53  	// stat requested path to make sure it isn't existing yet
    54  	// NOTE: It could be on another storage provider than the 'parent' of it
    55  	sr, err := client.Stat(ctx, &provider.StatRequest{
    56  		Ref: &provider.Reference{
    57  			Path: fn,
    58  		},
    59  	})
    60  	switch {
    61  	case err != nil:
    62  		return http.StatusInternalServerError, err
    63  	case sr.Status.Code == rpc.Code_CODE_OK:
    64  		// https://www.rfc-editor.org/rfc/rfc4918#section-9.3.1:
    65  		// 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL.
    66  		return http.StatusMethodNotAllowed, fmt.Errorf("The resource you tried to create already exists")
    67  	case sr.Status.Code == rpc.Code_CODE_ABORTED:
    68  		return http.StatusPreconditionFailed, errtypes.NewErrtypeFromStatus(sr.Status)
    69  	case sr.Status.Code != rpc.Code_CODE_NOT_FOUND:
    70  		return rstatus.HTTPStatusFromCode(sr.Status.Code), errtypes.NewErrtypeFromStatus(sr.Status)
    71  	}
    72  
    73  	parentPath := path.Dir(fn)
    74  
    75  	space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, parentPath)
    76  	switch {
    77  	case err != nil:
    78  		return http.StatusInternalServerError, err
    79  	case rpcStatus.Code == rpc.Code_CODE_NOT_FOUND:
    80  		// https://www.rfc-editor.org/rfc/rfc4918#section-9.3.1:
    81  		// 409 (Conflict) - A collection cannot be made at the Request-URI until
    82  		// one or more intermediate collections have been created.  The server
    83  		// MUST NOT create those intermediate collections automatically.
    84  		return http.StatusConflict, fmt.Errorf("intermediate collection does not exist")
    85  	case rpcStatus.Code == rpc.Code_CODE_ABORTED:
    86  		return http.StatusPreconditionFailed, errtypes.NewErrtypeFromStatus(rpcStatus)
    87  	case rpcStatus.Code != rpc.Code_CODE_OK:
    88  		return rstatus.HTTPStatusFromCode(rpcStatus.Code), errtypes.NewErrtypeFromStatus(rpcStatus)
    89  	}
    90  
    91  	return s.handleMkcol(ctx, w, r, spacelookup.MakeRelativeReference(space, parentPath, false), spacelookup.MakeRelativeReference(space, fn, false), sublog)
    92  }
    93  
    94  func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID string) (status int, err error) {
    95  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "spaces_mkcol")
    96  	defer span.End()
    97  
    98  	sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "mkcol").Logger()
    99  
   100  	parentRef, err := spacelookup.MakeStorageSpaceReference(spaceID, path.Dir(r.URL.Path))
   101  	if err != nil {
   102  		return http.StatusBadRequest, fmt.Errorf("invalid space id")
   103  	}
   104  	childRef, _ := spacelookup.MakeStorageSpaceReference(spaceID, r.URL.Path)
   105  
   106  	return s.handleMkcol(ctx, w, r, &parentRef, &childRef, sublog)
   107  }
   108  
   109  func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Request, parentRef, childRef *provider.Reference, log zerolog.Logger) (status int, err error) {
   110  	if r.Body != http.NoBody {
   111  		// We currently do not support extended mkcol https://datatracker.ietf.org/doc/rfc5689/
   112  		// TODO let clients send a body with properties to set on the new resource
   113  		return http.StatusUnsupportedMediaType, fmt.Errorf("extended-mkcol not supported")
   114  	}
   115  
   116  	client, err := s.gatewaySelector.Next()
   117  	if err != nil {
   118  		return http.StatusInternalServerError, errtypes.InternalError(err.Error())
   119  	}
   120  	req := &provider.CreateContainerRequest{Ref: childRef}
   121  	res, err := client.CreateContainer(ctx, req)
   122  	switch {
   123  	case err != nil:
   124  		return http.StatusInternalServerError, err
   125  	case res.Status.Code == rpc.Code_CODE_OK:
   126  		w.WriteHeader(http.StatusCreated)
   127  		return 0, nil
   128  	case res.Status.Code == rpc.Code_CODE_NOT_FOUND:
   129  		// This should never happen because if the parent collection does not exist we should
   130  		// get a Code_CODE_FAILED_PRECONDITION. We play stupid and return what the response gave us
   131  		//lint:ignore ST1005 mimic the exact oc10 error message
   132  		return http.StatusNotFound, errors.New("Resource not found")
   133  	case res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED:
   134  		// check if user has access to parent
   135  		sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{
   136  			ResourceId: childRef.GetResourceId(),
   137  			Path:       utils.MakeRelativePath(path.Dir(childRef.Path)),
   138  		}})
   139  		if err != nil {
   140  			return http.StatusInternalServerError, err
   141  		}
   142  		if sRes.Status.Code != rpc.Code_CODE_OK {
   143  			// return not found error so we do not leak existence of a file
   144  			// TODO hide permission failed for users without access in every kind of request
   145  			// TODO should this be done in the driver?
   146  			//lint:ignore ST1005 mimic the exact oc10 error message
   147  			return http.StatusNotFound, errors.New("Resource not found")
   148  		}
   149  		return http.StatusForbidden, errors.New(sRes.Status.Message)
   150  	case res.Status.Code == rpc.Code_CODE_ABORTED:
   151  		return http.StatusPreconditionFailed, errors.New(res.Status.Message)
   152  	case res.Status.Code == rpc.Code_CODE_FAILED_PRECONDITION:
   153  		// https://www.rfc-editor.org/rfc/rfc4918#section-9.3.1:
   154  		// 409 (Conflict) - A collection cannot be made at the Request-URI until
   155  		// one or more intermediate collections have been created. The server
   156  		// MUST NOT create those intermediate collections automatically.
   157  		return http.StatusConflict, errors.New(res.Status.Message)
   158  	case res.Status.Code == rpc.Code_CODE_ALREADY_EXISTS:
   159  		// https://www.rfc-editor.org/rfc/rfc4918#section-9.3.1:
   160  		// 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL.
   161  		//lint:ignore ST1005 mimic the exact oc10 error message
   162  		return http.StatusMethodNotAllowed, errors.New("The resource you tried to create already exists")
   163  	}
   164  	return rstatus.HTTPStatusFromCode(res.Status.Code), errtypes.NewErrtypeFromStatus(res.Status)
   165  }