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 }