Skip to content

Commit

Permalink
implement restore for spaces trashbin
Browse files Browse the repository at this point in the history
  • Loading branch information
David Christofas committed Mar 10, 2022
1 parent 9d357cb commit d192df5
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 146 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/spaces-trashbin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Add spaces aware trash-bin API

Added the webdav trash-bin endpoint for spaces.

https://github.com/cs3org/reva/pull/2628
9 changes: 7 additions & 2 deletions internal/http/services/owncloud/ocdav/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string)
defer span.End()

src := path.Join(ns, r.URL.Path)
dst, err := extractDestination(r)

dh := r.Header.Get(net.HeaderDestination)
baseURI := r.Context().Value(net.CtxKeyBaseURI).(string)
dst, err := net.ParseDestination(baseURI, dh)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
Expand Down Expand Up @@ -274,7 +277,9 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s
ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), "spaces_copy")
defer span.End()

dst, err := extractDestination(r)
dh := r.Header.Get(net.HeaderDestination)
baseURI := r.Context().Value(net.CtxKeyBaseURI).(string)
dst, err := net.ParseDestination(baseURI, dh)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
Expand Down
8 changes: 6 additions & 2 deletions internal/http/services/owncloud/ocdav/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string)
defer span.End()

srcPath := path.Join(ns, r.URL.Path)
dstPath, err := extractDestination(r)
dh := r.Header.Get(net.HeaderDestination)
baseURI := r.Context().Value(net.CtxKeyBaseURI).(string)
dstPath, err := net.ParseDestination(baseURI, dh)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
Expand Down Expand Up @@ -98,7 +100,9 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI
ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "spaces_move")
defer span.End()

dst, err := extractDestination(r)
dh := r.Header.Get(net.HeaderDestination)
baseURI := r.Context().Value(net.CtxKeyBaseURI).(string)
dst, err := net.ParseDestination(baseURI, dh)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
Expand Down
34 changes: 32 additions & 2 deletions internal/http/services/owncloud/ocdav/net/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ package net

import (
"fmt"
"net/url"
"regexp"
"strings"

"github.com/pkg/errors"
)

var (
// ErrInvalidHeaderValue defines an error which can occure when trying to parse a header value.
ErrInvalidHeaderValue = errors.New("invalid value")
)

type ctxKey int
Expand Down Expand Up @@ -110,7 +118,7 @@ func ParseDepth(s string) (Depth, error) {
case DepthInfinity.String():
return DepthInfinity, nil
default:
return "", fmt.Errorf("invalid depth: %s", s)
return "", errors.Wrapf(ErrInvalidHeaderValue, "invalid depth: %s", s)
}
}

Expand All @@ -122,7 +130,29 @@ func ParseOverwrite(s string) (bool, error) {
s = "T"
}
if s != "T" && s != "F" {
return false, fmt.Errorf("invalid overwrite: %s", s)
return false, errors.Wrapf(ErrInvalidHeaderValue, "invalid overwrite: %s", s)
}
return s == "T", nil
}

// ParseDestination parses the destination header value defined in https://datatracker.ietf.org/doc/html/rfc4918#section-10.3
// The returned path will be relative to the given baseURI.
func ParseDestination(baseURI, s string) (string, error) {
if s == "" {
return "", errors.Wrap(ErrInvalidHeaderValue, "destination header is empty")
}
dstURL, err := url.ParseRequestURI(s)
if err != nil {
return "", errors.Wrap(ErrInvalidHeaderValue, err.Error())
}

// TODO check if path is on same storage, return 502 on problems, see https://tools.ietf.org/html/rfc4918#section-9.9.4
// TODO make request.php optional in destination header
// Strip the base URI from the destination. The destination might contain redirection prefixes which need to be handled
urlSplit := strings.Split(dstURL.Path, baseURI)
if len(urlSplit) != 2 {
return "", errors.Wrap(ErrInvalidHeaderValue, "destination path does not contain base URI")
}

return urlSplit[1], nil
}
12 changes: 12 additions & 0 deletions internal/http/services/owncloud/ocdav/net/net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,16 @@ var _ = Describe("Net", func() {
Entry("F", "F", true, false),
Entry("f", "f", true, false),
Entry("invalid", "invalid", false, false))

DescribeTable("TestParseDestination",
func(baseURI, v string, expectSuccess bool, expectedValue string) {
parsed, err := net.ParseDestination(baseURI, v)
Expect(err == nil).To(Equal(expectSuccess))
Expect(parsed).To(Equal(expectedValue))
},
Entry("invalid1", "", "", false, ""),
Entry("invalid2", "baseURI", "", false, ""),
Entry("invalid3", "", "/dest/path", false, ""),
Entry("invalid4", "/foo", "/dest/path", false, ""),
Entry("valid", "/foo", "https://example.com/foo/dest/path", true, "/dest/path"))
})
25 changes: 0 additions & 25 deletions internal/http/services/owncloud/ocdav/ocdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package ocdav
import (
"context"
"net/http"
"net/url"
"path"
"strings"
"time"
Expand All @@ -42,13 +41,10 @@ import (
"github.com/cs3org/reva/v2/pkg/storage/favorite/registry"
"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)

var (
errInvalidValue = errors.New("invalid value")

nameRules = [...]nameRule{
nameNotEmpty{},
nameDoesNotContain{chars: "\f\r\n\\"},
Expand Down Expand Up @@ -339,24 +335,3 @@ func addAccessHeaders(w http.ResponseWriter, r *http.Request) {
headers.Set("Strict-Transport-Security", "max-age=63072000")
}
}

func extractDestination(r *http.Request) (string, error) {
dstHeader := r.Header.Get(net.HeaderDestination)
if dstHeader == "" {
return "", errors.Wrap(errInvalidValue, "destination header is empty")
}
dstURL, err := url.ParseRequestURI(dstHeader)
if err != nil {
return "", errors.Wrap(errInvalidValue, err.Error())
}

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)
if len(urlSplit) != 2 {
return "", errors.Wrap(errInvalidValue, "destination path does not contain base URI")
}

return urlSplit[1], nil
}
62 changes: 0 additions & 62 deletions internal/http/services/owncloud/ocdav/ocdav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@
package ocdav

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"

providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
"github.com/cs3org/reva/v2/pkg/utils/resourceid"
)

Expand All @@ -38,63 +33,6 @@ 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(net.HeaderDestination, "https://example.org/remote.php/dav/dst")

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)
}

if destination != expected {
t.Errorf("Extracted destination is not expected, got %s want %s", destination, expected)
}
}

func TestExtractDestinationWithoutHeader(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil)

_, err := extractDestination(request)
if err == nil {
t.Errorf("Expected err to be nil got %s", err)
}

if !errors.Is(err, errInvalidValue) {
t.Errorf("Expected error invalid value, got %s", err)
}
}

func TestExtractDestinationWithInvalidDestination(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil)
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)
}

if !errors.Is(err, errInvalidValue) {
t.Errorf("Expected error invalid value, got %s", err)
}
}

func TestExtractDestinationWithDestinationWrongBaseURI(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil)
request.Header.Set(net.HeaderDestination, "https://example.org/remote.php/dav/dst")

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)
}

if !errors.Is(err, errInvalidValue) {
t.Errorf("Expected error invalid value, got %s", err)
}
}

func TestNameNotEmptyRule(t *testing.T) {
tests := map[string]bool{
"": false,
Expand Down
44 changes: 33 additions & 11 deletions internal/http/services/owncloud/ocdav/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
Expand Down Expand Up @@ -56,23 +57,21 @@ func (h *SpacesHandler) Handler(s *svc, trashbinHandler *TrashbinHandler) http.H
return
}

var p string
p, r.URL.Path = router.ShiftPath(r.URL.Path)

var spaceID string
if p == _trashbinPath {
h.handleSpacesTrashbin(w, r, s, trashbinHandler)
var segment string
segment, r.URL.Path = router.ShiftPath(r.URL.Path)
if segment == "" {
// listing is disabled, no auth will change that
w.WriteHeader(http.StatusMethodNotAllowed)
return
} else {
spaceID = p
}

if spaceID == "" {
// listing is disabled, no auth will change that
w.WriteHeader(http.StatusMethodNotAllowed)
if segment == _trashbinPath {
h.handleSpacesTrashbin(w, r, s, trashbinHandler)
return
}

spaceID := segment

switch r.Method {
case MethodPropfind:
p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) {
Expand Down Expand Up @@ -146,6 +145,9 @@ func (h *SpacesHandler) Handler(s *svc, trashbinHandler *TrashbinHandler) http.H
}

func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Request, s *svc, trashbinHandler *TrashbinHandler) {
ctx := r.Context()
log := appctx.GetLogger(ctx)

var spaceID string
spaceID, r.URL.Path = router.ShiftPath(r.URL.Path)
if spaceID == "" {
Expand All @@ -167,7 +169,27 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ
switch r.Method {
case MethodPropfind:
trashbinHandler.listTrashbin(w, r, s, ref, path.Join(_trashbinPath, spaceID), key, r.URL.Path)
case MethodMove:
if key == "" {
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
break
}
// find path in url relative to trash base
baseURI := ctx.Value(net.CtxKeyBaseURI).(string)
baseURI = path.Join(baseURI, spaceID)

dh := r.Header.Get(net.HeaderDestination)
dst, err := net.ParseDestination(baseURI, dh)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

log.Debug().Str("key", key).Str("path", r.URL.Path).Str("dst", dst).Msg("spaces restore")
trashbinHandler.restore(w, r, s, ref, dst, key, r.URL.Path)
case http.MethodDelete:
trashbinHandler.delete(w, r, s, ref, key, r.URL.Path)
default:
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
}
}
Loading

0 comments on commit d192df5

Please sign in to comment.