From 6b8e690d4119546e80d9bc851dccf900ad9a8fdd Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Wed, 12 Jan 2022 09:50:26 +0100 Subject: [PATCH] Start splitting up ocdav (#2434) * Start splitting up ocdav into smaller chunks That increases clarity and allows for making things testable. * Add a basic propfind unit test * Fix linter and hound issues * Add changelog --- changelog/unreleased/refactor-ocdav.md | 5 + go.mod | 2 +- go.sum | 2 + .../http/services/owncloud/ocdav/avatars.go | 3 +- internal/http/services/owncloud/ocdav/copy.go | 122 +- internal/http/services/owncloud/ocdav/dav.go | 52 +- .../http/services/owncloud/ocdav/delete.go | 46 +- .../owncloud/ocdav/{ => errors}/error.go | 30 +- internal/http/services/owncloud/ocdav/get.go | 59 +- internal/http/services/owncloud/ocdav/head.go | 44 +- .../http/services/owncloud/ocdav/mkcol.go | 55 +- internal/http/services/owncloud/ocdav/move.go | 70 +- .../services/owncloud/ocdav/net/context.go | 37 + .../services/owncloud/ocdav/net/headers.go | 58 + .../http/services/owncloud/ocdav/net/net.go | 80 + .../http/services/owncloud/ocdav/ocdav.go | 54 +- .../owncloud/ocdav/ocdav_suite_test.go | 31 + .../services/owncloud/ocdav/ocdav_test.go | 13 +- .../http/services/owncloud/ocdav/options.go | 18 +- .../ocdav/propfind/mocks/GatewayClient.go | 2735 +++++++++++++++++ .../owncloud/ocdav/{ => propfind}/propfind.go | 469 ++- .../ocdav/propfind/propfind_suite_test.go | 31 + .../owncloud/ocdav/propfind/propfind_test.go | 73 + .../http/services/owncloud/ocdav/proppatch.go | 135 +- .../services/owncloud/ocdav/props/props.go | 99 + .../services/owncloud/ocdav/publicfile.go | 12 +- internal/http/services/owncloud/ocdav/put.go | 83 +- .../http/services/owncloud/ocdav/report.go | 12 +- .../owncloud/ocdav/spacelookup/spacelookup.go | 153 + .../http/services/owncloud/ocdav/spaces.go | 150 +- .../http/services/owncloud/ocdav/trashbin.go | 212 +- internal/http/services/owncloud/ocdav/tus.go | 87 +- .../http/services/owncloud/ocdav/versions.go | 28 +- .../http/services/owncloud/ocdav/webdav.go | 55 +- .../ocs/handlers/cloud/users/users.go | 6 +- 35 files changed, 4102 insertions(+), 1019 deletions(-) create mode 100644 changelog/unreleased/refactor-ocdav.md rename internal/http/services/owncloud/ocdav/{ => errors}/error.go (87%) create mode 100644 internal/http/services/owncloud/ocdav/net/context.go create mode 100644 internal/http/services/owncloud/ocdav/net/headers.go create mode 100644 internal/http/services/owncloud/ocdav/net/net.go create mode 100644 internal/http/services/owncloud/ocdav/ocdav_suite_test.go create mode 100644 internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go rename internal/http/services/owncloud/ocdav/{ => propfind}/propfind.go (68%) create mode 100644 internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go create mode 100644 internal/http/services/owncloud/ocdav/propfind/propfind_test.go create mode 100644 internal/http/services/owncloud/ocdav/props/props.go create mode 100644 internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go diff --git a/changelog/unreleased/refactor-ocdav.md b/changelog/unreleased/refactor-ocdav.md new file mode 100644 index 00000000000..89fb956f468 --- /dev/null +++ b/changelog/unreleased/refactor-ocdav.md @@ -0,0 +1,5 @@ +Enhancement: Refactor ocdav into smaller chunks + +That increases code clarity and enables testing. + +https://github.com/cs3org/reva/pull/2434 diff --git a/go.mod b/go.mod index b4ad8f6ecdb..38b1fae6b9e 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/sethvargo/go-password v0.2.0 github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.7.0 - github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df + github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f github.com/thanhpk/randstr v1.0.4 github.com/tidwall/pretty v1.2.0 // indirect github.com/tus/tusd v1.8.0 diff --git a/go.sum b/go.sum index fc9a5475843..4307249b7a3 100644 --- a/go.sum +++ b/go.sum @@ -666,6 +666,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8gRPt1MdSzBNZP0OYuDm5wsmDKgwpLjYzo= github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= diff --git a/internal/http/services/owncloud/ocdav/avatars.go b/internal/http/services/owncloud/ocdav/avatars.go index 2271d2901bd..6815d36ad39 100644 --- a/internal/http/services/owncloud/ocdav/avatars.go +++ b/internal/http/services/owncloud/ocdav/avatars.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "net/http" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -57,7 +58,7 @@ func (h *AvatarsHandler) Handler(s *svc) http.Handler { log.Error().Err(err).Msg("error decoding string") w.WriteHeader(http.StatusInternalServerError) } - w.Header().Set(HeaderContentType, "image/png") + w.Header().Set(net.HeaderContentType, "image/png") if _, err := w.Write(decoded); err != nil { log.Error().Err(err).Msg("error writing data response") } diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index 350fb50a2c6..f3c94eb355a 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -32,6 +32,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" "github.com/cs3org/reva/pkg/rhttp/router" @@ -68,39 +71,39 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() - srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, src) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, src) if err != nil { sublog.Error().Err(err).Str("path", src).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dst) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dst) if err != nil { sublog.Error().Err(err).Str("path", dst).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - cp := s.prepareCopy(ctx, w, r, makeRelativeReference(srcSpace, src, false), makeRelativeReference(dstSpace, dst, false), &sublog) + cp := s.prepareCopy(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, src, false), spacelookup.MakeRelativeReference(dstSpace, dst, false), &sublog) if cp == nil { return } - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } - if err := s.executePathCopy(ctx, client, w, r, cp); err != nil { sublog.Error().Err(err).Str("depth", cp.depth).Msg("error executing path copy") w.WriteHeader(http.StatusInternalServerError) @@ -125,11 +128,8 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if createRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) } return nil } @@ -218,14 +218,11 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if uRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } - HandleErrorStatus(log, w, uRes.Status) + errors.HandleErrorStatus(log, w, uRes.Status) return nil } @@ -286,8 +283,15 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Str("destination", dst).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + srcRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -295,14 +299,14 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) + dstRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -310,7 +314,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -318,12 +322,6 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s if cp == nil { return } - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } err = s.executeSpacesCopy(ctx, w, client, cp) if err != nil { @@ -352,11 +350,8 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) } return nil } @@ -412,7 +407,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie Ref: cp.destination, Opaque: &typespb.Opaque{ Map: map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", // TODO: handle case where size is not known in advance Value: []byte(strconv.FormatUint(cp.sourceInfo.GetSize(), 10)), @@ -431,14 +426,11 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path can be empty or relative m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } - HandleErrorStatus(log, w, uRes.Status) + errors.HandleErrorStatus(log, w, uRes.Status) return nil } @@ -492,22 +484,16 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } depth, err := extractDepth(w, r) if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Depth header is set to incorrect value %v", depth) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } @@ -532,13 +518,10 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(log, w, b, err) } - HandleErrorStatus(log, w, srcStatRes.Status) + errors.HandleErrorStatus(log, w, srcStatRes.Status) return nil } @@ -550,7 +533,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re return nil } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(log, w, srcStatRes.Status) + errors.HandleErrorStatus(log, w, srcStatRes.Status) return nil } @@ -562,11 +545,8 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) m := fmt.Sprintf("Could not overwrite Resource %v", dstRef.Path) - b, err := Marshal(exception{ - code: SabredavPreconditionFailed, - message: m, - }) - HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 + b, err := errors.Marshal(errors.SabredavPreconditionFailed, m, "") + errors.HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 return nil } @@ -580,7 +560,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(log, w, delRes.Status) + errors.HandleErrorStatus(log, w, delRes.Status) return nil } } else if p := path.Dir(dstRef.Path); p != "" { @@ -602,7 +582,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Debug().Interface("parent", pRef).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - HandleErrorStatus(log, w, intStatRes.Status) + errors.HandleErrorStatus(log, w, intStatRes.Status) } return nil } @@ -613,7 +593,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re } func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) overwrite = strings.ToUpper(overwrite) if overwrite == "" { overwrite = "T" @@ -627,7 +607,7 @@ func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { } func extractDepth(w http.ResponseWriter, r *http.Request) (string, error) { - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "infinity" } diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index a830c399b87..def7ab68c5f 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -29,6 +29,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -110,10 +112,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { if r.Header.Get("Depth") == "" { w.WriteHeader(http.StatusMethodNotAllowed) - b, err := Marshal(exception{ - code: SabredavMethodNotAllowed, - message: "Listing members of this collection is disabled", - }) + b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "Listing members of this collection is disabled", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) @@ -146,37 +145,37 @@ func (h *DavHandler) Handler(s *svc) http.Handler { contextUser, ok := ctxpkg.ContextGetUser(ctx) if ok && isOwner(requestUserID, contextUser) { // use home storage handler when user was detected - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "files", requestUserID) - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "files", requestUserID) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.FilesHomeHandler.Handler(s).ServeHTTP(w, r) } else { r.URL.Path = oldPath - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "files") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "files") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.FilesHandler.Handler(s).ServeHTTP(w, r) } case "meta": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "meta") - ctx = context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "meta") + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.MetaHandler.Handler(s).ServeHTTP(w, r) case "trash-bin": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "trash-bin") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "trash-bin") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.TrashbinHandler.Handler(s).ServeHTTP(w, r) case "spaces": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "spaces") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "spaces") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.SpacesHandler.Handler(s).ServeHTTP(w, r) case "public-files": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "public-files") - ctx = context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "public-files") + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, base) c, err := pool.GetGatewayServiceClient(s.c.GatewaySvc) if err != nil { w.WriteHeader(http.StatusNotFound) @@ -209,18 +208,12 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) if hasValidBasicAuthHeader { - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - message: "Username or password was incorrect", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "Username or password was incorrect", "") + errors.HandleWebdavError(log, w, b, err) return } - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - message: "No 'Authorization: Basic' header found", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "No 'Authorization: Basic' header found", "") + errors.HandleWebdavError(log, w, b, err) return case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusNotFound) @@ -270,11 +263,8 @@ func (h *DavHandler) Handler(s *svc) http.Handler { default: w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "File not found in root", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "File not found in root", "") + errors.HandleWebdavError(log, w, b, err) } }) } diff --git a/internal/http/services/owncloud/ocdav/delete.go b/internal/http/services/owncloud/ocdav/delete.go index 7e2d954f820..c8d74248271 100644 --- a/internal/http/services/owncloud/ocdav/delete.go +++ b/internal/http/services/owncloud/ocdav/delete.go @@ -26,6 +26,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/rs/zerolog" @@ -35,18 +37,24 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(r.Context()).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(r.Context(), fn) + client, err := s.getClient() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + space, status, err := spacelookup.LookUpStorageSpaceForPath(r.Context(), client, fn) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleDelete(r.Context(), w, r, makeRelativeReference(space, fn, false), sublog) + s.handleDelete(r.Context(), w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -72,32 +80,23 @@ func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.R w.WriteHeader(http.StatusNotFound) // TODO path might be empty or relative... m := fmt.Sprintf("Resource %v not found", ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) } if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) // TODO path might be empty or relative... m := fmt.Sprintf("Permission denied to delete %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) } if res.Status.Code == rpc.Code_CODE_INTERNAL && res.Status.Message == "can't delete mount path" { w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: res.Status.Message, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, res.Status.Message, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return } @@ -110,9 +109,14 @@ func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID defer span.End() sublog := appctx.GetLogger(ctx).With().Logger() + client, err := s.getClient() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -120,7 +124,7 @@ func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } diff --git a/internal/http/services/owncloud/ocdav/error.go b/internal/http/services/owncloud/ocdav/errors/error.go similarity index 87% rename from internal/http/services/owncloud/ocdav/error.go rename to internal/http/services/owncloud/ocdav/errors/error.go index fec06430c50..bb395d21975 100644 --- a/internal/http/services/owncloud/ocdav/error.go +++ b/internal/http/services/owncloud/ocdav/errors/error.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocdav +package errors import ( "encoding/xml" @@ -59,20 +59,21 @@ var ( } ) -type exception struct { - code code - message string - header string +// Exception represents a ocdav exception +type Exception struct { + Code code + Message string + Header string } // Marshal just calls the xml marshaller for a given exception. -func Marshal(e exception) ([]byte, error) { - xmlstring, err := xml.Marshal(&errorXML{ +func Marshal(code code, message string, header string) ([]byte, error) { + xmlstring, err := xml.Marshal(&ErrorXML{ Xmlnsd: "DAV", Xmlnss: "http://sabredav.org/ns", - Exception: codesEnum[e.code], - Message: e.message, - Header: e.header, + Exception: codesEnum[code], + Message: message, + Header: header, }) if err != nil { return []byte(""), err @@ -80,8 +81,9 @@ func Marshal(e exception) ([]byte, error) { return []byte(xml.Header + string(xmlstring)), err } +// ErrorXML holds the xml representation of an error // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error -type errorXML struct { +type ErrorXML struct { XMLName xml.Name `xml:"d:error"` Xmlnsd string `xml:"xmlns:d,attr"` Xmlnss string `xml:"xmlns:s,attr"` @@ -92,7 +94,11 @@ type errorXML struct { Header string `xml:"s:header,omitempty"` } -var errInvalidPropfind = errors.New("webdav: invalid propfind") +// ErrorInvalidPropfind is an invalid propfind error +var ErrorInvalidPropfind = errors.New("webdav: invalid propfind") + +// ErrInvalidProppatch is an invalid proppatch error +var ErrInvalidProppatch = errors.New("webdav: invalid proppatch") // HandleErrorStatus checks the status code, logs a Debug or Error level message // and writes an appropriate http status diff --git a/internal/http/services/owncloud/ocdav/get.go b/internal/http/services/owncloud/ocdav/get.go index eea0d196738..80443edc79a 100644 --- a/internal/http/services/owncloud/ocdav/get.go +++ b/internal/http/services/owncloud/ocdav/get.go @@ -32,6 +32,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/grpc/services/storageprovider" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" rtrace "github.com/cs3org/reva/pkg/trace" @@ -48,18 +51,24 @@ func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) { sublog := appctx.GetLogger(ctx).With().Str("path", fn).Str("svc", "ocdav").Str("handler", "get").Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleGet(ctx, w, r, makeRelativeReference(space, fn, false), "spaces", sublog) + s.handleGet(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), "spaces", sublog) } func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, dlProtocol string, log zerolog.Logger) { @@ -78,7 +87,7 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return case sRes.Status.Code != rpc.Code_CODE_OK: - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return case sRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER: log.Warn().Msg("resource is a folder and cannot be downloaded") @@ -93,7 +102,7 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return } else if dRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, dRes.Status) + errors.HandleErrorStatus(&log, w, dRes.Status) return } @@ -112,8 +121,8 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ } httpReq.Header.Set(datagateway.TokenTransportHeader, token) - if r.Header.Get(HeaderRange) != "" { - httpReq.Header.Set(HeaderRange, r.Header.Get(HeaderRange)) + if r.Header.Get(net.HeaderRange) != "" { + httpReq.Header.Set(net.HeaderRange, r.Header.Get(net.HeaderRange)) } httpClient := s.client @@ -133,34 +142,34 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ info := sRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ path.Base(r.URL.Path)+"; filename=\""+path.Base(r.URL.Path)+"\"") - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) if httpRes.StatusCode == http.StatusPartialContent { - w.Header().Set(HeaderContentRange, httpRes.Header.Get(HeaderContentRange)) - w.Header().Set(HeaderContentLength, httpRes.Header.Get(HeaderContentLength)) + w.Header().Set(net.HeaderContentRange, httpRes.Header.Get(net.HeaderContentRange)) + w.Header().Set(net.HeaderContentLength, httpRes.Header.Get(net.HeaderContentLength)) w.WriteHeader(http.StatusPartialContent) } else { - w.Header().Set(HeaderContentLength, strconv.FormatUint(info.Size, 10)) + w.Header().Set(net.HeaderContentLength, strconv.FormatUint(info.Size, 10)) } if info.Checksum != nil { - w.Header().Set(HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) + w.Header().Set(net.HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) } var c int64 if c, err = io.Copy(w, httpRes.Body); err != nil { log.Error().Err(err).Msg("error finishing copying data to response") } - if httpRes.Header.Get(HeaderContentLength) != "" { - i, err := strconv.ParseInt(httpRes.Header.Get(HeaderContentLength), 10, 64) + if httpRes.Header.Get(net.HeaderContentLength) != "" { + i, err := strconv.ParseInt(httpRes.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { - log.Error().Err(err).Str("content-length", httpRes.Header.Get(HeaderContentLength)).Msg("invalid content length in datagateway response") + log.Error().Err(err).Str("content-length", httpRes.Header.Get(net.HeaderContentLength)).Msg("invalid content length in datagateway response") } if i != c { log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch") @@ -174,9 +183,15 @@ func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID st defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "get").Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -184,7 +199,7 @@ func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID st } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } s.handleGet(ctx, w, r, ref, "spaces", sublog) diff --git a/internal/http/services/owncloud/ocdav/head.go b/internal/http/services/owncloud/ocdav/head.go index 3b112c8a37a..3836ea17a56 100644 --- a/internal/http/services/owncloud/ocdav/head.go +++ b/internal/http/services/owncloud/ocdav/head.go @@ -33,6 +33,9 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/utils" "github.com/rs/zerolog" @@ -45,18 +48,25 @@ func (s *svc) handlePathHead(w http.ResponseWriter, r *http.Request, ns string) fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleHead(ctx, w, r, makeRelativeReference(space, fn, false), sublog) + s.handleHead(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -76,24 +86,24 @@ func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Req } if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return } info := res.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) if info.Checksum != nil { - w.Header().Set(HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) + w.Header().Set(net.HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) } t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) - w.Header().Set(HeaderContentLength, strconv.FormatUint(info.Size, 10)) + w.Header().Set(net.HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderContentLength, strconv.FormatUint(info.Size, 10)) if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { - w.Header().Set(HeaderAcceptRanges, "bytes") + w.Header().Set(net.HeaderAcceptRanges, "bytes") } w.WriteHeader(http.StatusOK) } @@ -103,8 +113,14 @@ func (s *svc) handleSpacesHead(w http.ResponseWriter, r *http.Request, spaceID s defer span.End() sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -112,7 +128,7 @@ func (s *svc) handleSpacesHead(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index 1df68122762..72d57acb5f0 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -26,6 +26,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/rs/zerolog" @@ -43,21 +45,27 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) } } sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } parentPath := path.Dir(fn) - space, status, err := s.lookUpStorageSpaceForPath(ctx, parentPath) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, parentPath) if err != nil { sublog.Error().Err(err).Str("path", parentPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleMkcol(ctx, w, r, makeRelativeReference(space, parentPath, false), makeRelativeReference(space, fn, false), sublog) + 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) { @@ -65,8 +73,14 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "mkcol").Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - parentRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Dir(r.URL.Path), true) + parentRef, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, path.Dir(r.URL.Path), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -74,11 +88,11 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - childRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + childRef, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -86,7 +100,7 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } @@ -123,13 +137,10 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re // all ancestors must already exist, or the method must fail // with a 409 (Conflict) status code. w.WriteHeader(http.StatusConflict) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "Parent node does not exist", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "Parent node does not exist", "") + errors.HandleWebdavError(&log, w, b, err) } else { - HandleErrorStatus(&log, w, parentStatRes.Status) + errors.HandleErrorStatus(&log, w, parentStatRes.Status) } return } @@ -146,13 +157,10 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re if statRes.Status.Code != rpc.Code_CODE_NOT_FOUND { if statRes.Status.Code == rpc.Code_CODE_OK { w.WriteHeader(http.StatusMethodNotAllowed) // 405 if it already exists - b, err := Marshal(exception{ - code: SabredavMethodNotAllowed, - message: "The resource you tried to create already exists", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "The resource you tried to create already exists", "") + errors.HandleWebdavError(&log, w, b, err) } else { - HandleErrorStatus(&log, w, statRes.Status) + errors.HandleErrorStatus(&log, w, statRes.Status) } return } @@ -174,12 +182,9 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", childRef.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) default: - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index cf7f382531c..d68ffff23e8 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -27,6 +27,9 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" @@ -56,25 +59,31 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) dstPath = path.Join(ns, dstPath) sublog := appctx.GetLogger(ctx).With().Str("src", srcPath).Str("dst", dstPath).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, srcPath) + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, srcPath) if err != nil { sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dstPath) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dstPath) if err != nil { sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -83,7 +92,7 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) dstSpace.Root = srcSpace.Root } - s.handleMove(ctx, w, r, makeRelativeReference(srcSpace, srcPath, false), makeRelativeReference(dstSpace, dstPath, false), sublog) + s.handleMove(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, srcPath, false), spacelookup.MakeRelativeReference(dstSpace, dstPath, false), sublog) } func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceID string) { @@ -97,8 +106,15 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } sublog := appctx.GetLogger(ctx).With().Str("spaceid", srcSpaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, srcSpaceID, r.URL.Path, true) + srcRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, srcSpaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -106,14 +122,14 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) + dstRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -121,7 +137,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -129,7 +145,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, log zerolog.Logger) { - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) log.Debug().Str("overwrite", overwrite).Msg("move") overwrite = strings.ToUpper(overwrite) @@ -161,13 +177,10 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, srcStatRes.Status) + errors.HandleErrorStatus(&log, w, srcStatRes.Status) return } @@ -180,7 +193,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req return } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, srcStatRes.Status) + errors.HandleErrorStatus(&log, w, srcStatRes.Status) return } @@ -204,7 +217,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, delRes.Status) + errors.HandleErrorStatus(&log, w, delRes.Status) return } } else { @@ -225,7 +238,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req log.Debug().Interface("parent", dst).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - HandleErrorStatus(&log, w, intStatRes.Status) + errors.HandleErrorStatus(&log, w, intStatRes.Status) } return } @@ -244,13 +257,10 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if mRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to move %v", src.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, mRes.Status) + errors.HandleErrorStatus(&log, w, mRes.Status) return } @@ -262,14 +272,14 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } if dstStatRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, dstStatRes.Status) + errors.HandleErrorStatus(&log, w, dstStatRes.Status) return } info := dstStatRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) w.WriteHeader(successCode) } diff --git a/internal/http/services/owncloud/ocdav/net/context.go b/internal/http/services/owncloud/ocdav/net/context.go new file mode 100644 index 00000000000..d3069e89724 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/context.go @@ -0,0 +1,37 @@ +// Copyright 2018-2022 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 net + +import ( + "context" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +// IsCurrentUserOwner returns whether the context user is the given owner or not +func IsCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { + contextUser, ok := ctxpkg.ContextGetUser(ctx) + if ok && contextUser.Id != nil && owner != nil && + contextUser.Id.Idp == owner.Idp && + contextUser.Id.OpaqueId == owner.OpaqueId { + return true + } + return false +} diff --git a/internal/http/services/owncloud/ocdav/net/headers.go b/internal/http/services/owncloud/ocdav/net/headers.go new file mode 100644 index 00000000000..657ad770202 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/headers.go @@ -0,0 +1,58 @@ +// Copyright 2018-2022 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 net + +// Common HTTP headers. +const ( + HeaderAcceptRanges = "Accept-Ranges" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderContentDisposistion = "Content-Disposition" + HeaderContentLength = "Content-Length" + HeaderContentRange = "Content-Range" + HeaderContentType = "Content-Type" + HeaderETag = "ETag" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderRange = "Range" + HeaderIfMatch = "If-Match" +) + +// Non standard HTTP headers. +const ( + HeaderOCFileID = "OC-FileId" + HeaderOCETag = "OC-ETag" + HeaderOCChecksum = "OC-Checksum" + HeaderOCPermissions = "OC-Perm" + HeaderDepth = "Depth" + HeaderDav = "DAV" + HeaderTusResumable = "Tus-Resumable" + HeaderTusVersion = "Tus-Version" + HeaderTusExtension = "Tus-Extension" + HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" + HeaderTusUploadExpires = "Upload-Expires" + HeaderDestination = "Destination" + HeaderOverwrite = "Overwrite" + HeaderUploadChecksum = "Upload-Checksum" + HeaderUploadLength = "Upload-Length" + HeaderUploadMetadata = "Upload-Metadata" + HeaderUploadOffset = "Upload-Offset" + HeaderOCMtime = "X-OC-Mtime" + HeaderExpectedEntityLength = "X-Expected-Entity-Length" +) diff --git a/internal/http/services/owncloud/ocdav/net/net.go b/internal/http/services/owncloud/ocdav/net/net.go new file mode 100644 index 00000000000..e5f3ded8dc5 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/net.go @@ -0,0 +1,80 @@ +// Copyright 2018-2022 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 net + +import ( + "fmt" + "regexp" + "strings" +) + +type ctxKey int + +const ( + // CtxKeyBaseURI is the key of the base URI context field + CtxKeyBaseURI ctxKey = iota + + // NsDav is the Dav ns + NsDav = "DAV:" + // NsOwncloud is the owncloud ns + NsOwncloud = "http://owncloud.org/ns" + // NsOCS is the OCS ns + NsOCS = "http://open-collaboration-services.org/ns" + + // RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781 + RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT" + + // PropQuotaUnknown is the quota unknown property + PropQuotaUnknown = "-2" + // PropOcFavorite is the favorite ns property + PropOcFavorite = "http://owncloud.org/ns/favorite" +) + +// replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' +// see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb +func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { + result := "" + lastIndex := 0 + for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { + groups := []string{} + for i := 0; i < len(v); i += 2 { + groups = append(groups, str[v[i]:v[i+1]]) + } + result += str[lastIndex:v[0]] + repl(groups) + lastIndex = v[1] + } + return result + str[lastIndex:] +} + +var hrefre = regexp.MustCompile(`([^A-Za-z0-9_\-.~()/:@!$])`) + +// EncodePath encodes the path of a url. +// +// slashes (/) are treated as path-separators. +// ported from https://github.com/sabre-io/http/blob/bb27d1a8c92217b34e778ee09dcf79d9a2936e84/lib/functions.php#L369-L379 +func EncodePath(path string) string { + return replaceAllStringSubmatchFunc(hrefre, path, func(groups []string) string { + b := groups[1] + var sb strings.Builder + for i := 0; i < len(b); i++ { + sb.WriteString(fmt.Sprintf("%%%x", b[i])) + } + return sb.String() + }) +} diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 185ad25c943..ae2e5ac570c 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -20,17 +20,16 @@ package ocdav import ( "context" - "fmt" "net/http" "net/url" "path" - "regexp" "strings" "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" @@ -47,12 +46,6 @@ import ( "github.com/rs/zerolog" ) -type ctxKey int - -const ( - ctxKeyBaseURI ctxKey = iota -) - var ( errInvalidValue = errors.New("invalid value") @@ -123,6 +116,10 @@ type svc struct { client *http.Client } +func (s *svc) Config() *Config { + return s.c +} + func getFavoritesManager(c *Config) (favorite.Manager, error) { if f, ok := registry.NewFuncs[c.FavoriteStorageDriver]; ok { return f(c.FavoriteStorageDrivers[c.FavoriteStorageDriver]) @@ -228,7 +225,7 @@ func (s *svc) Handler() http.Handler { // for oc we need to prepend /home as the path that will be passed to the home storage provider // will not contain the username base = path.Join(base, "webdav") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) s.webDavHandler.Handler(s).ServeHTTP(w, r) return @@ -238,7 +235,7 @@ func (s *svc) Handler() http.Handler { // for oc we need to prepend the path to user homes // or we take the path starting at /dav and allow rewriting it? base = path.Join(base, "dav") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) s.davHandler.Handler(s).ServeHTTP(w, r) return @@ -328,7 +325,7 @@ func addAccessHeaders(w http.ResponseWriter, r *http.Request) { } func extractDestination(r *http.Request) (string, error) { - dstHeader := r.Header.Get(HeaderDestination) + dstHeader := r.Header.Get(net.HeaderDestination) if dstHeader == "" { return "", errors.Wrap(errInvalidValue, "destination header is empty") } @@ -337,7 +334,7 @@ func extractDestination(r *http.Request) (string, error) { return "", errors.Wrap(errInvalidValue, err.Error()) } - baseURI := r.Context().Value(ctxKeyBaseURI).(string) + baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) // TODO check if path is on same storage, return 502 on problems, see https://tools.ietf.org/html/rfc4918#section-9.9.4 // Strip the base URI from the destination. The destination might contain redirection prefixes which need to be handled urlSplit := strings.Split(dstURL.Path, baseURI) @@ -347,36 +344,3 @@ func extractDestination(r *http.Request) (string, error) { return urlSplit[1], nil } - -// replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' -// see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb -func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { - result := "" - lastIndex := 0 - for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { - groups := []string{} - for i := 0; i < len(v); i += 2 { - groups = append(groups, str[v[i]:v[i+1]]) - } - result += str[lastIndex:v[0]] + repl(groups) - lastIndex = v[1] - } - return result + str[lastIndex:] -} - -var hrefre = regexp.MustCompile(`([^A-Za-z0-9_\-.~()/:@!$])`) - -// encodePath encodes the path of a url. -// -// slashes (/) are treated as path-separators. -// ported from https://github.com/sabre-io/http/blob/bb27d1a8c92217b34e778ee09dcf79d9a2936e84/lib/functions.php#L369-L379 -func encodePath(path string) string { - return replaceAllStringSubmatchFunc(hrefre, path, func(groups []string) string { - b := groups[1] - var sb strings.Builder - for i := 0; i < len(b); i++ { - sb.WriteString(fmt.Sprintf("%%%x", b[i])) - } - return sb.String() - }) -} diff --git a/internal/http/services/owncloud/ocdav/ocdav_suite_test.go b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go new file mode 100644 index 00000000000..c31853b080f --- /dev/null +++ b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go @@ -0,0 +1,31 @@ +// 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_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestOcdav(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Ocdav Suite") +} diff --git a/internal/http/services/owncloud/ocdav/ocdav_test.go b/internal/http/services/owncloud/ocdav/ocdav_test.go index 7d0de23167d..98c60afa33c 100644 --- a/internal/http/services/owncloud/ocdav/ocdav_test.go +++ b/internal/http/services/owncloud/ocdav/ocdav_test.go @@ -25,6 +25,7 @@ import ( "testing" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/utils/resourceid" ) @@ -37,7 +38,7 @@ then this method alone will cost a huge amount of time. */ func BenchmarkEncodePath(b *testing.B) { for i := 0; i < b.N; i++ { - _ = encodePath("/some/path/Folder %^*(#1)") + _ = net.EncodePath("/some/path/Folder %^*(#1)") } } @@ -53,9 +54,9 @@ func TestWrapResourceID(t *testing.T) { func TestExtractDestination(t *testing.T) { expected := "/dst" request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "https://example.org/remote.php/dav/dst") - ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/dav") + ctx := context.WithValue(context.Background(), net.CtxKeyBaseURI, "remote.php/dav") destination, err := extractDestination(request.WithContext(ctx)) if err != nil { t.Errorf("Expected err to be nil got %s", err) @@ -81,7 +82,7 @@ func TestExtractDestinationWithoutHeader(t *testing.T) { func TestExtractDestinationWithInvalidDestination(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "://example.org/remote.php/dav/dst") _, err := extractDestination(request) if err == nil { t.Errorf("Expected err to be nil got %s", err) @@ -94,9 +95,9 @@ func TestExtractDestinationWithInvalidDestination(t *testing.T) { func TestExtractDestinationWithDestinationWrongBaseURI(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "https://example.org/remote.php/dav/dst") - ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/webdav") + ctx := context.WithValue(context.Background(), net.CtxKeyBaseURI, "remote.php/webdav") _, err := extractDestination(request.WithContext(ctx)) if err == nil { t.Errorf("Expected err to be nil got %s", err) diff --git a/internal/http/services/owncloud/ocdav/options.go b/internal/http/services/owncloud/ocdav/options.go index e2db6fe9ae2..abe4917cf75 100644 --- a/internal/http/services/owncloud/ocdav/options.go +++ b/internal/http/services/owncloud/ocdav/options.go @@ -21,6 +21,8 @@ package ocdav import ( "net/http" "strings" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" ) func (s *svc) handleOptions(w http.ResponseWriter, r *http.Request) { @@ -28,19 +30,19 @@ func (s *svc) handleOptions(w http.ResponseWriter, r *http.Request) { allow += " MOVE, UNLOCK, PROPFIND, MKCOL, REPORT, SEARCH," allow += " PUT" // TODO(jfd): only for files ... but we cannot create the full path without a user ... which we only have when credentials are sent - isPublic := strings.Contains(r.Context().Value(ctxKeyBaseURI).(string), "public-files") + isPublic := strings.Contains(r.Context().Value(net.CtxKeyBaseURI).(string), "public-files") - w.Header().Set(HeaderContentType, "application/xml") + w.Header().Set(net.HeaderContentType, "application/xml") w.Header().Set("Allow", allow) w.Header().Set("DAV", "1, 2") w.Header().Set("MS-Author-Via", "DAV") if !isPublic { - w.Header().Add(HeaderAccessControlAllowHeaders, HeaderTusResumable) - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ",")) - w.Header().Set(HeaderTusResumable, "1.0.0") // TODO(jfd): only for dirs? - w.Header().Set(HeaderTusVersion, "1.0.0") - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") - w.Header().Set(HeaderTusChecksumAlgorithm, "md5,sha1,crc32") + w.Header().Add(net.HeaderAccessControlAllowHeaders, net.HeaderTusResumable) + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderTusVersion, net.HeaderTusExtension}, ",")) + w.Header().Set(net.HeaderTusResumable, "1.0.0") // TODO(jfd): only for dirs? + w.Header().Set(net.HeaderTusVersion, "1.0.0") + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Set(net.HeaderTusChecksumAlgorithm, "md5,sha1,crc32") } w.WriteHeader(http.StatusNoContent) } diff --git a/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go new file mode 100644 index 00000000000..9d03625ddb1 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go @@ -0,0 +1,2735 @@ +// 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. + +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + + authregistryv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" + + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + + context "context" + + corev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + + grpc "google.golang.org/grpc" + + invitev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" + + linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + + mock "github.com/stretchr/testify/mock" + + ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + + preferencesv1beta1 "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + registryv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" + + txv1beta1 "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + + v1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" +) + +// GatewayClient is an autogenerated mock type for the GatewayClient type +type GatewayClient struct { + mock.Mock +} + +// AcceptInvite provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) AcceptInvite(ctx context.Context, in *invitev1beta1.AcceptInviteRequest, opts ...grpc.CallOption) (*invitev1beta1.AcceptInviteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.AcceptInviteResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.AcceptInviteRequest, ...grpc.CallOption) *invitev1beta1.AcceptInviteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.AcceptInviteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.AcceptInviteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AddAppProvider provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) AddAppProvider(ctx context.Context, in *registryv1beta1.AddAppProviderRequest, opts ...grpc.CallOption) (*registryv1beta1.AddAppProviderResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.AddAppProviderResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.AddAppProviderRequest, ...grpc.CallOption) *registryv1beta1.AddAppProviderResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.AddAppProviderResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.AddAppProviderRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Authenticate provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.AuthenticateResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) *gatewayv1beta1.AuthenticateResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.AuthenticateResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CancelTransfer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CancelTransfer(ctx context.Context, in *txv1beta1.CancelTransferRequest, opts ...grpc.CallOption) (*txv1beta1.CancelTransferResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.CancelTransferResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.CancelTransferRequest, ...grpc.CallOption) *txv1beta1.CancelTransferResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.CancelTransferResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.CancelTransferRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateContainer(ctx context.Context, in *providerv1beta1.CreateContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) *providerv1beta1.CreateContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateHome provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateHome(ctx context.Context, in *providerv1beta1.CreateHomeRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateHomeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateHomeResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateHomeRequest, ...grpc.CallOption) *providerv1beta1.CreateHomeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateHomeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateHomeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateOCMCoreShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateOCMCoreShare(ctx context.Context, in *corev1beta1.CreateOCMCoreShareRequest, opts ...grpc.CallOption) (*corev1beta1.CreateOCMCoreShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *corev1beta1.CreateOCMCoreShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *corev1beta1.CreateOCMCoreShareRequest, ...grpc.CallOption) *corev1beta1.CreateOCMCoreShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*corev1beta1.CreateOCMCoreShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *corev1beta1.CreateOCMCoreShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateOCMShare(ctx context.Context, in *ocmv1beta1.CreateOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.CreateOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.CreateOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.CreateOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.CreateOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.CreateOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.CreateOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreatePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreatePublicShare(ctx context.Context, in *linkv1beta1.CreatePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.CreatePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.CreatePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.CreatePublicShareRequest, ...grpc.CallOption) *linkv1beta1.CreatePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.CreatePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.CreatePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateShare(ctx context.Context, in *collaborationv1beta1.CreateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.CreateShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.CreateShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) *collaborationv1beta1.CreateShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.CreateShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateStorageSpace(ctx context.Context, in *providerv1beta1.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.CreateStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateSymlink provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateSymlink(ctx context.Context, in *providerv1beta1.CreateSymlinkRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateSymlinkResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateSymlinkResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateSymlinkRequest, ...grpc.CallOption) *providerv1beta1.CreateSymlinkResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateSymlinkResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateSymlinkRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateTransfer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateTransfer(ctx context.Context, in *txv1beta1.CreateTransferRequest, opts ...grpc.CallOption) (*txv1beta1.CreateTransferResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.CreateTransferResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.CreateTransferRequest, ...grpc.CallOption) *txv1beta1.CreateTransferResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.CreateTransferResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.CreateTransferRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Delete(ctx context.Context, in *providerv1beta1.DeleteRequest, opts ...grpc.CallOption) (*providerv1beta1.DeleteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.DeleteResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) *providerv1beta1.DeleteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.DeleteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) DeleteStorageSpace(ctx context.Context, in *providerv1beta1.DeleteStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.DeleteStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.DeleteStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.DeleteStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.DeleteStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.DeleteStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.DeleteStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindAcceptedUsers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindAcceptedUsers(ctx context.Context, in *invitev1beta1.FindAcceptedUsersRequest, opts ...grpc.CallOption) (*invitev1beta1.FindAcceptedUsersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.FindAcceptedUsersResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.FindAcceptedUsersRequest, ...grpc.CallOption) *invitev1beta1.FindAcceptedUsersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.FindAcceptedUsersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.FindAcceptedUsersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindGroups provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindGroups(ctx context.Context, in *groupv1beta1.FindGroupsRequest, opts ...grpc.CallOption) (*groupv1beta1.FindGroupsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.FindGroupsResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.FindGroupsRequest, ...grpc.CallOption) *groupv1beta1.FindGroupsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.FindGroupsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.FindGroupsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindUsers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindUsers(ctx context.Context, in *userv1beta1.FindUsersRequest, opts ...grpc.CallOption) (*userv1beta1.FindUsersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.FindUsersResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.FindUsersRequest, ...grpc.CallOption) *userv1beta1.FindUsersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.FindUsersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.FindUsersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ForwardInvite provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ForwardInvite(ctx context.Context, in *invitev1beta1.ForwardInviteRequest, opts ...grpc.CallOption) (*invitev1beta1.ForwardInviteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.ForwardInviteResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.ForwardInviteRequest, ...grpc.CallOption) *invitev1beta1.ForwardInviteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.ForwardInviteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.ForwardInviteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GenerateAppPassword(ctx context.Context, in *applicationsv1beta1.GenerateAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.GenerateAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.GenerateAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.GenerateAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.GenerateAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.GenerateAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.GenerateAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateInviteToken provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GenerateInviteToken(ctx context.Context, in *invitev1beta1.GenerateInviteTokenRequest, opts ...grpc.CallOption) (*invitev1beta1.GenerateInviteTokenResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.GenerateInviteTokenResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.GenerateInviteTokenRequest, ...grpc.CallOption) *invitev1beta1.GenerateInviteTokenResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.GenerateInviteTokenResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.GenerateInviteTokenRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAcceptedUser provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAcceptedUser(ctx context.Context, in *invitev1beta1.GetAcceptedUserRequest, opts ...grpc.CallOption) (*invitev1beta1.GetAcceptedUserResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.GetAcceptedUserResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.GetAcceptedUserRequest, ...grpc.CallOption) *invitev1beta1.GetAcceptedUserResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.GetAcceptedUserResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.GetAcceptedUserRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAppPassword(ctx context.Context, in *applicationsv1beta1.GetAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.GetAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.GetAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.GetAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.GetAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.GetAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.GetAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAppProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAppProviders(ctx context.Context, in *registryv1beta1.GetAppProvidersRequest, opts ...grpc.CallOption) (*registryv1beta1.GetAppProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.GetAppProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.GetAppProvidersRequest, ...grpc.CallOption) *registryv1beta1.GetAppProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.GetAppProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.GetAppProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDefaultAppProviderForMimeType provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetDefaultAppProviderForMimeType(ctx context.Context, in *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, opts ...grpc.CallOption) (*registryv1beta1.GetDefaultAppProviderForMimeTypeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.GetDefaultAppProviderForMimeTypeResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) *registryv1beta1.GetDefaultAppProviderForMimeTypeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.GetDefaultAppProviderForMimeTypeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetGroup provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetGroup(ctx context.Context, in *groupv1beta1.GetGroupRequest, opts ...grpc.CallOption) (*groupv1beta1.GetGroupResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetGroupResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetGroupRequest, ...grpc.CallOption) *groupv1beta1.GetGroupResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetGroupResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetGroupRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetGroupByClaim provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetGroupByClaim(ctx context.Context, in *groupv1beta1.GetGroupByClaimRequest, opts ...grpc.CallOption) (*groupv1beta1.GetGroupByClaimResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetGroupByClaimResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetGroupByClaimRequest, ...grpc.CallOption) *groupv1beta1.GetGroupByClaimResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetGroupByClaimResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetGroupByClaimRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetHome provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetHome(ctx context.Context, in *providerv1beta1.GetHomeRequest, opts ...grpc.CallOption) (*providerv1beta1.GetHomeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetHomeResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetHomeRequest, ...grpc.CallOption) *providerv1beta1.GetHomeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetHomeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetHomeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetInfoByDomain provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetInfoByDomain(ctx context.Context, in *v1beta1.GetInfoByDomainRequest, opts ...grpc.CallOption) (*v1beta1.GetInfoByDomainResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.GetInfoByDomainResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.GetInfoByDomainRequest, ...grpc.CallOption) *v1beta1.GetInfoByDomainResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.GetInfoByDomainResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.GetInfoByDomainRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetKey provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetKey(ctx context.Context, in *preferencesv1beta1.GetKeyRequest, opts ...grpc.CallOption) (*preferencesv1beta1.GetKeyResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *preferencesv1beta1.GetKeyResponse + if rf, ok := ret.Get(0).(func(context.Context, *preferencesv1beta1.GetKeyRequest, ...grpc.CallOption) *preferencesv1beta1.GetKeyResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*preferencesv1beta1.GetKeyResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *preferencesv1beta1.GetKeyRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetLock(ctx context.Context, in *providerv1beta1.GetLockRequest, opts ...grpc.CallOption) (*providerv1beta1.GetLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetLockRequest, ...grpc.CallOption) *providerv1beta1.GetLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMembers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetMembers(ctx context.Context, in *groupv1beta1.GetMembersRequest, opts ...grpc.CallOption) (*groupv1beta1.GetMembersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetMembersResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetMembersRequest, ...grpc.CallOption) *groupv1beta1.GetMembersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetMembersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetMembersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetOCMShare(ctx context.Context, in *ocmv1beta1.GetOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.GetOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.GetOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.GetOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.GetOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.GetOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.GetOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPath provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPath(ctx context.Context, in *providerv1beta1.GetPathRequest, opts ...grpc.CallOption) (*providerv1beta1.GetPathResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetPathResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) *providerv1beta1.GetPathResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetPathResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPublicShare(ctx context.Context, in *linkv1beta1.GetPublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.GetPublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.GetPublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.GetPublicShareRequest, ...grpc.CallOption) *linkv1beta1.GetPublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.GetPublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.GetPublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPublicShareByToken provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPublicShareByToken(ctx context.Context, in *linkv1beta1.GetPublicShareByTokenRequest, opts ...grpc.CallOption) (*linkv1beta1.GetPublicShareByTokenResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.GetPublicShareByTokenResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.GetPublicShareByTokenRequest, ...grpc.CallOption) *linkv1beta1.GetPublicShareByTokenResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.GetPublicShareByTokenResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.GetPublicShareByTokenRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetQuota provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetQuota(ctx context.Context, in *gatewayv1beta1.GetQuotaRequest, opts ...grpc.CallOption) (*providerv1beta1.GetQuotaResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetQuotaResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.GetQuotaRequest, ...grpc.CallOption) *providerv1beta1.GetQuotaResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetQuotaResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.GetQuotaRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetReceivedOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetReceivedOCMShare(ctx context.Context, in *ocmv1beta1.GetReceivedOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.GetReceivedOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.GetReceivedOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.GetReceivedOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.GetReceivedOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.GetReceivedOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.GetReceivedOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetReceivedShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetReceivedShare(ctx context.Context, in *collaborationv1beta1.GetReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.GetReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.GetReceivedShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.GetReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetShare(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.GetShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) *collaborationv1beta1.GetShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.GetShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransferStatus provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetTransferStatus(ctx context.Context, in *txv1beta1.GetTransferStatusRequest, opts ...grpc.CallOption) (*txv1beta1.GetTransferStatusResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.GetTransferStatusResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.GetTransferStatusRequest, ...grpc.CallOption) *txv1beta1.GetTransferStatusResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.GetTransferStatusResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.GetTransferStatusRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUser provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUser(ctx context.Context, in *userv1beta1.GetUserRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserRequest, ...grpc.CallOption) *userv1beta1.GetUserResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserByClaim provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUserByClaim(ctx context.Context, in *userv1beta1.GetUserByClaimRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserByClaimResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserByClaimResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserByClaimRequest, ...grpc.CallOption) *userv1beta1.GetUserByClaimResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserByClaimResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserByClaimRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserGroups provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUserGroups(ctx context.Context, in *userv1beta1.GetUserGroupsRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserGroupsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserGroupsResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserGroupsRequest, ...grpc.CallOption) *userv1beta1.GetUserGroupsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserGroupsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserGroupsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HasMember provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) HasMember(ctx context.Context, in *groupv1beta1.HasMemberRequest, opts ...grpc.CallOption) (*groupv1beta1.HasMemberResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.HasMemberResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.HasMemberRequest, ...grpc.CallOption) *groupv1beta1.HasMemberResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.HasMemberResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.HasMemberRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileDownload provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InitiateFileDownload(ctx context.Context, in *providerv1beta1.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileDownloadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileDownloadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileDownloadResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileDownloadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileUpload provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InitiateFileUpload(ctx context.Context, in *providerv1beta1.InitiateFileUploadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileUploadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileUploadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileUploadResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileUploadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InvalidateAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InvalidateAppPassword(ctx context.Context, in *applicationsv1beta1.InvalidateAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.InvalidateAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.InvalidateAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.InvalidateAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.InvalidateAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.InvalidateAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.InvalidateAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsProviderAllowed provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) IsProviderAllowed(ctx context.Context, in *v1beta1.IsProviderAllowedRequest, opts ...grpc.CallOption) (*v1beta1.IsProviderAllowedResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.IsProviderAllowedResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.IsProviderAllowedRequest, ...grpc.CallOption) *v1beta1.IsProviderAllowedResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.IsProviderAllowedResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.IsProviderAllowedRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAllProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAllProviders(ctx context.Context, in *v1beta1.ListAllProvidersRequest, opts ...grpc.CallOption) (*v1beta1.ListAllProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.ListAllProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.ListAllProvidersRequest, ...grpc.CallOption) *v1beta1.ListAllProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.ListAllProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.ListAllProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAppPasswords provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAppPasswords(ctx context.Context, in *applicationsv1beta1.ListAppPasswordsRequest, opts ...grpc.CallOption) (*applicationsv1beta1.ListAppPasswordsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.ListAppPasswordsResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.ListAppPasswordsRequest, ...grpc.CallOption) *applicationsv1beta1.ListAppPasswordsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.ListAppPasswordsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.ListAppPasswordsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAppProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAppProviders(ctx context.Context, in *registryv1beta1.ListAppProvidersRequest, opts ...grpc.CallOption) (*registryv1beta1.ListAppProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.ListAppProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.ListAppProvidersRequest, ...grpc.CallOption) *registryv1beta1.ListAppProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.ListAppProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.ListAppProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAuthProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAuthProviders(ctx context.Context, in *authregistryv1beta1.ListAuthProvidersRequest, opts ...grpc.CallOption) (*gatewayv1beta1.ListAuthProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.ListAuthProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *authregistryv1beta1.ListAuthProvidersRequest, ...grpc.CallOption) *gatewayv1beta1.ListAuthProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.ListAuthProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *authregistryv1beta1.ListAuthProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListContainer(ctx context.Context, in *providerv1beta1.ListContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.ListContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) *providerv1beta1.ListContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListContainerStream provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListContainerStream(ctx context.Context, in *providerv1beta1.ListContainerStreamRequest, opts ...grpc.CallOption) (gatewayv1beta1.GatewayAPI_ListContainerStreamClient, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 gatewayv1beta1.GatewayAPI_ListContainerStreamClient + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListContainerStreamRequest, ...grpc.CallOption) gatewayv1beta1.GatewayAPI_ListContainerStreamClient); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(gatewayv1beta1.GatewayAPI_ListContainerStreamClient) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListContainerStreamRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListFileVersions provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListFileVersions(ctx context.Context, in *providerv1beta1.ListFileVersionsRequest, opts ...grpc.CallOption) (*providerv1beta1.ListFileVersionsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListFileVersionsResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) *providerv1beta1.ListFileVersionsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListFileVersionsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListOCMShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListOCMShares(ctx context.Context, in *ocmv1beta1.ListOCMSharesRequest, opts ...grpc.CallOption) (*ocmv1beta1.ListOCMSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.ListOCMSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.ListOCMSharesRequest, ...grpc.CallOption) *ocmv1beta1.ListOCMSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.ListOCMSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.ListOCMSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListPublicShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListPublicShares(ctx context.Context, in *linkv1beta1.ListPublicSharesRequest, opts ...grpc.CallOption) (*linkv1beta1.ListPublicSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.ListPublicSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.ListPublicSharesRequest, ...grpc.CallOption) *linkv1beta1.ListPublicSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.ListPublicSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.ListPublicSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListReceivedOCMShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListReceivedOCMShares(ctx context.Context, in *ocmv1beta1.ListReceivedOCMSharesRequest, opts ...grpc.CallOption) (*ocmv1beta1.ListReceivedOCMSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.ListReceivedOCMSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.ListReceivedOCMSharesRequest, ...grpc.CallOption) *ocmv1beta1.ListReceivedOCMSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.ListReceivedOCMSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.ListReceivedOCMSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListReceivedShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListReceivedShares(ctx context.Context, in *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.ListReceivedSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) *collaborationv1beta1.ListReceivedSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ListReceivedSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecycle provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListRecycle(ctx context.Context, in *providerv1beta1.ListRecycleRequest, opts ...grpc.CallOption) (*providerv1beta1.ListRecycleResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListRecycleResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListRecycleRequest, ...grpc.CallOption) *providerv1beta1.ListRecycleResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListRecycleResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListRecycleRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecycleStream provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListRecycleStream(ctx context.Context, in *providerv1beta1.ListRecycleStreamRequest, opts ...grpc.CallOption) (gatewayv1beta1.GatewayAPI_ListRecycleStreamClient, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 gatewayv1beta1.GatewayAPI_ListRecycleStreamClient + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListRecycleStreamRequest, ...grpc.CallOption) gatewayv1beta1.GatewayAPI_ListRecycleStreamClient); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(gatewayv1beta1.GatewayAPI_ListRecycleStreamClient) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListRecycleStreamRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListShares(ctx context.Context, in *collaborationv1beta1.ListSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.ListSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) *collaborationv1beta1.ListSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ListSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListStorageSpaces provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListStorageSpaces(ctx context.Context, in *providerv1beta1.ListStorageSpacesRequest, opts ...grpc.CallOption) (*providerv1beta1.ListStorageSpacesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListStorageSpacesResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListStorageSpacesRequest, ...grpc.CallOption) *providerv1beta1.ListStorageSpacesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListStorageSpacesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListStorageSpacesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListSupportedMimeTypes provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListSupportedMimeTypes(ctx context.Context, in *registryv1beta1.ListSupportedMimeTypesRequest, opts ...grpc.CallOption) (*registryv1beta1.ListSupportedMimeTypesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.ListSupportedMimeTypesResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.ListSupportedMimeTypesRequest, ...grpc.CallOption) *registryv1beta1.ListSupportedMimeTypesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.ListSupportedMimeTypesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.ListSupportedMimeTypesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Move provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Move(ctx context.Context, in *providerv1beta1.MoveRequest, opts ...grpc.CallOption) (*providerv1beta1.MoveResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.MoveResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) *providerv1beta1.MoveResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.MoveResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OpenInApp provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) OpenInApp(ctx context.Context, in *gatewayv1beta1.OpenInAppRequest, opts ...grpc.CallOption) (*appproviderv1beta1.OpenInAppResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *appproviderv1beta1.OpenInAppResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.OpenInAppRequest, ...grpc.CallOption) *appproviderv1beta1.OpenInAppResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*appproviderv1beta1.OpenInAppResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.OpenInAppRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PurgeRecycle provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) PurgeRecycle(ctx context.Context, in *providerv1beta1.PurgeRecycleRequest, opts ...grpc.CallOption) (*providerv1beta1.PurgeRecycleResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.PurgeRecycleResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.PurgeRecycleRequest, ...grpc.CallOption) *providerv1beta1.PurgeRecycleResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.PurgeRecycleResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.PurgeRecycleRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RefreshLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RefreshLock(ctx context.Context, in *providerv1beta1.RefreshLockRequest, opts ...grpc.CallOption) (*providerv1beta1.RefreshLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RefreshLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RefreshLockRequest, ...grpc.CallOption) *providerv1beta1.RefreshLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RefreshLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RefreshLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemoveOCMShare(ctx context.Context, in *ocmv1beta1.RemoveOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.RemoveOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.RemoveOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.RemoveOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.RemoveOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.RemoveOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.RemoveOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemovePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemovePublicShare(ctx context.Context, in *linkv1beta1.RemovePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.RemovePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.RemovePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.RemovePublicShareRequest, ...grpc.CallOption) *linkv1beta1.RemovePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.RemovePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.RemovePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemoveShare(ctx context.Context, in *collaborationv1beta1.RemoveShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.RemoveShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.RemoveShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) *collaborationv1beta1.RemoveShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.RemoveShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RestoreFileVersion provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RestoreFileVersion(ctx context.Context, in *providerv1beta1.RestoreFileVersionRequest, opts ...grpc.CallOption) (*providerv1beta1.RestoreFileVersionResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RestoreFileVersionResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) *providerv1beta1.RestoreFileVersionResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RestoreFileVersionResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RestoreRecycleItem provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RestoreRecycleItem(ctx context.Context, in *providerv1beta1.RestoreRecycleItemRequest, opts ...grpc.CallOption) (*providerv1beta1.RestoreRecycleItemResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RestoreRecycleItemResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RestoreRecycleItemRequest, ...grpc.CallOption) *providerv1beta1.RestoreRecycleItemResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RestoreRecycleItemResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RestoreRecycleItemRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetArbitraryMetadata provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetArbitraryMetadata(ctx context.Context, in *providerv1beta1.SetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.SetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.SetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.SetArbitraryMetadataResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.SetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetDefaultAppProviderForMimeType provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetDefaultAppProviderForMimeType(ctx context.Context, in *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, opts ...grpc.CallOption) (*registryv1beta1.SetDefaultAppProviderForMimeTypeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.SetDefaultAppProviderForMimeTypeResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) *registryv1beta1.SetDefaultAppProviderForMimeTypeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.SetDefaultAppProviderForMimeTypeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetKey provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetKey(ctx context.Context, in *preferencesv1beta1.SetKeyRequest, opts ...grpc.CallOption) (*preferencesv1beta1.SetKeyResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *preferencesv1beta1.SetKeyResponse + if rf, ok := ret.Get(0).(func(context.Context, *preferencesv1beta1.SetKeyRequest, ...grpc.CallOption) *preferencesv1beta1.SetKeyResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*preferencesv1beta1.SetKeyResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *preferencesv1beta1.SetKeyRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetLock(ctx context.Context, in *providerv1beta1.SetLockRequest, opts ...grpc.CallOption) (*providerv1beta1.SetLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.SetLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.SetLockRequest, ...grpc.CallOption) *providerv1beta1.SetLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.SetLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.SetLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Stat provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Stat(ctx context.Context, in *providerv1beta1.StatRequest, opts ...grpc.CallOption) (*providerv1beta1.StatResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.StatResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) *providerv1beta1.StatResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.StatResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TouchFile provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) TouchFile(ctx context.Context, in *providerv1beta1.TouchFileRequest, opts ...grpc.CallOption) (*providerv1beta1.TouchFileResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.TouchFileResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.TouchFileRequest, ...grpc.CallOption) *providerv1beta1.TouchFileResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.TouchFileResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.TouchFileRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unlock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Unlock(ctx context.Context, in *providerv1beta1.UnlockRequest, opts ...grpc.CallOption) (*providerv1beta1.UnlockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UnlockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UnlockRequest, ...grpc.CallOption) *providerv1beta1.UnlockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UnlockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UnlockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnsetArbitraryMetadata provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UnsetArbitraryMetadata(ctx context.Context, in *providerv1beta1.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.UnsetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UnsetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.UnsetArbitraryMetadataResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UnsetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateOCMShare(ctx context.Context, in *ocmv1beta1.UpdateOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.UpdateOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.UpdateOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.UpdateOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.UpdateOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.UpdateOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.UpdateOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdatePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdatePublicShare(ctx context.Context, in *linkv1beta1.UpdatePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.UpdatePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.UpdatePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.UpdatePublicShareRequest, ...grpc.CallOption) *linkv1beta1.UpdatePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.UpdatePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.UpdatePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateReceivedOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateReceivedOCMShare(ctx context.Context, in *ocmv1beta1.UpdateReceivedOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.UpdateReceivedOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.UpdateReceivedOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.UpdateReceivedOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.UpdateReceivedOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.UpdateReceivedOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.UpdateReceivedOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateReceivedShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateReceivedShare(ctx context.Context, in *collaborationv1beta1.UpdateReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.UpdateReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.UpdateReceivedShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.UpdateReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateShare(ctx context.Context, in *collaborationv1beta1.UpdateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.UpdateShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) *collaborationv1beta1.UpdateShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.UpdateShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateStorageSpace(ctx context.Context, in *providerv1beta1.UpdateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.UpdateStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UpdateStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UpdateStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.UpdateStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UpdateStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UpdateStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WhoAmI provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) WhoAmI(ctx context.Context, in *gatewayv1beta1.WhoAmIRequest, opts ...grpc.CallOption) (*gatewayv1beta1.WhoAmIResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.WhoAmIResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.WhoAmIRequest, ...grpc.CallOption) *gatewayv1beta1.WhoAmIResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.WhoAmIResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.WhoAmIRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go similarity index 68% rename from internal/http/services/owncloud/ocdav/propfind.go rename to internal/http/services/owncloud/ocdav/propfind/propfind.go index 9732e06ca73..7d5a42c7b31 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -16,10 +16,9 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocdav +package propfind import ( - "bytes" "context" "encoding/json" "encoding/xml" @@ -34,11 +33,14 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" @@ -52,23 +54,33 @@ import ( "go.opentelemetry.io/otel/codes" ) -const ( - _nsDav = "DAV:" - _nsOwncloud = "http://owncloud.org/ns" - _nsOCS = "http://open-collaboration-services.org/ns" +//go:generate mockery -name GatewayClient - _propOcFavorite = "http://owncloud.org/ns/favorite" +// GatewayClient is the interface that's being uses to interact with the gateway +type GatewayClient interface { + gateway.GatewayAPIClient +} - // RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781 - RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT" +// GetGatewayServiceClientFunc is a callback used to pass in a StorageProviderClient during testing +type GetGatewayServiceClientFunc func() (GatewayClient, error) - // _propQuotaUncalculated = "-1" - _propQuotaUnknown = "-2" - // _propQuotaUnlimited = "-3" -) +// Handler handles propfind requests +type Handler struct { + PublicURL string + getClient GetGatewayServiceClientFunc +} + +// NewHandler returns a new PropfindHandler instance +func NewHandler(publicURL string, getClientFunc GetGatewayServiceClientFunc) *Handler { + return &Handler{ + PublicURL: publicURL, + getClient: getClientFunc, + } +} +// HandlePathPropfind handles a path based propfind request // ns is the namespace that is prefixed to the path in the cs3 namespace -func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns string) { +func (p *Handler) HandlePathPropfind(w http.ResponseWriter, r *http.Request, ns string) { ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) defer span.End() @@ -78,7 +90,7 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - pf, status, err := readPropfind(r.Body) + pf, status, err := ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -86,7 +98,14 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri } // retrieve a specific storage space - spaces, rpcStatus, err := s.lookUpStorageSpacesForPathWithChildren(ctx, fn) + client, err := p.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error retrieving a gateway service client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + spaces, rpcStatus, err := spacelookup.LookUpStorageSpacesForPathWithChildren(ctx, client.(gateway.GatewayAPIClient), fn) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -94,25 +113,32 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, spaces, fn, false, sublog) + resourceInfos, sendTusHeaders, ok := p.getResourceInfos(ctx, w, r, pf, spaces, fn, false, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return } - s.propfindResponse(ctx, w, r, ns, pf, sendTusHeaders, resourceInfos, sublog) + p.propfindResponse(ctx, w, r, ns, pf, sendTusHeaders, resourceInfos, sublog) } -func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { +// HandleSpacesPropfind handles a spaces based propfind request +func (p *Handler) HandleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "spaces_propfind") defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Logger() + client, err := p.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - pf, status, err := readPropfind(r.Body) + pf, status, err := ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -120,19 +146,19 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } // retrieve a specific storage space - space, rpcStatus, err := s.lookUpStorageSpaceByID(ctx, spaceID) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceByID(ctx, client.(gateway.GatewayAPIClient), spaceID) if err != nil { - sublog.Error().Err(err).Msg("error sending a grpc request") + sublog.Error().Err(err).Msg("error looking up the space by id") w.WriteHeader(http.StatusInternalServerError) return } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, []*provider.StorageSpace{space}, r.URL.Path, true, sublog) + resourceInfos, sendTusHeaders, ok := p.getResourceInfos(ctx, w, r, pf, []*provider.StorageSpace{space}, r.URL.Path, true, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return @@ -143,11 +169,11 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space resourceInfos[i].Path = path.Join("/", spaceID, resourceInfos[i].Path) } - s.propfindResponse(ctx, w, r, "", pf, sendTusHeaders, resourceInfos, sublog) + p.propfindResponse(ctx, w, r, "", pf, sendTusHeaders, resourceInfos, sublog) } -func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, sendTusHeaders bool, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { +func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf XML, sendTusHeaders bool, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response") defer span.End() @@ -156,7 +182,7 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id)) } - client, err := s.getClient() + client, err := p.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -175,20 +201,20 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht span.SetStatus(codes.Error, err.Error()) } - propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, linkshares) + propRes, err := MultistatusResponse(ctx, &pf, resourceInfos, p.PublicURL, namespace, linkshares) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") if sendTusHeaders { - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ", ")) - w.Header().Set(HeaderTusResumable, "1.0.0") - w.Header().Set(HeaderTusVersion, "1.0.0") - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderTusVersion, net.HeaderTusExtension}, ", ")) + w.Header().Set(net.HeaderTusResumable, "1.0.0") + w.Header().Set(net.HeaderTusVersion, "1.0.0") + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") } w.WriteHeader(http.StatusMultiStatus) @@ -198,7 +224,7 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht } // TODO this is just a stat -> rename -func (s *svc) statSpace(ctx context.Context, client gateway.GatewayAPIClient, space *provider.StorageSpace, ref *provider.Reference, metadataKeys []string) (*provider.ResourceInfo, *rpc.Status, error) { +func (p *Handler) statSpace(ctx context.Context, client gateway.GatewayAPIClient, space *provider.StorageSpace, ref *provider.Reference, metadataKeys []string) (*provider.ResourceInfo, *rpc.Status, error) { req := &provider.StatRequest{ Ref: ref, ArbitraryMetadataKeys: metadataKeys, @@ -210,8 +236,8 @@ func (s *svc) statSpace(ctx context.Context, client gateway.GatewayAPIClient, sp return res.Info, res.Status, nil } -func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf propfindXML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { - depth := r.Header.Get(HeaderDepth) +func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf XML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } @@ -220,15 +246,12 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht log.Debug().Str("depth", depth).Msg("invalid Depth header value") w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", depth) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, false, false } - client, err := s.getClient() + client, err := p.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -266,8 +289,8 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht spacePath := string(space.Opaque.Map["path"].Value) // TODO separate stats to the path or to the children, after statting all children update the mtime/etag // TODO get mtime, and size from space as well, so we no longer have to stat here? - spaceRef := makeRelativeReference(space, requestPath, spacesPropfind) - info, status, err := s.statSpace(ctx, client, space, spaceRef, metadataKeys) + spaceRef := spacelookup.MakeRelativeReference(space, requestPath, spacesPropfind) + info, status, err := p.statSpace(ctx, client.(gateway.GatewayAPIClient), space, spaceRef, metadataKeys) if err != nil || status.Code != rpc.Code_CODE_OK { continue } @@ -305,11 +328,8 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht // TODO if we have children invent node on the fly w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", requestPath) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, false, false } if mostRecentChildInfo != nil { @@ -471,7 +491,7 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht func requiresExplicitFetching(n *xml.Name) bool { switch n.Space { - case _nsDav: + case net.NsDav: switch n.Local { case "quota-available-bytes", "quota-used-bytes": // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes @@ -480,54 +500,56 @@ func requiresExplicitFetching(n *xml.Name) bool { default: return false } - case _nsOwncloud: + case net.NsOwncloud: switch n.Local { case "favorite", "share-types", "checksums", "size": return true default: return false } - case _nsOCS: + case net.NsOCS: return false } return true } +// ReadPropfind extracts and parses the propfind XML information from a Reader // from https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/xml.go#L178-L205 -func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { +func ReadPropfind(r io.Reader) (pf XML, status int, err error) { c := countingReader{r: r} if err = xml.NewDecoder(&c).Decode(&pf); err != nil { if err == io.EOF { if c.n == 0 { // An empty body means to propfind allprop. // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND - return propfindXML{Allprop: new(struct{})}, 0, nil + return XML{Allprop: new(struct{})}, 0, nil } - err = errInvalidPropfind + err = errors.ErrorInvalidPropfind } - return propfindXML{}, http.StatusBadRequest, err + return XML{}, http.StatusBadRequest, err } if pf.Allprop == nil && pf.Include != nil { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Prop != nil && pf.Propname != nil { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { // jfd: I think is perfectly valid ... treat it as allprop - return propfindXML{Allprop: new(struct{})}, 0, nil + return XML{Allprop: new(struct{})}, 0, nil } return pf, 0, nil } -func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, linkshares map[string]struct{}) (string, error) { - responses := make([]*responseXML, 0, len(mds)) +// MultistatusResponse converts a list of resource infos into a multistatus response string +func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) (string, error) { + responses := make([]*ResponseXML, 0, len(mds)) for i := range mds { - res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, linkshares) + res, err := mdToPropResponse(ctx, pf, mds[i], publicURL, ns, linkshares) if err != nil { return "", err } @@ -544,55 +566,23 @@ func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*p return msg, nil } -func (s *svc) xmlEscaped(val string) []byte { - buf := new(bytes.Buffer) - xml.Escape(buf, []byte(val)) - return buf.Bytes() -} - -func (s *svc) newPropNS(namespace string, local string, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: namespace, Local: local}, - Lang: "", - InnerXML: s.xmlEscaped(val), - } -} - -// TODO properly use the space -func (s *svc) newProp(key, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: "", Local: key}, - Lang: "", - InnerXML: s.xmlEscaped(val), - } -} - -// TODO properly use the space -func (s *svc) newPropRaw(key, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: "", Local: key}, - Lang: "", - InnerXML: []byte(val), - } -} - // mdToPropResponse converts the CS3 metadata into a webdav PropResponse // ns is the CS3 namespace that needs to be removed from the CS3 path before // prefixing it with the baseURI -func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, linkshares map[string]struct{}) (*responseXML, error) { +func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) (*ResponseXML, error) { sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger() md.Path = strings.TrimPrefix(md.Path, ns) - baseURI := ctx.Value(ctxKeyBaseURI).(string) + baseURI := ctx.Value(net.CtxKeyBaseURI).(string) ref := path.Join(baseURI, md.Path) if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" } - response := responseXML{ - Href: encodePath(ref), - Propstat: []propstatXML{}, + response := ResponseXML{ + Href: net.EncodePath(ref), + Propstat: []PropstatXML{}, } var ls *link.PublicShare @@ -600,7 +590,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // -1 indicates uncalculated // -2 indicates unknown (default) // -3 indicates unlimited - quota := _propQuotaUnknown + quota := net.PropQuotaUnknown size := fmt.Sprintf("%d", md.Size) // TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err // or use ok like pattern and return bool? @@ -619,7 +609,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide role := conversions.RoleFromResourcePermissions(md.PermissionSet) - isShared := !isCurrentUserOwner(ctx, md.Owner) + isShared := !net.IsCurrentUserOwner(ctx, md.Owner) var wdp string isPublic := ls != nil if md.PermissionSet != nil { @@ -632,13 +622,13 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") } - propstatOK := propstatXML{ + propstatOK := PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } - propstatNotFound := propstatXML{ + propstatNotFound := PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } // when allprops has been requested if pf.Allprop != nil { @@ -647,8 +637,8 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if md.Id != nil { id := resourceid.OwnCloudResourceIDWrap(md.Id) propstatOK.Prop = append(propstatOK.Prop, - s.newProp("oc:id", id), - s.newProp("oc:fileid", id), + props.NewProp("oc:id", id), + props.NewProp("oc:fileid", id), ) } @@ -656,37 +646,37 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // etags must be enclosed in double quotes and cannot contain them. // See https://tools.ietf.org/html/rfc7232#section-2.3 for details // TODO(jfd) handle weak tags that start with 'W/' - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) } if md.PermissionSet != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) } // always return size, well nearly always ... public link shares are a little weird if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) if ls == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) } // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes // from https://www.rfc-editor.org/rfc/rfc4331.html#section-2 - // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) - // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) + // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) } else { propstatOK.Prop = append(propstatOK.Prop, - s.newProp("d:resourcetype", ""), - s.newProp("d:getcontentlength", size), + props.NewProp("d:resourcetype", ""), + props.NewProp("d:getcontentlength", size), ) if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) } } // Finder needs the getLastModified property to work. if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() - lastModifiedString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + lastModifiedString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) } // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 @@ -717,20 +707,20 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if checksums.Len() > 0 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) } // ls do not report any properties as missing by default if ls == nil { // favorites from arbitrary metadata if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd[_propOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", v)) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } } // TODO return other properties ... but how do we put them in a namespace? @@ -738,21 +728,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // otherwise return only the requested properties for i := range pf.Prop { switch pf.Prop[i].Space { - case _nsOwncloud: + case net.NsOwncloud: switch pf.Prop[i].Local { // TODO(jfd): maybe phoenix and the other clients can just use this id as an opaque string? // I tested the desktop client and phoenix to annotate which properties are requestted, see below cases case "fileid": // phoenix only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:fileid", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:fileid", "")) } case "id": // desktop client only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:id", "")) } case "permissions": // both // oc:permissions take several char flags to indicate the permissions the user has on this node: @@ -764,78 +754,78 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // R = Shareable (Reshare) // M = Mounted // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) case "public-link-permission": // only on a share root node if ls != nil && md.PermissionSet != nil { propstatOK.Prop = append( propstatOK.Prop, - s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) + props.NewProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-permission", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-permission", "")) } case "public-link-item-type": // only on a share root node if ls != nil { if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "folder")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "folder")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "file")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "file")) // redirectref is another option } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-item-type", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-item-type", "")) } case "public-link-share-datetime": if ls != nil && ls.Mtime != nil { t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime? - shareTimeString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-datetime", shareTimeString)) + shareTimeString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-datetime", shareTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-datetime", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-datetime", "")) } case "public-link-share-owner": if ls != nil && ls.Owner != nil { - if isCurrentUserOwner(ctx, ls.Owner) { + if net.IsCurrentUserOwner(ctx, ls.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-owner", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-owner", u.Username)) } else { u, _ := ctxpkg.ContextGetUser(ctx) sublog.Error().Interface("share", ls).Interface("user", u).Msg("the current user in the context should be the owner of a public link share") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) } case "public-link-expiration": if ls != nil && ls.Expiration != nil { t := utils.TSToTime(ls.Expiration).UTC() - expireTimeString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-expiration", expireTimeString)) + expireTimeString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-expiration", expireTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) } - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) case "size": // phoenix only // TODO we cannot find out if md.Size is set or not because ints in go default to 0 // TODO what is the difference to d:quota-used-bytes (which only exists for collections)? // oc:size is available on files and folders and behaves like d:getcontentlength or d:quota-used-bytes respectively // The hasPrefix is a workaround to make children of the link root show a size if they have 0 bytes if ls == nil || strings.HasPrefix(md.Path, "/"+ls.Token+"/") { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) } else { // link share root collection has no size - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) } case "owner-id": // phoenix only if md.Owner != nil { - if isCurrentUserOwner(ctx, md.Owner) { + if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-id", u.Username)) } else { sublog.Debug().Msg("TODO fetch user username") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) } case "favorite": // phoenix only // TODO: can be 0 or 1?, in oc10 it is present or not @@ -843,17 +833,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // TODO: this boolean favorite property is so horribly wrong ... either it is presont, or it is not ... unless ... it is possible to have a non binary value ... we need to double check if ls == nil { if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd[_propOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "1")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } } else { // link share root collection has no favorite - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:favorite", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:favorite", "")) } case "checksums": // desktop ... not really ... the desktop sends the OC-Checksum header @@ -885,9 +875,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if checksums.Len() > 13 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:checksums", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:checksums", "")) } case "share-types": // desktop var types strings.Builder @@ -906,21 +896,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if types.Len() != 0 { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:share-types", types.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:share-types", types.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } case "owner-display-name": // phoenix only if md.Owner != nil { - if isCurrentUserOwner(ctx, md.Owner) { + if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", u.DisplayName)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-display-name", u.DisplayName)) } else { sublog.Debug().Msg("TODO fetch user displayname") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) } case "downloadURL": // desktop if isPublic && md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { @@ -939,9 +929,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide path = sb.String() } - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:downloadURL", s.c.PublicURL+baseURI+path)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:downloadURL", publicURL+baseURI+path)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } case "signature-auth": if isPublic { @@ -956,9 +946,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sb.WriteString(expiration.Format(time.RFC3339)) sb.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:signature-auth", sb.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:signature-auth", sb.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:signature-auth", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:signature-auth", "")) } } case "privatelink": // phoenix only @@ -974,15 +964,15 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // TODO(jfd): double check the client behavior with reva on backup restore fallthrough default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } - case _nsDav: + case net.NsDav: switch pf.Prop[i].Local { case "getetag": // both if md.Etag != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getetag", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getetag", "")) } case "getcontentlength": // both // see everts stance on this https://stackoverflow.com/a/31621912, he points to http://tools.ietf.org/html/rfc4918#section-15.3 @@ -991,53 +981,53 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // which is not the case ... so we don't return it on collections. owncloud has oc:size for that // TODO we cannot find out if md.Size is set or not because ints in go default to 0 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } case "resourcetype": // both if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": // phoenix if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // directories have no contenttype - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) } else if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) } case "getlastmodified": // both // TODO we cannot find out if md.Mtime is set or not because ints in go default to 0 if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() - lastModifiedString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + lastModifiedString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getlastmodified", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getlastmodified", "")) } case "quota-used-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // always returns the current usage, // in oc10 there seems to be a bug that makes the size in webdav differ from the one in the user properties, not taking shares into account // in ocis we plan to always mak the quota a property of the storage space - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-used-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-used-bytes", "")) } case "quota-available-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // oc10 returns -3 for unlimited, -2 for unknown, -1 for uncalculated - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-available-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-available-bytes", "")) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } - case _nsOCS: + case net.NsOCS: switch pf.Prop[i].Local { // ocs:share-permissions indicate clients the maximum permissions that can be granted: // 1 = read @@ -1054,21 +1044,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide perms &^= conversions.PermissionCreate perms &^= conversions.PermissionDelete } - propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } default: // handle custom properties if k := md.GetArbitraryMetadata(); k == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } else if amd := k.GetMetadata(); amd == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } else if v, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } } } @@ -1092,17 +1082,6 @@ func quoteEtag(etag string) string { return `"` + strings.Trim(etag, `"`) + `"` } -// a file is only yours if you are the owner -func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { - contextUser, ok := ctxpkg.ContextGetUser(ctx) - if ok && contextUser.Id != nil && owner != nil && - contextUser.Id.Idp == owner.Idp && - contextUser.Id.OpaqueId == owner.OpaqueId { - return true - } - return false -} - type countingReader struct { n int r io.Reader @@ -1116,23 +1095,24 @@ func (c *countingReader) Read(p []byte) (int, error) { func metadataKeyOf(n *xml.Name) string { switch { - case n.Space == _nsDav && n.Local == "quota-available-bytes": + case n.Space == net.NsDav && n.Local == "quota-available-bytes": return "quota" default: return fmt.Sprintf("%s/%s", n.Space, n.Local) } } +// Props represents properties related to a resource // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) -type propfindProps []xml.Name +type Props []xml.Name // UnmarshalXML appends the property names enclosed within start to pn. // // It returns an error if start does not contain any properties or if // properties contain values. Character data between properties is ignored. -func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { +func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for { - t, err := next(d) + t, err := props.Next(d) if err != nil { return err } @@ -1146,7 +1126,7 @@ func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er */ return nil case xml.StartElement: - t, err = next(d) + t, err = props.Next(d) if err != nil { return err } @@ -1158,53 +1138,36 @@ func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er } } +// XML holds the xml representation of a propfind // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind -type propfindXML struct { - XMLName xml.Name `xml:"DAV: propfind"` - Allprop *struct{} `xml:"DAV: allprop"` - Propname *struct{} `xml:"DAV: propname"` - Prop propfindProps `xml:"DAV: prop"` - Include propfindProps `xml:"DAV: include"` +type XML struct { + XMLName xml.Name `xml:"DAV: propfind"` + Allprop *struct{} `xml:"DAV: allprop"` + Propname *struct{} `xml:"DAV: propname"` + Prop Props `xml:"DAV: prop"` + Include Props `xml:"DAV: include"` } -type responseXML struct { - XMLName xml.Name `xml:"d:response"` - Href string `xml:"d:href"` - Propstat []propstatXML `xml:"d:propstat"` - Status string `xml:"d:status,omitempty"` - Error *errorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` +// ResponseXML holds the xml representation of a propfind response +type ResponseXML struct { + XMLName xml.Name `xml:"d:response"` + Href string `xml:"d:href"` + Propstat []PropstatXML `xml:"d:propstat"` + Status string `xml:"d:status,omitempty"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` } +// PropstatXML holds the xml representation of a propfind response // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat -type propstatXML struct { +type PropstatXML struct { // Prop requires DAV: to be the default namespace in the enclosing // XML. This is due to the standard encoding/xml package currently // not honoring namespace declarations inside a xmltag with a // parent element for anonymous slice elements. // Use of multistatusWriter takes care of this. - Prop []*propertyXML `xml:"d:prop>_ignored_"` - Status string `xml:"d:status"` - Error *errorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` -} - -// Property represents a single DAV resource property as defined in RFC 4918. -// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties -type propertyXML struct { - // XMLName is the fully qualified name that identifies this property. - XMLName xml.Name - - // Lang is an optional xml:lang attribute. - Lang string `xml:"xml:lang,attr,omitempty"` - - // InnerXML contains the XML representation of the property value. - // See http://www.webdav.org/specs/rfc4918.html#property_values - // - // Property values of complex type or mixed-content must have fully - // expanded XML namespaces or be self-contained with according - // XML namespace declarations. They must not rely on any XML - // namespace declarations within the scope of the XML document, - // even including the DAV: namespace. - InnerXML []byte `xml:",innerxml"` + Prop []*props.PropertyXML `xml:"d:prop>_ignored_"` + Status string `xml:"d:status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` } diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go new file mode 100644 index 00000000000..d02dba936bd --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go @@ -0,0 +1,31 @@ +// 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 propfind_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestPropfind(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Propfind Suite") +} diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go new file mode 100644 index 00000000000..0109fb82a89 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go @@ -0,0 +1,73 @@ +// 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 propfind_test + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + + sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind/mocks" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/stretchr/testify/mock" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Propfind", func() { + var ( + handler *propfind.Handler + client *mocks.GatewayClient + ctx context.Context + ) + + JustBeforeEach(func() { + ctx = context.Background() + client = &mocks.GatewayClient{} + handler = propfind.NewHandler("127.0.0.1:3000", func() (propfind.GatewayClient, error) { + return client, nil + }) + }) + + Describe("NewHandler", func() { + It("returns a handler", func() { + Expect(handler).ToNot(BeNil()) + }) + }) + + Describe("HandleSpacesPropfind", func() { + It("handles invalid space ids", func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*sprovider.StorageSpace{}, + }, nil) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + + handler.HandleSpacesPropfind(rr, req, "foo") + Expect(rr.Code).To(Equal(http.StatusNotFound)) + }) + }) +}) diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 73495868913..0846579e28c 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -29,10 +29,14 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" - "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -49,11 +53,8 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str sublog.Debug().Err(err).Msg("error reading proppatch") w.WriteHeader(status) m := fmt.Sprintf("Error reading proppatch: %v", err) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(&sublog, w, b, err) return } @@ -64,18 +65,18 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str return } - space, rpcStatus, err := s.lookUpStorageSpaceForPath(ctx, fn) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, c, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } // check if resource exists - statReq := &provider.StatRequest{Ref: makeRelativeReference(space, fn, false)} + statReq := &provider.StatRequest{Ref: spacelookup.MakeRelativeReference(space, fn, false)} statRes, err := c.Stat(ctx, statReq) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc stat request") @@ -87,24 +88,21 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", fn) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&sublog, w, b, err) } - HandleErrorStatus(&sublog, w, statRes.Status) + errors.HandleErrorStatus(&sublog, w, statRes.Status) return } - acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, makeRelativeReference(space, fn, false), pp, sublog) + acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), pp, sublog) if !ok { // handleProppatch handles responses in error cases so we can just return return } nRef := strings.TrimPrefix(fn, ns) - nRef = path.Join(ctx.Value(ctxKeyBaseURI).(string), nRef) + nRef = path.Join(ctx.Value(net.CtxKeyBaseURI).(string), nRef) if statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { nRef += "/" } @@ -118,6 +116,13 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Logger() + c, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + pp, status, err := readProppatch(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading proppatch") @@ -126,7 +131,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, c, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -134,16 +139,10 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - c, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } // check if resource exists statReq := &provider.StatRequest{ Ref: ref, @@ -156,7 +155,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } if statRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, statRes.Status) + errors.HandleErrorStatus(&sublog, w, statRes.Status) return } @@ -167,7 +166,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } nRef := path.Join(spaceID, statRes.Info.Path) - nRef = path.Join(ctx.Value(ctxKeyBaseURI).(string), nRef) + nRef = path.Join(ctx.Value(net.CtxKeyBaseURI).(string), nRef) if statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { nRef += "/" } @@ -232,14 +231,11 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to remove properties on resource %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return nil, nil, false } if key == "http://owncloud.org/ns/favorite" { @@ -269,14 +265,11 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to set properties on resource %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return nil, nil, false } @@ -313,8 +306,8 @@ func (s *svc) handleProppatchResponse(ctx context.Context, w http.ResponseWriter w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(propRes)); err != nil { log.Err(err).Msg("error writing response") @@ -322,29 +315,29 @@ func (s *svc) handleProppatchResponse(ctx context.Context, w http.ResponseWriter } func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.Name, removedProps []xml.Name, ref string) (string, error) { - responses := make([]responseXML, 0, 1) - response := responseXML{ - Href: encodePath(ref), - Propstat: []propstatXML{}, + responses := make([]propfind.ResponseXML, 0, 1) + response := propfind.ResponseXML{ + Href: net.EncodePath(ref), + Propstat: []propfind.PropstatXML{}, } if len(acceptedProps) > 0 { - propstatBody := []*propertyXML{} + propstatBody := []*props.PropertyXML{} for i := range acceptedProps { - propstatBody = append(propstatBody, s.newPropNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) + propstatBody = append(propstatBody, props.NewPropNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) } - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", Prop: propstatBody, }) } if len(removedProps) > 0 { - propstatBody := []*propertyXML{} + propstatBody := []*props.PropertyXML{} for i := range removedProps { - propstatBody = append(propstatBody, s.newPropNS(removedProps[i].Space, removedProps[i].Local, "")) + propstatBody = append(propstatBody, props.NewPropNS(removedProps[i].Space, removedProps[i].Local, "")) } - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 204 No Content", Prop: propstatBody, }) @@ -364,7 +357,7 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N func (s *svc) isBooleanProperty(prop string) bool { // TODO add other properties we know to be boolean? - return prop == _propOcFavorite + return prop == net.PropOcFavorite } func (s *svc) as0or1(val string) string { @@ -390,11 +383,11 @@ type Proppatch struct { // remove them, it sets them. Remove bool // Props contains the properties to be set or removed. - Props []propertyXML + Props []props.PropertyXML } // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) -type proppatchProps []propertyXML +type proppatchProps []props.PropertyXML // UnmarshalXML appends the property names and values enclosed within start // to ps. @@ -407,7 +400,7 @@ type proppatchProps []propertyXML func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { lang := xmlLang(start, "") for { - t, err := next(d) + t, err := props.Next(d) if err != nil { return err } @@ -418,7 +411,7 @@ func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) e } return nil case xml.StartElement: - p := propertyXML{} + p := props.PropertyXML{} err = d.DecodeElement(&p, &elem) if err != nil { return err @@ -453,17 +446,17 @@ func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { for _, op := range pu.SetRemove { remove := false switch op.XMLName { - case xml.Name{Space: _nsDav, Local: "set"}: + case xml.Name{Space: net.NsDav, Local: "set"}: // No-op. - case xml.Name{Space: _nsDav, Local: "remove"}: + case xml.Name{Space: net.NsDav, Local: "remove"}: for _, p := range op.Prop { if len(p.InnerXML) > 0 { - return nil, http.StatusBadRequest, errInvalidProppatch + return nil, http.StatusBadRequest, errors.ErrInvalidProppatch } } remove = true default: - return nil, http.StatusBadRequest, errInvalidProppatch + return nil, http.StatusBadRequest, errors.ErrInvalidProppatch } patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) } @@ -480,25 +473,3 @@ func xmlLang(s xml.StartElement, d string) string { } return d } - -// Next returns the next token, if any, in the XML stream of d. -// RFC 4918 requires to ignore comments, processing instructions -// and directives. -// http://www.webdav.org/specs/rfc4918.html#property_values -// http://www.webdav.org/specs/rfc4918.html#xml-extensibility -func next(d *xml.Decoder) (xml.Token, error) { - for { - t, err := d.Token() - if err != nil { - return t, err - } - switch t.(type) { - case xml.Comment, xml.Directive, xml.ProcInst: - continue - default: - return t, nil - } - } -} - -var errInvalidProppatch = errors.New("webdav: invalid proppatch") diff --git a/internal/http/services/owncloud/ocdav/props/props.go b/internal/http/services/owncloud/ocdav/props/props.go new file mode 100644 index 00000000000..54142cbd979 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/props/props.go @@ -0,0 +1,99 @@ +// 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 props + +import ( + "bytes" + "encoding/xml" +) + +// PropertyXML represents a single DAV resource property as defined in RFC 4918. +// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties +type PropertyXML struct { + // XMLName is the fully qualified name that identifies this property. + XMLName xml.Name + + // Lang is an optional xml:lang attribute. + Lang string `xml:"xml:lang,attr,omitempty"` + + // InnerXML contains the XML representation of the property value. + // See http://www.webdav.org/specs/rfc4918.html#property_values + // + // Property values of complex type or mixed-content must have fully + // expanded XML namespaces or be self-contained with according + // XML namespace declarations. They must not rely on any XML + // namespace declarations within the scope of the XML document, + // even including the DAV: namespace. + InnerXML []byte `xml:",innerxml"` +} + +func xmlEscaped(val string) []byte { + buf := new(bytes.Buffer) + xml.Escape(buf, []byte(val)) + return buf.Bytes() +} + +// NewPropNS returns a new PropertyXML instance +func NewPropNS(namespace string, local string, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: namespace, Local: local}, + Lang: "", + InnerXML: xmlEscaped(val), + } +} + +// NewProp returns a new PropertyXML instance while xml-escaping the value +// TODO properly use the space +func NewProp(key, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: xmlEscaped(val), + } +} + +// NewPropRaw returns a new PropertyXML instance for the given key/value pair +// TODO properly use the space +func NewPropRaw(key, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: []byte(val), + } +} + +// Next returns the next token, if any, in the XML stream of d. +// RFC 4918 requires to ignore comments, processing instructions +// and directives. +// http://www.webdav.org/specs/rfc4918.html#property_values +// http://www.webdav.org/specs/rfc4918.html#xml-extensibility +func Next(d *xml.Decoder) (xml.Token, error) { + for { + t, err := d.Token() + if err != nil { + return t, err + } + switch t.(type) { + case xml.Comment, xml.Directive, xml.ProcInst: + continue + default: + return t, nil + } + } +} diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 183291ccd54..e64ceb92995 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -25,6 +25,8 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" @@ -90,7 +92,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() sublog.Debug().Msg("handlePropfindOnToken") - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } @@ -102,7 +104,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s return } - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -114,15 +116,15 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) - propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil) + propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(propRes)); err != nil { sublog.Err(err).Msg("error writing response") diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index f7b0dae4e1a..9dc53e38a89 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -30,6 +30,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rhttp" @@ -41,7 +44,7 @@ import ( ) func sufferMacOSFinder(r *http.Request) bool { - return r.Header.Get(HeaderExpectedEntityLength) != "" + return r.Header.Get(net.HeaderExpectedEntityLength) != "" } func handleMacOSFinder(w http.ResponseWriter, r *http.Request) error { @@ -63,8 +66,8 @@ func handleMacOSFinder(w http.ResponseWriter, r *http.Request) error { */ log := appctx.GetLogger(r.Context()) - content := r.Header.Get(HeaderContentLength) - expected := r.Header.Get(HeaderExpectedEntityLength) + content := r.Header.Get(net.HeaderContentLength) + expected := r.Header.Get(net.HeaderExpectedEntityLength) log.Warn().Str("content-length", content).Str("x-expected-entity-length", expected).Msg("Mac OS Finder corner-case detected") // The best mitigation to this problem is to tell users to not use crappy Finder. @@ -102,7 +105,7 @@ func isContentRange(r *http.Request) bool { in unexpected behaviour (cf PEAR::HTTP_WebDAV_Client 1.0.1), we reject all PUT requests with a Content-Range for now. */ - return r.Header.Get(HeaderContentRange) != "" + return r.Header.Get(net.HeaderContentRange) != "" } func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { @@ -112,18 +115,24 @@ func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handlePut(ctx, w, r, makeRelativeReference(space, fn, false), sublog) + s.handlePut(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -153,7 +162,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -164,7 +173,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusConflict) return } - clientETag := r.Header.Get(HeaderIfMatch) + clientETag := r.Header.Get(net.HeaderIfMatch) serverETag := info.Etag if clientETag != "" { if clientETag != serverETag { @@ -176,27 +185,27 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } opaqueMap := map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", Value: []byte(strconv.FormatInt(length, 10)), }, } - if mtime := r.Header.Get(HeaderOCMtime); mtime != "" { - opaqueMap[HeaderOCMtime] = &typespb.OpaqueEntry{ + if mtime := r.Header.Get(net.HeaderOCMtime); mtime != "" { + opaqueMap[net.HeaderOCMtime] = &typespb.OpaqueEntry{ Decoder: "plain", Value: []byte(mtime), } // TODO: find a way to check if the storage really accepted the value - w.Header().Set(HeaderOCMtime, "accepted") + w.Header().Set(net.HeaderOCMtime, "accepted") } // curl -X PUT https://demo.owncloud.com/remote.php/webdav/testcs.bin -u demo:demo -d '123' -v -H 'OC-Checksum: SHA1:40bd001563085fc35165329ea1ff5c5ecbdbbeef' var cparts []string // TUS Upload-Checksum header takes precedence - if checksum := r.Header.Get(HeaderUploadChecksum); checksum != "" { + if checksum := r.Header.Get(net.HeaderUploadChecksum); checksum != "" { cparts = strings.SplitN(checksum, " ", 2) if len(cparts) != 2 { log.Debug().Str("upload-checksum", checksum).Msg("invalid Upload-Checksum format, expected '[algorithm] [checksum]'") @@ -204,7 +213,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } // Then try owncloud header - } else if checksum := r.Header.Get(HeaderOCChecksum); checksum != "" { + } else if checksum := r.Header.Get(net.HeaderOCChecksum); checksum != "" { cparts = strings.SplitN(checksum, ":", 2) if len(cparts) != 2 { log.Debug().Str("oc-checksum", checksum).Msg("invalid OC-Checksum format, expected '[algorithm]:[checksum]'") @@ -215,7 +224,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ // we do not check the algorithm here, because it might depend on the storage if len(cparts) == 2 { // Translate into TUS style Upload-Checksum header - opaqueMap[HeaderUploadChecksum] = &typespb.OpaqueEntry{ + opaqueMap[net.HeaderUploadChecksum] = &typespb.OpaqueEntry{ Decoder: "plain", // algorithm is always lowercase, checksum is separated by space Value: []byte(strings.ToLower(cparts[0]) + " " + cparts[1]), @@ -239,15 +248,12 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ switch uRes.Status.Code { case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: "permission denied: you have no permission to upload content", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, "permission denied: you have no permission to upload content", "") + errors.HandleWebdavError(&log, w, b, err) case rpc.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusConflict) default: - HandleErrorStatus(&log, w, uRes.Status) + errors.HandleErrorStatus(&log, w, uRes.Status) } return } @@ -283,11 +289,8 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } if httpRes.StatusCode == errtypes.StatusChecksumMismatch { w.WriteHeader(http.StatusBadRequest) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: "The computed checksum does not match the one received from the client.", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, "The computed checksum does not match the one received from the client.", "") + errors.HandleWebdavError(&log, w, b, err) return } log.Error().Err(err).Msg("PUT request to data server failed") @@ -321,19 +324,19 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } if sRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } newInfo := sRes.Info - w.Header().Add(HeaderContentType, newInfo.MimeType) - w.Header().Set(HeaderETag, newInfo.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) - w.Header().Set(HeaderOCETag, newInfo.Etag) + w.Header().Add(net.HeaderContentType, newInfo.MimeType) + w.Header().Set(net.HeaderETag, newInfo.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) + w.Header().Set(net.HeaderOCETag, newInfo.Etag) t := utils.TSToTime(newInfo.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) // file was new if info == nil { @@ -350,8 +353,14 @@ func (s *svc) handleSpacesPut(w http.ResponseWriter, r *http.Request, spaceID st defer span.End() sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -359,7 +368,7 @@ func (s *svc) handleSpacesPut(w http.ResponseWriter, r *http.Request, spaceID st } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -385,10 +394,10 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, log zerolog.Logg } func getContentLength(w http.ResponseWriter, r *http.Request) (int64, error) { - length, err := strconv.ParseInt(r.Header.Get(HeaderContentLength), 10, 64) + length, err := strconv.ParseInt(r.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { // Fallback to Upload-Length - length, err = strconv.ParseInt(r.Header.Get(HeaderUploadLength), 10, 64) + length, err = strconv.ParseInt(r.Header.Get(net.HeaderUploadLength), 10, 64) if err != nil { return 0, err } diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index d25f7280d06..85feefb7d50 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -26,6 +26,8 @@ import ( rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" ) @@ -118,14 +120,14 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi infos = append(infos, statRes.Info) } - responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace, nil) + responsesXML, err := propfind.MultistatusResponse(ctx, &propfind.XML{Prop: ff.Prop}, infos, s.c.PublicURL, namespace, nil) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(responsesXML)); err != nil { log.Err(err).Msg("error writing response") @@ -141,7 +143,7 @@ type report struct { type reportSearchFiles struct { XMLName xml.Name `xml:"search-files"` Lang string `xml:"xml:lang,attr,omitempty"` - Prop propfindProps `xml:"DAV: prop"` + Prop propfind.Props `xml:"DAV: prop"` Search reportSearchFilesSearch `xml:"search"` } type reportSearchFilesSearch struct { @@ -153,7 +155,7 @@ type reportSearchFilesSearch struct { type reportFilterFiles struct { XMLName xml.Name `xml:"filter-files"` Lang string `xml:"xml:lang,attr,omitempty"` - Prop propfindProps `xml:"DAV: prop"` + Prop propfind.Props `xml:"DAV: prop"` Rules reportFilterFilesRules `xml:"filter-rules"` } diff --git a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go new file mode 100644 index 00000000000..3be319bec29 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go @@ -0,0 +1,153 @@ +// Copyright 2018-2022 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 spacelookup + +import ( + "context" + "fmt" + "strconv" + "strings" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/utils" +) + +// LookUpStorageSpaceForPath returns: +// th storage spaces responsible for a path +// the status and error for the lookup +func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte(path), + }, + "unique": { + Decoder: "plain", + Value: []byte(strconv.FormatBool(true)), + }, + }, + }, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + switch len(lSSRes.StorageSpaces) { + case 0: + return nil, status.NewNotFound(ctx, "no space found"), nil + case 1: + return lSSRes.StorageSpaces[0], lSSRes.Status, nil + } + + return nil, status.NewInternal(ctx, "too many spaces returned"), nil +} + +// LookUpStorageSpacesForPathWithChildren returns: +// the list of storage spaces responsible for a path +// the status and error for the lookup +func LookUpStorageSpacesForPathWithChildren(ctx context.Context, client gateway.GatewayAPIClient, path string) ([]*storageProvider.StorageSpace, *rpc.Status, error) { + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": {Decoder: "plain", Value: []byte(path)}, + "withChildMounts": {Decoder: "plain", Value: []byte("true")}, + }}, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + + return lSSRes.StorageSpaces, lSSRes.Status, nil +} + +// LookUpStorageSpaceByID find a space by ID +func LookUpStorageSpaceByID(ctx context.Context, client gateway.GatewayAPIClient, spaceID string) (*storageProvider.StorageSpace, *rpc.Status, error) { + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{}, + Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ + { + Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, + Term: &storageProvider.ListStorageSpacesRequest_Filter_Id{ + Id: &storageProvider.StorageSpaceId{ + OpaqueId: spaceID, + }, + }, + }, + }, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + + switch len(lSSRes.StorageSpaces) { + case 0: + return nil, &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, nil // since the caller only expects a single space return not found status + case 1: + return lSSRes.StorageSpaces[0], lSSRes.Status, nil + default: + return nil, nil, fmt.Errorf("unexpected number of spaces %d", len(lSSRes.StorageSpaces)) + } +} + +// LookUpStorageSpaceReference find a space by id and returns a relative reference +func LookUpStorageSpaceReference(ctx context.Context, client gateway.GatewayAPIClient, spaceID string, relativePath string, spacesDavRequest bool) (*storageProvider.Reference, *rpc.Status, error) { + space, status, err := LookUpStorageSpaceByID(ctx, client, spaceID) + if space == nil { + return nil, status, err + } + return MakeRelativeReference(space, relativePath, spacesDavRequest), status, err +} + +// MakeRelativeReference returns a relative reference for the given space and path +func MakeRelativeReference(space *storageProvider.StorageSpace, relativePath string, spacesDavRequest bool) *storageProvider.Reference { + if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { + return nil // not mounted + } + spacePath := string(space.Opaque.Map["path"].Value) + relativeSpacePath := "." + if strings.HasPrefix(relativePath, spacePath) { + relativeSpacePath = utils.MakeRelativePath(strings.TrimPrefix(relativePath, spacePath)) + } else if spacesDavRequest { + relativeSpacePath = utils.MakeRelativePath(relativePath) + } + return &storageProvider.Reference{ + ResourceId: space.Root, + Path: relativeSpacePath, + } +} diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index 2f6276bcfb1..e633dc84771 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -19,19 +19,12 @@ package ocdav import ( - "context" - "fmt" "net/http" "path" - "strconv" - "strings" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" - "github.com/cs3org/reva/pkg/utils" ) // SpacesHandler handles trashbin requests @@ -50,6 +43,7 @@ func (h *SpacesHandler) init(c *Config) error { // Handler handles requests func (h *SpacesHandler) Handler(s *svc) http.Handler { + config := s.Config() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // ctx := r.Context() // log := appctx.GetLogger(ctx) @@ -70,7 +64,10 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { switch r.Method { case MethodPropfind: - s.handleSpacesPropfind(w, r, spaceID) + p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) { + return pool.GetGatewayServiceClient(config.GatewaySvc) + }) + p.HandleSpacesPropfind(w, r, spaceID) case MethodProppatch: s.handleSpacesProppatch(w, r, spaceID) case MethodLock: @@ -102,136 +99,3 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { } }) } - -// lookUpStorageSpacesForPath returns: -// th storage spaces responsible for a path -// the status and error for the lookup -func (s *svc) lookUpStorageSpaceForPath(ctx context.Context, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // TODO add filter to only fetch spaces changed in the last 30 sec? - // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? - // TODO use ListContainerStream to listen for changes - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{ - Map: map[string]*typesv1beta1.OpaqueEntry{ - "path": { - Decoder: "plain", - Value: []byte(path), - }, - "unique": { - Decoder: "plain", - Value: []byte(strconv.FormatBool(true)), - }, - }, - }, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - switch len(lSSRes.StorageSpaces) { - case 0: - return nil, status.NewNotFound(ctx, "no space found"), nil - case 1: - return lSSRes.StorageSpaces[0], lSSRes.Status, nil - } - - return nil, status.NewInternal(ctx, "too many spaces returned"), nil -} - -// lookUpStorageSpacesForPathWithChildren returns: -// the list of storage spaces responsible for a path -// the status and error for the lookup -func (s *svc) lookUpStorageSpacesForPathWithChildren(ctx context.Context, path string) ([]*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // TODO add filter to only fetch spaces changed in the last 30 sec? - // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? - // TODO use ListContainerStream to listen for changes - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{ - Map: map[string]*typesv1beta1.OpaqueEntry{ - "path": {Decoder: "plain", Value: []byte(path)}, - "withChildMounts": {Decoder: "plain", Value: []byte("true")}, - }}, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - - return lSSRes.StorageSpaces, lSSRes.Status, nil -} -func (s *svc) lookUpStorageSpaceByID(ctx context.Context, spaceID string) (*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{}, - Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ - { - Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, - Term: &storageProvider.ListStorageSpacesRequest_Filter_Id{ - Id: &storageProvider.StorageSpaceId{ - OpaqueId: spaceID, - }, - }, - }, - }, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - - switch len(lSSRes.StorageSpaces) { - case 0: - return nil, &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, nil // since the caller only expects a single space return not found status - case 1: - return lSSRes.StorageSpaces[0], lSSRes.Status, nil - default: - return nil, nil, fmt.Errorf("unexpected number of spaces %d", len(lSSRes.StorageSpaces)) - } -} -func (s *svc) lookUpStorageSpaceReference(ctx context.Context, spaceID string, relativePath string, spacesDavRequest bool) (*storageProvider.Reference, *rpc.Status, error) { - space, status, err := s.lookUpStorageSpaceByID(ctx, spaceID) - if space == nil { - return nil, status, err - } - return makeRelativeReference(space, relativePath, spacesDavRequest), status, err -} - -func makeRelativeReference(space *storageProvider.StorageSpace, relativePath string, spacesDavRequest bool) *storageProvider.Reference { - if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { - return nil // not mounted - } - spacePath := string(space.Opaque.Map["path"].Value) - relativeSpacePath := "." - if strings.HasPrefix(relativePath, spacePath) { - relativeSpacePath = utils.MakeRelativePath(strings.TrimPrefix(relativePath, spacePath)) - } else if spacesDavRequest { - relativeSpacePath = utils.MakeRelativePath(relativePath) - } - return &storageProvider.Reference{ - ResourceId: space.Root, - Path: relativeSpacePath, - } -} diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 1e3787ecd81..dd58d7b22a3 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -29,6 +29,11 @@ import ( "strings" "time" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils/resourceid" @@ -80,9 +85,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { if u.Username != username { log.Debug().Str("username", username).Interface("user", u).Msg("trying to read another users trash") // listing other users trash is forbidden, no auth will change that - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - }) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) @@ -123,7 +126,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { return } if getHomeRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(log, w, getHomeRes.Status) + errors.HandleErrorStatus(log, w, getHomeRes.Status) return } basePath = getHomeRes.Path @@ -135,9 +138,9 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { } if key != "" && r.Method == MethodMove { // find path in url relative to trash base - trashBase := ctx.Value(ctxKeyBaseURI).(string) + trashBase := ctx.Value(net.CtxKeyBaseURI).(string) baseURI := path.Join(path.Dir(trashBase), "files", username) - ctx = context.WithValue(ctx, ctxKeyBaseURI, baseURI) + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) r = r.WithContext(ctx) // TODO make request.php optional in destination header @@ -167,12 +170,18 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s ctx, span := rtrace.Provider.Tracer("trash-bin").Start(r.Context(), "list_trashbin") defer span.End() - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } sublog := appctx.GetLogger(ctx).With().Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } // see https://tools.ietf.org/html/rfc4918#section-9.1 if depth != "0" && depth != "1" && depth != "infinity" { @@ -188,8 +197,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -199,7 +208,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -216,17 +225,17 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } - ref := makeRelativeReference(space, basePath, false) + ref := spacelookup.MakeRelativeReference(space, basePath, false) // ask gateway for recycle items getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) @@ -238,7 +247,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if getRecycleRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, getRecycleRes.Status) + errors.HandleErrorStatus(&sublog, w, getRecycleRes.Status) return } @@ -265,7 +274,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if getRecycleRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, getRecycleRes.Status) + errors.HandleErrorStatus(&sublog, w, getRecycleRes.Status) return } items = append(items, getRecycleRes.RecycleItems...) @@ -293,8 +302,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -303,25 +312,25 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } } -func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, items []*provider.RecycleItem) (string, error) { - responses := make([]*responseXML, 0, len(items)+1) +func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, items []*provider.RecycleItem) (string, error) { + responses := make([]*propfind.ResponseXML, 0, len(items)+1) // add trashbin dir . entry - responses = append(responses, &responseXML{ - Href: encodePath(ctx.Value(ctxKeyBaseURI).(string) + "/"), // url encode response.Href TODO - Propstat: []propstatXML{ + responses = append(responses, &propfind.ResponseXML{ + Href: net.EncodePath(ctx.Value(net.CtxKeyBaseURI).(string) + "/"), // url encode response.Href TODO + Propstat: []propfind.PropstatXML{ { Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{ - s.newPropRaw("d:resourcetype", ""), + Prop: []*props.PropertyXML{ + props.NewPropRaw("d:resourcetype", ""), }, }, { Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{ - s.newProp("oc:trashbin-original-filename", ""), - s.newProp("oc:trashbin-original-location", ""), - s.newProp("oc:trashbin-delete-datetime", ""), - s.newProp("d:getcontentlength", ""), + Prop: []*props.PropertyXML{ + props.NewProp("oc:trashbin-original-filename", ""), + props.NewProp("oc:trashbin-original-location", ""), + props.NewProp("oc:trashbin-delete-datetime", ""), + props.NewProp("d:getcontentlength", ""), }, }, }, @@ -348,17 +357,17 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *us // itemToPropResponse needs to create a listing that contains a key and destination // the key is the name of an entry in the trash listing // for now we need to limit trash to the users home, so we can expect all trash keys to have the home storage as the opaque id -func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, item *provider.RecycleItem) (*responseXML, error) { +func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, item *provider.RecycleItem) (*propfind.ResponseXML, error) { - baseURI := ctx.Value(ctxKeyBaseURI).(string) + baseURI := ctx.Value(net.CtxKeyBaseURI).(string) ref := path.Join(baseURI, u.Username, item.Key) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" } - response := responseXML{ - Href: encodePath(ref), // url encode response.Href - Propstat: []propstatXML{}, + response := propfind.ResponseXML{ + Href: net.EncodePath(ref), // url encode response.Href + Propstat: []propfind.PropstatXML{}, } // TODO(jfd): if the path we list here is taken from the ListRecycle request we rely on the gateway to prefix it with the mount point @@ -369,86 +378,86 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use // when allprops has been requested if pf.Allprop != nil { // return all known properties - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, }) // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newPropRaw("d:resourcetype", "")) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewPropRaw("d:resourcetype", "")) // TODO(jfd): decide if we can and want to list oc:size for folders } else { response.Propstat[0].Prop = append(response.Propstat[0].Prop, - s.newProp("d:resourcetype", ""), - s.newProp("d:getcontentlength", fmt.Sprintf("%d", item.Size)), + props.NewProp("d:resourcetype", ""), + props.NewProp("d:getcontentlength", fmt.Sprintf("%d", item.Size)), ) } } else { // otherwise return only the requested properties - propstatOK := propstatXML{ + propstatOK := propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } - propstatNotFound := propstatXML{ + propstatNotFound := propfind.PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } size := fmt.Sprintf("%d", item.Size) for i := range pf.Prop { switch pf.Prop[i].Space { - case _nsOwncloud: + case net.NsOwncloud: switch pf.Prop[i].Local { case "oc:size": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) } case "trashbin-original-filename": // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) case "trashbin-delete-datetime": - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) case "trashbin-delete-timestamp": - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } - case _nsDav: + case net.NsDav: switch pf.Prop[i].Local { case "getcontentlength": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } case "resourcetype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", "httpd/unix-directory")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", "httpd/unix-directory")) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } default: // TODO (jfd) lookup shortname for unknown namespaces? - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp(pf.Prop[i].Space+":"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp(pf.Prop[i].Space+":"+pf.Prop[i].Local, "")) } } response.Propstat = append(response.Propstat, propstatOK, propstatNotFound) @@ -463,7 +472,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc sublog := appctx.GetLogger(ctx).With().Logger() - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) overwrite = strings.ToUpper(overwrite) if overwrite == "" { @@ -481,17 +490,17 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc w.WriteHeader(http.StatusInternalServerError) return } - space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, dst) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dst) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } - dstRef := makeRelativeReference(space, dst, false) + dstRef := spacelookup.MakeRelativeReference(space, dst, false) dstStatReq := &provider.StatRequest{ Ref: dstRef, @@ -505,7 +514,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, dstStatRes.Status) + errors.HandleErrorStatus(&sublog, w, dstStatRes.Status) return } @@ -513,7 +522,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // restore location exists, and if it doesn't returns a conflict error code. if dstStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND && isNested(dst) { parentStatReq := &provider.StatRequest{ - Ref: makeRelativeReference(space, filepath.Dir(dst), false), + Ref: spacelookup.MakeRelativeReference(space, filepath.Dir(dst), false), } parentStatResponse, err := client.Stat(ctx, parentStatReq) @@ -524,7 +533,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if parentStatResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, &rpc.Status{Code: rpc.Code_CODE_FAILED_PRECONDITION}) + errors.HandleErrorStatus(&sublog, w, &rpc.Status{Code: rpc.Code_CODE_FAILED_PRECONDITION}) return } } @@ -536,12 +545,12 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc if overwrite != "T" { sublog.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.9.4 - b, err := Marshal(exception{ - code: SabredavPreconditionFailed, - message: "The destination node already exists, and the overwrite header is set to false", - header: HeaderOverwrite, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal( + errors.SabredavPreconditionFailed, + "The destination node already exists, and the overwrite header is set to false", + net.HeaderOverwrite, + ) + errors.HandleWebdavError(&sublog, w, b, err) return } // delete existing tree @@ -554,19 +563,19 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, delRes.Status) + errors.HandleErrorStatus(&sublog, w, delRes.Status) return } } - sourceSpace, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + sourceSpace, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } req := &provider.RestoreRecycleItemRequest{ @@ -574,7 +583,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // this means we can only undelete on the same storage, not to a different folder // use the key which is prefixed with the StoragePath to lookup the correct storage ... // TODO currently limited to the home storage - Ref: makeRelativeReference(sourceSpace, basePath, false), + Ref: spacelookup.MakeRelativeReference(sourceSpace, basePath, false), Key: path.Join(key, itemPath), RestoreRef: dstRef, } @@ -589,13 +598,10 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: "Permission denied to restore", - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, "Permission denied to restore", "") + errors.HandleWebdavError(&sublog, w, b, err) } - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } @@ -606,15 +612,15 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc return } if dstStatRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, dstStatRes.Status) + errors.HandleErrorStatus(&sublog, w, dstStatRes.Status) return } info := dstStatRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) w.WriteHeader(successCode) } @@ -635,19 +641,19 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, // set key as opaque id, the storageprovider will use it as the key for the // storage drives PurgeRecycleItem key call - space, status, err := s.lookUpStorageSpaceForPath(ctx, basePath) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } req := &provider.PurgeRecycleRequest{ - Ref: makeRelativeReference(space, basePath, false), + Ref: spacelookup.MakeRelativeReference(space, basePath, false), Key: path.Join(key, itemPath), } @@ -664,11 +670,8 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, sublog.Debug().Str("path", basePath).Str("key", key).Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusConflict) m := fmt.Sprintf("path %s not found", basePath) - b, err := Marshal(exception{ - code: SabredavConflict, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavConflict, m, "") + errors.HandleWebdavError(&sublog, w, b, err) case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) var m string @@ -677,13 +680,10 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, } else { m = "Permission denied to delete" } - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&sublog, w, b, err) default: - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index f5ddb67ec8c..7d48af06d02 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -31,6 +31,9 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" @@ -46,7 +49,7 @@ func (s *svc) handlePathTusPost(w http.ResponseWriter, r *http.Request, ns strin defer span.End() // read filename from metadata - meta := tusd.ParseMetadataHeader(r.Header.Get(HeaderUploadMetadata)) + meta := tusd.ParseMetadataHeader(r.Header.Get(net.HeaderUploadMetadata)) for _, r := range nameRules { if !r.Test(meta["filename"]) { w.WriteHeader(http.StatusPreconditionFailed) @@ -72,22 +75,28 @@ func (s *svc) handleSpacesTusPost(w http.ResponseWriter, r *http.Request, spaceI defer span.End() // read filename from metadata - meta := tusd.ParseMetadataHeader(r.Header.Get(HeaderUploadMetadata)) + meta := tusd.ParseMetadataHeader(r.Header.Get(net.HeaderUploadMetadata)) if meta["filename"] == "" { w.WriteHeader(http.StatusPreconditionFailed) return } sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Join(r.URL.Path, meta["filename"]), true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, path.Join(r.URL.Path, meta["filename"]), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -95,20 +104,20 @@ func (s *svc) handleSpacesTusPost(w http.ResponseWriter, r *http.Request, spaceI } func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.Request, meta map[string]string, ref *provider.Reference, log zerolog.Logger) { - w.Header().Add(HeaderAccessControlAllowHeaders, strings.Join([]string{HeaderTusResumable, HeaderUploadLength, HeaderUploadMetadata, HeaderIfMatch}, ", ")) - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderLocation}, ", ")) - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Add(net.HeaderAccessControlAllowHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderUploadLength, net.HeaderUploadMetadata, net.HeaderIfMatch}, ", ")) + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderLocation}, ", ")) + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") - w.Header().Set(HeaderTusResumable, "1.0.0") + w.Header().Set(net.HeaderTusResumable, "1.0.0") // Test if the version sent by the client is supported // GET methods are not checked since a browser may visit this URL and does // not include this header. This request is not part of the specification. - if r.Header.Get(HeaderTusResumable) != "1.0.0" { + if r.Header.Get(net.HeaderTusResumable) != "1.0.0" { w.WriteHeader(http.StatusPreconditionFailed) return } - if r.Header.Get(HeaderUploadLength) == "" { + if r.Header.Get(net.HeaderUploadLength) == "" { w.WriteHeader(http.StatusPreconditionFailed) return } @@ -137,7 +146,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -149,7 +158,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } if info != nil { - clientETag := r.Header.Get(HeaderIfMatch) + clientETag := r.Header.Get(net.HeaderIfMatch) serverETag := info.Etag if clientETag != "" { if clientETag != serverETag { @@ -161,15 +170,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } opaqueMap := map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", - Value: []byte(r.Header.Get(HeaderUploadLength)), + Value: []byte(r.Header.Get(net.HeaderUploadLength)), }, } mtime := meta["mtime"] if mtime != "" { - opaqueMap[HeaderOCMtime] = &typespb.OpaqueEntry{ + opaqueMap[net.HeaderOCMtime] = &typespb.OpaqueEntry{ Decoder: "plain", Value: []byte(mtime), } @@ -195,7 +204,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusPreconditionFailed) return } - HandleErrorStatus(&log, w, uRes.Status) + errors.HandleErrorStatus(&log, w, uRes.Status) return } @@ -215,13 +224,13 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. ep += token } - w.Header().Set(HeaderLocation, ep) + w.Header().Set(net.HeaderLocation, ep) // for creation-with-upload extension forward bytes to dataprovider // TODO check this really streams - if r.Header.Get(HeaderContentType) == "application/offset+octet-stream" { + if r.Header.Get(net.HeaderContentType) == "application/offset+octet-stream" { - length, err := strconv.ParseInt(r.Header.Get(HeaderContentLength), 10, 64) + length, err := strconv.ParseInt(r.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { log.Debug().Err(err).Msg("wrong request") w.WriteHeader(http.StatusBadRequest) @@ -237,14 +246,14 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } - httpReq.Header.Set(HeaderContentType, r.Header.Get(HeaderContentType)) - httpReq.Header.Set(HeaderContentLength, r.Header.Get(HeaderContentLength)) - if r.Header.Get(HeaderUploadOffset) != "" { - httpReq.Header.Set(HeaderUploadOffset, r.Header.Get(HeaderUploadOffset)) + httpReq.Header.Set(net.HeaderContentType, r.Header.Get(net.HeaderContentType)) + httpReq.Header.Set(net.HeaderContentLength, r.Header.Get(net.HeaderContentLength)) + if r.Header.Get(net.HeaderUploadOffset) != "" { + httpReq.Header.Set(net.HeaderUploadOffset, r.Header.Get(net.HeaderUploadOffset)) } else { - httpReq.Header.Set(HeaderUploadOffset, "0") + httpReq.Header.Set(net.HeaderUploadOffset, "0") } - httpReq.Header.Set(HeaderTusResumable, r.Header.Get(HeaderTusResumable)) + httpReq.Header.Set(net.HeaderTusResumable, r.Header.Get(net.HeaderTusResumable)) httpRes, err = s.client.Do(httpReq) if err != nil { @@ -254,16 +263,16 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } defer httpRes.Body.Close() - w.Header().Set(HeaderUploadOffset, httpRes.Header.Get(HeaderUploadOffset)) - w.Header().Set(HeaderTusResumable, httpRes.Header.Get(HeaderTusResumable)) - w.Header().Set(HeaderTusUploadExpires, httpRes.Header.Get(HeaderTusUploadExpires)) + w.Header().Set(net.HeaderUploadOffset, httpRes.Header.Get(net.HeaderUploadOffset)) + w.Header().Set(net.HeaderTusResumable, httpRes.Header.Get(net.HeaderTusResumable)) + w.Header().Set(net.HeaderTusUploadExpires, httpRes.Header.Get(net.HeaderTusUploadExpires)) if httpRes.StatusCode != http.StatusNoContent { w.WriteHeader(httpRes.StatusCode) return } // check if upload was fully completed - if length == 0 || httpRes.Header.Get(HeaderUploadOffset) == r.Header.Get(HeaderUploadLength) { + if length == 0 || httpRes.Header.Get(net.HeaderUploadOffset) == r.Header.Get(net.HeaderUploadLength) { // get uploaded file metadata sRes, err := client.Stat(ctx, sReq) @@ -283,7 +292,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -293,9 +302,9 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusInternalServerError) return } - if httpRes != nil && httpRes.Header != nil && httpRes.Header.Get(HeaderOCMtime) != "" { + if httpRes != nil && httpRes.Header != nil && httpRes.Header.Get(net.HeaderOCMtime) != "" { // set the "accepted" value if returned in the upload response headers - w.Header().Set(HeaderOCMtime, httpRes.Header.Get(HeaderOCMtime)) + w.Header().Set(net.HeaderOCMtime, httpRes.Header.Get(net.HeaderOCMtime)) } // get WebDav permissions for file @@ -307,7 +316,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. isPublic = ls != nil } } - isShared := !isCurrentUserOwner(ctx, info.Owner) + isShared := !net.IsCurrentUserOwner(ctx, info.Owner) role := conversions.RoleFromResourcePermissions(info.PermissionSet) permissions := role.WebDAVPermissions( info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, @@ -316,15 +325,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. isPublic, ) - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCPermissions, permissions) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCPermissions, permissions) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) } } diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index f56bba6e480..ebe609a925d 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -23,6 +23,9 @@ import ( "net/http" "path" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils/resourceid" @@ -54,8 +57,8 @@ func (h *VersionsHandler) Handler(s *svc, rid *provider.ResourceId) http.Handler } // baseURI is encoded as part of the response payload in href field - baseURI := path.Join(ctx.Value(ctxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) - ctx = context.WithValue(ctx, ctxKeyBaseURI, baseURI) + baseURI := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) r = r.WithContext(ctx) var key string @@ -86,7 +89,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, sublog := appctx.GetLogger(ctx).With().Interface("resourceid", rid).Logger() - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -110,14 +113,11 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED || res.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "Resource not found", - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "Resource not found", "") + errors.HandleWebdavError(&sublog, w, b, err) return } - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } @@ -130,7 +130,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, return } if lvRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, lvRes.Status) + errors.HandleErrorStatus(&sublog, w, lvRes.Status) return } @@ -165,14 +165,14 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, infos = append(infos, vi) } - propRes, err := s.multistatusResponse(ctx, &pf, infos, "", nil) + propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, "", nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -207,7 +207,7 @@ func (h *VersionsHandler) doRestore(w http.ResponseWriter, r *http.Request, s *s return } if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } w.WriteHeader(http.StatusNoContent) diff --git a/internal/http/services/owncloud/ocdav/webdav.go b/internal/http/services/owncloud/ocdav/webdav.go index abd984b9fff..fcb24b6a175 100644 --- a/internal/http/services/owncloud/ocdav/webdav.go +++ b/internal/http/services/owncloud/ocdav/webdav.go @@ -23,7 +23,10 @@ import ( "net/http" "path" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) // Common Webdav methods. @@ -40,45 +43,6 @@ const ( MethodReport = "REPORT" ) -// Common HTTP headers. -const ( - HeaderAcceptRanges = "Accept-Ranges" - HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" - HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" - HeaderContentDisposistion = "Content-Disposition" - HeaderContentLength = "Content-Length" - HeaderContentRange = "Content-Range" - HeaderContentType = "Content-Type" - HeaderETag = "ETag" - HeaderLastModified = "Last-Modified" - HeaderLocation = "Location" - HeaderRange = "Range" - HeaderIfMatch = "If-Match" -) - -// Non standard HTTP headers. -const ( - HeaderOCFileID = "OC-FileId" - HeaderOCETag = "OC-ETag" - HeaderOCChecksum = "OC-Checksum" - HeaderOCPermissions = "OC-Perm" - HeaderDepth = "Depth" - HeaderDav = "DAV" - HeaderTusResumable = "Tus-Resumable" - HeaderTusVersion = "Tus-Version" - HeaderTusExtension = "Tus-Extension" - HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" - HeaderTusUploadExpires = "Upload-Expires" - HeaderDestination = "Destination" - HeaderOverwrite = "Overwrite" - HeaderUploadChecksum = "Upload-Checksum" - HeaderUploadLength = "Upload-Length" - HeaderUploadMetadata = "Upload-Metadata" - HeaderUploadOffset = "Upload-Offset" - HeaderOCMtime = "X-OC-Mtime" - HeaderExpectedEntityLength = "X-Expected-Entity-Length" -) - // WebDavHandler implements a dav endpoint type WebDavHandler struct { namespace string @@ -93,20 +57,21 @@ func (h *WebDavHandler) init(ns string, useLoggedInUserNS bool) error { // Handler handles requests func (h *WebDavHandler) Handler(s *svc) http.Handler { + config := s.Config() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ns, newPath, err := s.ApplyLayout(r.Context(), h.namespace, h.useLoggedInUserNS, r.URL.Path) if err != nil { w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: fmt.Sprintf("could not get storage for %s", r.URL.Path), - }) - HandleWebdavError(appctx.GetLogger(r.Context()), w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, fmt.Sprintf("could not get storage for %s", r.URL.Path), "") + errors.HandleWebdavError(appctx.GetLogger(r.Context()), w, b, err) } r.URL.Path = newPath switch r.Method { case MethodPropfind: - s.handlePathPropfind(w, r, ns) + p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) { + return pool.GetGatewayServiceClient(config.GatewaySvc) + }) + p.HandlePathPropfind(w, r, ns) case MethodLock: s.handleLock(w, r, ns) case MethodUnlock: diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 5bcf066cdac..8a0214cd18f 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -26,7 +26,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" @@ -126,7 +126,7 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { return } if res.Status.Code != rpc.Code_CODE_OK { - ocdav.HandleErrorStatus(sublog, w, res.Status) + errors.HandleErrorStatus(sublog, w, res.Status) return } @@ -152,7 +152,7 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { } if getQuotaRes.Status.Code != rpc.Code_CODE_OK { - ocdav.HandleErrorStatus(sublog, w, getQuotaRes.Status) + errors.HandleErrorStatus(sublog, w, getQuotaRes.Status) return } total = getQuotaRes.TotalBytes