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