From ed9106976f6c94a67e5fe53d796b7e6945754b1b Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 14 Sep 2021 17:00:52 +0200 Subject: [PATCH] first draft for listing favorites --- changelog/unreleased/list-favorites.md | 10 ++ .../http/services/owncloud/ocdav/ocdav.go | 11 +- .../http/services/owncloud/ocdav/propfind.go | 4 +- .../http/services/owncloud/ocdav/proppatch.go | 28 +++++ .../services/owncloud/ocdav/publicfile.go | 2 +- .../http/services/owncloud/ocdav/report.go | 62 ++++++++++- .../services/owncloud/ocdav/report_test.go | 63 +++++++++++ .../http/services/owncloud/ocdav/versions.go | 2 +- pkg/storage/favorite/favorite.go | 67 +++++++++++ pkg/storage/favorite/favorite_test.go | 104 ++++++++++++++++++ .../expected-failures-on-OCIS-storage.md | 6 - .../expected-failures-on-S3NG-storage.md | 6 - 12 files changed, 344 insertions(+), 21 deletions(-) create mode 100644 changelog/unreleased/list-favorites.md create mode 100644 internal/http/services/owncloud/ocdav/report_test.go create mode 100644 pkg/storage/favorite/favorite.go create mode 100644 pkg/storage/favorite/favorite_test.go diff --git a/changelog/unreleased/list-favorites.md b/changelog/unreleased/list-favorites.md new file mode 100644 index 00000000000..b506b13a9ac --- /dev/null +++ b/changelog/unreleased/list-favorites.md @@ -0,0 +1,10 @@ +Bugfix: Fix behavior for foobar (in present tense) + +We've fixed the behavior for foobar, a long-standing annoyance for Reva +users. + +The text in the paragraphs is written in past tense. The last section is a list +of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID, +in case there aren't any issue links) is used as the primary ID. + +https://github.com/cs3org/reva/pull/2071 diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index ff98568842c..c463b320531 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -40,6 +40,7 @@ import ( "github.com/cs3org/reva/pkg/rhttp/global" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage/favorite" "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -110,10 +111,11 @@ func (c *Config) init() { } type svc struct { - c *Config - webDavHandler *WebDavHandler - davHandler *DavHandler - client *http.Client + c *Config + webDavHandler *WebDavHandler + davHandler *DavHandler + favoritesManager favorite.Manager + client *http.Client } // New returns a new ocdav @@ -133,6 +135,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))), rhttp.Insecure(conf.Insecure), ), + favoritesManager: favorite.NewInMemoryManager(), } // initialize handlers and set default configs if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil { diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 0e9c99be888..e95007abc90 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -134,7 +134,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { - propRes, err := s.formatPropfind(ctx, &pf, resourceInfos, namespace) + propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -342,7 +342,7 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { return pf, 0, nil } -func (s *svc) formatPropfind(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { +func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { responses := make([]*responseXML, 0, len(mds)) for i := range mds { res, err := s.mdToPropResponse(ctx, pf, mds[i], ns) diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 2fb3a0f6a39..9c6afbc5104 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -30,6 +30,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "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" @@ -232,6 +233,19 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt HandleErrorStatus(&log, w, res.Status) return nil, nil, false } + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.UnsetFavorite(currentUser.Id, statRes.Info) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } removedProps = append(removedProps, propNameXML) } else { sreq.ArbitraryMetadata.Metadata[key] = value @@ -259,6 +273,20 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt acceptedProps = append(acceptedProps, propNameXML) delete(sreq.ArbitraryMetadata.Metadata, key) + + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.SetFavorite(currentUser.Id, statRes.Info) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } } } // FIXME: in case of error, need to set all properties back to the original state, diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 797f1e4d530..f2315e90cfd 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -186,7 +186,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) - propRes, err := s.formatPropfind(ctx, &pf, infos, ns) + propRes, err := s.multistatusResponse(ctx, &pf, infos, ns) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 5807d5094df..83854c40618 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -24,6 +24,12 @@ import ( "net/http" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +const ( + elementNameSearchFiles = "search-files" + elementNameFilterFiles = "filter-files" ) func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { @@ -42,6 +48,11 @@ func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { return } + if rep.FilterFiles != nil { + s.doFilterFiles(w, r, rep.FilterFiles, ns) + return + } + // TODO(jfd): implement report w.WriteHeader(http.StatusNotImplemented) @@ -59,9 +70,39 @@ func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSe w.WriteHeader(http.StatusNotImplemented) } +func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + if ff.Rules.Favorite { + // List the users favorite resources. + currentUser := ctxpkg.ContextMustGetUser(ctx) + favorites, err := s.favoritesManager.ListFavorites(currentUser.Id) + if err != nil { + log.Error().Err(err).Msg("error getting favorites") + w.WriteHeader(http.StatusInternalServerError) + return + } + + responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, favorites, namespace) + 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.WriteHeader(http.StatusMultiStatus) + if _, err := w.Write([]byte(responsesXML)); err != nil { + log.Err(err).Msg("error writing response") + } + } +} + type report struct { SearchFiles *reportSearchFiles // FilterFiles TODO add this for tag based search + FilterFiles *reportFilterFiles `xml:"filter-files"` } type reportSearchFiles struct { XMLName xml.Name `xml:"search-files"` @@ -75,6 +116,18 @@ type reportSearchFilesSearch struct { Offset int `xml:"offset"` } +type reportFilterFiles struct { + XMLName xml.Name `xml:"filter-files"` + Lang string `xml:"xml:lang,attr,omitempty"` + Prop propfindProps `xml:"DAV: prop"` + Rules reportFilterFilesRules `xml:"filter-rules"` +} + +type reportFilterFilesRules struct { + Favorite bool `xml:"favorite"` + SystemTag int `xml:"systemtag"` +} + func readReport(r io.Reader) (rep *report, status int, err error) { decoder := xml.NewDecoder(r) rep = &report{} @@ -89,13 +142,20 @@ func readReport(r io.Reader) (rep *report, status int, err error) { } if v, ok := t.(xml.StartElement); ok { - if v.Name.Local == "search-files" { + if v.Name.Local == elementNameSearchFiles { var repSF reportSearchFiles err = decoder.DecodeElement(&repSF, &v) if err != nil { return nil, http.StatusBadRequest, err } rep.SearchFiles = &repSF + } else if v.Name.Local == elementNameFilterFiles { + var repFF reportFilterFiles + err = decoder.DecodeElement(&repFF, &v) + if err != nil { + return nil, http.StatusBadRequest, err + } + rep.FilterFiles = &repFF } } } diff --git a/internal/http/services/owncloud/ocdav/report_test.go b/internal/http/services/owncloud/ocdav/report_test.go new file mode 100644 index 00000000000..42b5d64a90e --- /dev/null +++ b/internal/http/services/owncloud/ocdav/report_test.go @@ -0,0 +1,63 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "strings" + "testing" +) + +func TestUnmarshallReportFilterFiles(t *testing.T) { + ffXML := ` + + + + + + + + + + + + + + + + + + 1 + +` + + reader := strings.NewReader(ffXML) + + report, status, err := readReport(reader) + if status != 0 || err != nil { + t.Error("Failed to unmarshal filter-files xml") + } + + if report.FilterFiles == nil { + t.Error("Failed to unmarshal filter-files xml. FilterFiles is nil") + } + + if report.FilterFiles.Rules.Favorite == false { + t.Error("Failed to correctly unmarshal filter-rules. Favorite is expected to be true.") + } +} diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index b9af216c47f..459e995620c 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -164,7 +164,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, infos = append(infos, vi) } - propRes, err := s.formatPropfind(ctx, &pf, infos, "") + propRes, err := s.multistatusResponse(ctx, &pf, infos, "") if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/storage/favorite/favorite.go b/pkg/storage/favorite/favorite.go new file mode 100644 index 00000000000..bf33215f6df --- /dev/null +++ b/pkg/storage/favorite/favorite.go @@ -0,0 +1,67 @@ +// 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 favorite + +import ( + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Manager defines an interface for a favorites manager. +type Manager interface { + // ListFavorites returns all resources that were favorited by a user. + ListFavorites(userID *user.UserId) ([]*provider.ResourceInfo, error) + // SetFavorite marks a resource as favorited by a user. + SetFavorite(userID *user.UserId, id *provider.ResourceInfo) error + // UnsetFavorite unmarks a resource as favorited by a user. + UnsetFavorite(userID *user.UserId, id *provider.ResourceInfo) error +} + +// NewInMemoryManager returns an instance of a favorites manager using an in-memory storage. +func NewInMemoryManager() Manager { + return InMemoryManager{favorites: make(map[string]map[string]*provider.ResourceInfo)} +} + +// InMemoryManager implements the Manager interface to manage favorites using an in-memory storage. +type InMemoryManager struct { + favorites map[string]map[string]*provider.ResourceInfo +} + +func (m InMemoryManager) ListFavorites(userID *user.UserId) ([]*provider.ResourceInfo, error) { + favorites := make([]*provider.ResourceInfo, 0, len(m.favorites[userID.OpaqueId])) + for _, info := range m.favorites[userID.OpaqueId] { + favorites = append(favorites, info) + } + return favorites, nil +} + +func (m InMemoryManager) SetFavorite(userID *user.UserId, info *provider.ResourceInfo) error { + if m.favorites[userID.OpaqueId] == nil { + m.favorites[userID.OpaqueId] = make(map[string]*provider.ResourceInfo) + } + m.favorites[userID.OpaqueId][info.Id.OpaqueId] = info + + return nil +} + +func (m InMemoryManager) UnsetFavorite(userID *user.UserId, info *provider.ResourceInfo) error { + delete(m.favorites[userID.OpaqueId], info.Id.OpaqueId) + + return nil +} diff --git a/pkg/storage/favorite/favorite_test.go b/pkg/storage/favorite/favorite_test.go new file mode 100644 index 00000000000..a569c486649 --- /dev/null +++ b/pkg/storage/favorite/favorite_test.go @@ -0,0 +1,104 @@ +// 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 favorite + +import ( + "testing" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +func TestListFavorite(t *testing.T) { + userOneID := &user.UserId{OpaqueId: "userOne"} + userTwoID := &user.UserId{OpaqueId: "userTwo"} + userThreeID := &user.UserId{OpaqueId: "userThree"} + resourceInfoOne := &provider.ResourceInfo{ + Id: &provider.ResourceId{OpaqueId: "resourceInfoOne"}, + } + resourceInfoTwo := &provider.ResourceInfo{ + Id: &provider.ResourceId{OpaqueId: "resourceInfoTwo"}, + } + sut := NewInMemoryManager() + + favorites, _ := sut.ListFavorites(userOneID) + if len(favorites) != 0 { + t.Error("ListFavorites should not return anything when a user hasn't set a favorite") + } + + _ = sut.SetFavorite(userOneID, resourceInfoOne) + _ = sut.SetFavorite(userTwoID, resourceInfoOne) + _ = sut.SetFavorite(userTwoID, resourceInfoTwo) + + favorites, _ = sut.ListFavorites(userOneID) + if len(favorites) != 1 { + t.Error("ListFavorite should only return the users favorites") + } + + favorites, _ = sut.ListFavorites(userTwoID) + if len(favorites) != 2 { + t.Error("ListFavorite should only return the users favorites") + } + + favorites, _ = sut.ListFavorites(userThreeID) + if len(favorites) != 0 { + t.Error("ListFavorite should only return the users favorites") + } +} + +func TestSetFavorite(t *testing.T) { + userOneID := &user.UserId{OpaqueId: "userOne"} + resourceInfo := &provider.ResourceInfo{ + Id: &provider.ResourceId{OpaqueId: "resourceInfoOne"}, + } + sut := NewInMemoryManager() + + favorites, _ := sut.ListFavorites(userOneID) + lenBefore := len(favorites) + + _ = sut.SetFavorite(userOneID, resourceInfo) + + favorites, _ = sut.ListFavorites(userOneID) + lenAfter := len(favorites) + + if lenAfter-lenBefore != 1 { + t.Errorf("Setting a favorite should add 1 favorite but actually added %d", lenAfter-lenBefore) + } +} + +func TestUnsetFavorite(t *testing.T) { + userOneID := &user.UserId{OpaqueId: "userOne"} + resourceInfo := &provider.ResourceInfo{ + Id: &provider.ResourceId{OpaqueId: "resourceInfoOne"}, + } + sut := NewInMemoryManager() + + _ = sut.SetFavorite(userOneID, resourceInfo) + favorites, _ := sut.ListFavorites(userOneID) + lenBefore := len(favorites) + + _ = sut.UnsetFavorite(userOneID, resourceInfo) + + favorites, _ = sut.ListFavorites(userOneID) + lenAfter := len(favorites) + + if lenAfter-lenBefore != -1 { + t.Errorf("Setting a favorite should remove 1 favorite but actually removed %d", lenAfter-lenBefore) + } +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 6c1def9086b..0fa0c71caf7 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1007,18 +1007,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index b84f331dc50..b0989ce3e83 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -1006,18 +1006,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35)