Skip to content

Commit 68e00dc

Browse files
committed
allow listing directory trash items by key
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
1 parent 774910f commit 68e00dc

File tree

6 files changed

+135
-66
lines changed

6 files changed

+135
-66
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Bugfix: Allow listing directory trash items by key
2+
3+
The storageprovider now passes on the key without inventing a `/` as the relative path when it was not present at the end of the key. This allows differentiating requests that want to get the trash item of a folder itself (where the relative path is empty) or listing the children of a folder in the trash (where the relative path at least starts with a `/`).
4+
5+
We also fixed the `/dav/spaces` endpoint to not invent a `/` at the end of URLs to allow clients to actually make these different requests.
6+
7+
As a byproduct we now return the size of trashed items.
8+
9+
https://github.com/cs3org/reva/pull/4818

internal/grpc/services/storageprovider/storageprovider.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path"
2828
"sort"
2929
"strconv"
30+
"strings"
3031
"time"
3132

3233
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
@@ -880,8 +881,11 @@ func (s *Service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p
880881
ctx := ss.Context()
881882
log := appctx.GetLogger(ctx)
882883

883-
key, itemPath := router.ShiftPath(req.Key)
884-
items, err := s.Storage.ListRecycle(ctx, req.Ref, key, itemPath)
884+
// if no slash is present in the key, do not pass a relative path to the storage
885+
// when a path is passed to the storage, it will list the contents of the directory
886+
key, relativePath := splitKeyAndPath(req.GetKey())
887+
items, err := s.Storage.ListRecycle(ctx, req.Ref, key, relativePath)
888+
885889
if err != nil {
886890
var st *rpc.Status
887891
switch err.(type) {
@@ -924,8 +928,10 @@ func (s *Service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p
924928
}
925929

926930
func (s *Service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) {
927-
key, itemPath := router.ShiftPath(req.Key)
928-
items, err := s.Storage.ListRecycle(ctx, req.Ref, key, itemPath)
931+
// if no slash is present in the key, do not pass a relative path to the storage
932+
// when a path is passed to the storage, it will list the contents of the directory
933+
key, relativePath := splitKeyAndPath(req.GetKey())
934+
items, err := s.Storage.ListRecycle(ctx, req.Ref, key, relativePath)
929935
if err != nil {
930936
var st *rpc.Status
931937
switch err.(type) {
@@ -962,8 +968,8 @@ func (s *Service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR
962968
ctx = ctxpkg.ContextSetLockID(ctx, req.LockId)
963969

964970
// TODO(labkode): CRITICAL: fill recycle info with storage provider.
965-
key, itemPath := router.ShiftPath(req.Key)
966-
err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, itemPath, req.RestoreRef)
971+
key, relativePath := splitKeyAndPath(req.GetKey())
972+
err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, relativePath, req.RestoreRef)
967973

968974
res := &provider.RestoreRecycleItemResponse{
969975
Status: status.NewStatusFromErrType(ctx, "restore recycle item", err),
@@ -980,9 +986,9 @@ func (s *Service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRe
980986
}
981987

982988
// if a key was sent as opaque id purge only that item
983-
key, itemPath := router.ShiftPath(req.Key)
989+
key, relativePath := splitKeyAndPath(req.GetKey())
984990
if key != "" {
985-
if err := s.Storage.PurgeRecycleItem(ctx, req.Ref, key, itemPath); err != nil {
991+
if err := s.Storage.PurgeRecycleItem(ctx, req.Ref, key, relativePath); err != nil {
986992
st := status.NewStatusFromErrType(ctx, "error purging recycle item", err)
987993
appctx.GetLogger(ctx).
988994
Error().
@@ -1313,3 +1319,12 @@ func canLockPublicShare(ctx context.Context) bool {
13131319
psr := utils.ReadPlainFromOpaque(u.Opaque, "public-share-role")
13141320
return psr == "" || psr == conversions.RoleEditor
13151321
}
1322+
1323+
// splitKeyAndPath splits a key into a root and a relative path
1324+
func splitKeyAndPath(key string) (string, string) {
1325+
root, relativePath := router.ShiftPath(key)
1326+
if relativePath == "/" && !strings.HasSuffix(key, "/") {
1327+
relativePath = ""
1328+
}
1329+
return root, relativePath
1330+
}

internal/http/services/owncloud/ocdav/spaces.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package ocdav
2121
import (
2222
"net/http"
2323
"path"
24+
"strings"
2425

2526
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
2627
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config"
@@ -132,8 +133,7 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ
132133
ctx := r.Context()
133134
log := appctx.GetLogger(ctx)
134135

135-
var spaceID string
136-
spaceID, r.URL.Path = router.ShiftPath(r.URL.Path)
136+
spaceID, key := splitSpaceAndKey(r.URL.Path)
137137
if spaceID == "" {
138138
// listing is disabled, no auth will change that
139139
w.WriteHeader(http.StatusMethodNotAllowed)
@@ -146,12 +146,9 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ
146146
return
147147
}
148148

149-
var key string
150-
key, r.URL.Path = router.ShiftPath(r.URL.Path)
151-
152149
switch r.Method {
153150
case MethodPropfind:
154-
trashbinHandler.listTrashbin(w, r, s, &ref, path.Join(_trashbinPath, spaceID), key, r.URL.Path)
151+
trashbinHandler.listTrashbin(w, r, s, &ref, path.Join(_trashbinPath, spaceID), key)
155152
case MethodMove:
156153
if key == "" {
157154
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
@@ -167,15 +164,25 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ
167164
w.WriteHeader(http.StatusBadRequest)
168165
return
169166
}
170-
log.Debug().Str("key", key).Str("path", r.URL.Path).Str("dst", dst).Msg("spaces restore")
167+
log.Debug().Str("key", key).Str("dst", dst).Msg("spaces restore")
171168

172169
dstRef := proto.Clone(&ref).(*provider.Reference)
173170
dstRef.Path = utils.MakeRelativePath(dst)
174171

175-
trashbinHandler.restore(w, r, s, &ref, dstRef, key, r.URL.Path)
172+
trashbinHandler.restore(w, r, s, &ref, dstRef, key)
176173
case http.MethodDelete:
177-
trashbinHandler.delete(w, r, s, &ref, key, r.URL.Path)
174+
trashbinHandler.delete(w, r, s, &ref, key)
178175
default:
179176
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
180177
}
181178
}
179+
180+
func splitSpaceAndKey(p string) (space, key string) {
181+
p = strings.TrimPrefix(p, "/")
182+
parts := strings.SplitN(p, "/", 2)
183+
space = parts[0]
184+
if len(parts) > 1 {
185+
key = parts[1]
186+
}
187+
return
188+
}

internal/http/services/owncloud/ocdav/trashbin.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,12 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler {
131131
}
132132
ref := spacelookup.MakeRelativeReference(space, ".", false)
133133

134-
// key will be a base64 encoded cs3 path, it uniquely identifies a trash item & storage
135-
var key string
136-
key, r.URL.Path = router.ShiftPath(r.URL.Path)
134+
// key will be a base64 encoded cs3 path, it uniquely identifies a trash item with an opaque id and an optional path
135+
key := r.URL.Path
137136

138137
switch r.Method {
139138
case MethodPropfind:
140-
h.listTrashbin(w, r, s, ref, user.Username, key, r.URL.Path)
139+
h.listTrashbin(w, r, s, ref, user.Username, key)
141140
case MethodMove:
142141
if key == "" {
143142
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
@@ -172,16 +171,16 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler {
172171
dstRef := spacelookup.MakeRelativeReference(space, p, false)
173172

174173
log.Debug().Str("key", key).Str("dst", dst).Msg("restore")
175-
h.restore(w, r, s, ref, dstRef, key, r.URL.Path)
174+
h.restore(w, r, s, ref, dstRef, key)
176175
case http.MethodDelete:
177-
h.delete(w, r, s, ref, key, r.URL.Path)
176+
h.delete(w, r, s, ref, key)
178177
default:
179178
http.Error(w, "501 Not implemented", http.StatusNotImplemented)
180179
}
181180
})
182181
}
183182

184-
func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, refBase, key, itemPath string) {
183+
func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, refBase, key string) {
185184
ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "list_trashbin")
186185
defer span.End()
187186

@@ -214,7 +213,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s
214213
}
215214

216215
if depth == net.DepthZero {
217-
rootHref := path.Join(refBase, key, itemPath)
216+
rootHref := path.Join(refBase, key)
218217
propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.SpaceId, refBase, rootHref, nil, nil)
219218
if err != nil {
220219
sublog.Error().Err(err).Msg("error formatting propfind")
@@ -246,7 +245,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s
246245
return
247246
}
248247
// ask gateway for recycle items
249-
getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)})
248+
getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: key})
250249
if err != nil {
251250
sublog.Error().Err(err).Msg("error calling ListRecycle")
252251
w.WriteHeader(http.StatusInternalServerError)
@@ -304,7 +303,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s
304303
}
305304
}
306305

307-
rootHref := path.Join(refBase, key, itemPath)
306+
rootHref := path.Join(refBase, key)
308307
propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.SpaceId, refBase, rootHref, &pf, items)
309308
if err != nil {
310309
sublog.Error().Err(err).Msg("error formatting propfind")
@@ -480,7 +479,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI
480479
return &response, nil
481480
}
482481

483-
func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, ref, dst *provider.Reference, key, itemPath string) {
482+
func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, ref, dst *provider.Reference, key string) {
484483
ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "restore")
485484
defer span.End()
486485

@@ -566,7 +565,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc
566565

567566
req := &provider.RestoreRecycleItemRequest{
568567
Ref: ref,
569-
Key: path.Join(key, itemPath),
568+
Key: key,
570569
RestoreRef: dst,
571570
}
572571

@@ -608,16 +607,15 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc
608607
}
609608

610609
// delete has only a key
611-
func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, key, itemPath string) {
610+
func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, key string) {
612611
ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "erase")
613612
defer span.End()
614613

615-
sublog := appctx.GetLogger(ctx).With().Interface("reference", ref).Str("key", key).Str("item_path", itemPath).Logger()
614+
sublog := appctx.GetLogger(ctx).With().Interface("reference", ref).Str("key", key).Logger()
616615

617-
trashPath := path.Join(key, itemPath)
618616
req := &provider.PurgeRecycleRequest{
619617
Ref: ref,
620-
Key: trashPath,
618+
Key: key,
621619
}
622620

623621
client, err := s.gatewaySelector.Next()
@@ -638,7 +636,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc,
638636
case rpc.Code_CODE_NOT_FOUND:
639637
sublog.Debug().Interface("status", res.Status).Msg("resource not found")
640638
w.WriteHeader(http.StatusConflict)
641-
m := fmt.Sprintf("path %s not found", trashPath)
639+
m := fmt.Sprintf("key %s not found", key)
642640
b, err := errors.Marshal(http.StatusConflict, m, "", "")
643641
errors.HandleWebdavError(&sublog, w, b, err)
644642
case rpc.Code_CODE_PERMISSION_DENIED:

0 commit comments

Comments
 (0)