Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

WIP: search: add fcc search boilerplate code DO NOT MERGE #35

Open
wants to merge 9 commits into
base: cernbox
Choose a base branch
from
10 changes: 10 additions & 0 deletions internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/mime"
Expand Down Expand Up @@ -938,6 +939,15 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer
}, nil
}

// Ugly hack to enable search
if req.Opaque == nil {
req.Opaque = &typesv1beta1.Opaque{Map: make(map[string]*typesv1beta1.OpaqueEntry)}
}

if req.Opaque.Map["search"] != nil {
s.storage.ListFolder(ctx, newRef, []string{"search", string(req.Opaque.Map["searchString"].GetValue())})
}

mds, err := s.storage.ListFolder(ctx, newRef, req.ArbitraryMetadataKeys)
if err != nil {
var st *rpc.Status
Expand Down
97 changes: 62 additions & 35 deletions internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ package ocdav
import (
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"strings"

rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
)
Expand All @@ -38,14 +40,17 @@ const (
func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
// fn := path.Join(ns, r.URL.Path)

// TODO(salfagem): catch empty request body
rep, status, err := readReport(r.Body)
if err != nil {
log.Error().Err(err).Msg("error reading report")
w.WriteHeader(status)
return
}

log.Info().Msgf("searching in path: %s with pattern: %s", r.URL.Path, rep.SearchFiles.Search.Pattern)

if rep.SearchFiles != nil {
s.doSearchFiles(w, r, rep.SearchFiles)
return
Expand All @@ -56,21 +61,61 @@ func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) {
return
}

// TODO(jfd): implement report

w.WriteHeader(http.StatusNotImplemented)
}

func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSearchFiles) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
_, err := s.getClient()

log.Info().Msgf("search is: %+v", sf)

client, err := s.getClient()
if err != nil {
log.Error().Err(err).Msg("error getting grpc client")
log.Error().Err(err).Msg("search: error getting grpc client")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNotImplemented)

opaqueMap := map[string]*types.OpaqueEntry{
"search": {
Decoder: "plain",
Value: []byte("search"),
},
"searchString": {
Decoder: "plain",
Value: []byte(sf.Search.Pattern),
},
}

// TODO(salfagem): hardcoded path for the time being, enable a list:
ref := &provider.Reference{Path: "/eos/project/a/awesomeproject"}

req := &provider.ListContainerRequest{Opaque: &types.Opaque{
Map: opaqueMap,
}, Ref: ref}

res, err := client.ListContainer(ctx, req)

if err != nil {
log.Error().Err(err).Msg("search: error listing container")
w.WriteHeader(http.StatusInternalServerError)
return
}

log.Debug().Msgf("search: found %d matches", len(res.Infos))

for _, v := range res.Infos {
log.Debug().Msg(v.Path)
}

data := `
<d:multistatus xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:s="http://sabredav.org/ns"><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/New%20Text%20Document.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!420307062</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>New Text Document.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/New%20text%20file.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!166399624</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>New text file.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>5</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/recover.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!493427700</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>recover.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/test_support.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!420300390</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>test_support.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/testing.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!420300167</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>testing.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/testing2.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!420300326</oc:fileid><oc:file-parent>newproject-a!616510</oc:file-parent><oc:name>testing2.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/Denied%20Folder/New%20file.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!437033942</oc:fileid><oc:file-parent>newproject-a!67942728</oc:file-parent><oc:name>New file.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>0</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/remote.php/dav/files/cboxbot/eos/project/a/awesomeproject/test_versions/test.txt</d:href><d:propstat><d:prop><oc:fileid>newproject-a!506354963</oc:fileid><oc:file-parent>newproject-a!73758625</oc:file-parent><oc:name>test.txt</oc:name><d:getlastmodified>2022-12-05T16:12:20Z</d:getlastmodified><d:getcontenttype>text/plain</d:getcontenttype><oc:permissions>SRDNVCKZ</oc:permissions><d:getetag /><d:resourcetype><d:collection /></d:resourcetype><oc:size>9</oc:size></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>
`
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusMultiStatus)
w.Write([]byte(data))
return
}

func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) {
Expand Down Expand Up @@ -148,7 +193,7 @@ type reportSearchFiles struct {
Search reportSearchFilesSearch `xml:"search"`
}
type reportSearchFilesSearch struct {
Pattern string `xml:"search"`
Pattern string `xml:"pattern"`
Limit int `xml:"limit"`
Offset int `xml:"offset"`
}
Expand All @@ -166,34 +211,16 @@ type reportFilterFilesRules struct {
}

func readReport(r io.Reader) (rep *report, status int, err error) {
decoder := xml.NewDecoder(r)
rep = &report{}
for {
t, err := decoder.Token()
if err == io.EOF {
// io.EOF is a successful end
return rep, 0, nil
}
if err != nil {
return nil, http.StatusBadRequest, err
}
content, err := ioutil.ReadAll(r)
if err != nil {
return nil, 0, err
}

if v, ok := t.(xml.StartElement); ok {
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
}
}
s := &reportSearchFiles{Search: reportSearchFilesSearch{}}
err = xml.Unmarshal(content, s)
if err != nil {
return nil, 0, err
}

return &report{SearchFiles: s}, 0, err
}
30 changes: 28 additions & 2 deletions pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -250,9 +251,10 @@ func (c *Client) executeEOS(ctx context.Context, cmdArgs []string, auth eosclien
err = nil
case int(syscall.ENOENT):
err = errtypes.NotFound(errBuf.String())
case int(syscall.EPERM), int(syscall.E2BIG), int(syscall.EINVAL):
case int(syscall.EPERM), int(syscall.E2BIG), int(syscall.EINVAL), int(syscall.EACCES):
// eos reports back error code 1 (EPERM) when ?
// eos reports back error code 7 (E2BIG) when the user is not allowed to read the directory
// eos reports back error code 13 (EACCES) wheb the user is not allowed to read the directory in e.g. eos newfind
// eos reports back error code 22 (EINVAL) when the user is not allowed to enter the instance
err = errtypes.PermissionDenied(errBuf.String())
}
Expand Down Expand Up @@ -715,7 +717,31 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, path st
return c.parseFind(ctx, auth, path, stdout)
}

// Read reads a file from the mgm.
// List the contents of the directory given by path with depth infinity
func (c *Client) SearchDir(ctx context.Context, auth eosclient.Authorization, searchString string, path string) ([]*eosclient.FileInfo, error) {
// TODO(salfagem): path is truncated - i.e. /c/cernbox (not absolute)
args := []string{"find", "--fileinfo", "-name", searchString, path}
log := appctx.GetLogger(ctx)
log.Debug().Msgf("eosbinary search with args: %s", args)
// Safeguard #2 to prevent the search to go to undesired places:
if !strings.HasPrefix(path, "/eos/project/a/awesomeproject") {
log.Debug().Msgf("eosbinary - prefix doesn't match")
return nil, errors.Errorf("eosclient: search path out of bounds fn=%s", path)
}
// For the moment, just file names are supported, no paths (and no parentheses, braquets...)
searchStringMatch, _ := regexp.MatchString(`^[\w\d\s\*\-\.]+$`, searchString)
if !searchStringMatch {
log.Debug().Msgf("eosbinary - searchstring is not valid")
return nil, errors.Errorf("eosclient: ilegal search string: %s", searchString)
}
// TODO: set a timeout for the find in case it goes out of hand
stdout, _, _ := c.executeEOS(ctx, args, auth)
// We can ignore errors and stderr, we're just interested on the EOS output
log.Debug().Msgf("eos find stdout= %s", stdout)
return c.parseFind(ctx, auth, path, stdout)
}

// Read reads a file from the mgm
func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path string) (io.ReadCloser, error) {
rand := "eosread-" + uuid.New().String()
localTarget := fmt.Sprintf("%s/%s", c.opt.CacheDirectory, rand)
Expand Down
1 change: 1 addition & 0 deletions pkg/eosclient/eosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type EOSClient interface {
Remove(ctx context.Context, auth Authorization, path string, noRecycle bool) error
Rename(ctx context.Context, auth Authorization, oldPath, newPath string) error
List(ctx context.Context, auth Authorization, path string) ([]*FileInfo, error)
SearchDir(ctx context.Context, auth Authorization, searchString string, path string) ([]*FileInfo, error)
Read(ctx context.Context, auth Authorization, path string) (io.ReadCloser, error)
Write(ctx context.Context, auth Authorization, path string, stream io.ReadCloser) error
WriteFile(ctx context.Context, auth Authorization, path, source string) error
Expand Down
4 changes: 4 additions & 0 deletions pkg/eosclient/eosgrpc/eosgrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,10 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath s
return mylst, nil
}

func (c *Client) SearchDir(ctx context.Context, auth eosclient.Authorization, searchString string, dpath string) ([]*eosclient.FileInfo, error) {
return nil, errtypes.NotSupported("eosgrpc: search is not implemented")
}

// Read reads a file from the mgm and returns a handle to read it
// This handle could be directly the body of the response or a local tmp file
//
Expand Down
60 changes: 60 additions & 0 deletions pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,11 +1307,65 @@ func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string
}

func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) {
log := appctx.GetLogger(ctx)

p, err := fs.resolve(ctx, ref)

if err != nil {
return nil, errors.Wrap(err, "eosfs: error resolving reference")
}

u, err := getUser(ctx)
if err != nil {
return nil, errors.Wrap(err, "eosfs: no user in ctx")
}
// lightweight accounts don't have share folders, so we're passing an empty string as path
auth, err := fs.getUserAuth(ctx, u, "")
if err != nil {
return nil, err
}

searchString := ""
finfos := []*provider.ResourceInfo{}

for i, key := range mdKeys {
if key == "search" {
p = fs.wrap(ctx, p)

// TODO(salfagem): Ugly hack due to mdKeys being an array.
// - also check that searchString not empty:
searchString = mdKeys[i+1]

if searchString == "" {
return nil, errtypes.NotSupported("Search requires a search string")
}

log.Debug().Msgf("eosfs: running search: path=%s searchString=%s", p, searchString)

eosFileInfos, err := fs.c.SearchDir(ctx, auth, searchString, p)

if err != nil {
return nil, errors.Wrap(err, "eosfs: error searching")
}

for _, eosFileInfo := range eosFileInfos {

log.Debug().Msgf("seach: eosFileInfo %s", eosFileInfo.File)

// Search all files, hidden and not, for the time being

if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil {
log.Debug().Msgf("eosfs: file name from search %s", finfo.Name)
finfos = append(finfos, finfo)
} else {
log.Error().Err(err).Msg(" wtf 🥘")
}
}

return finfos, nil
}
}

if fs.conf.EnableHome {
return fs.listWithHome(ctx, p)
}
Expand Down Expand Up @@ -2070,6 +2124,8 @@ func (fs *eosfs) convertToResourceInfo(ctx context.Context, eosFileInfo *eosclie
}

func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
log := appctx.GetLogger(ctx)
log.Debug().Msg("convertToFileReference")
info, err := fs.convert(ctx, eosFileInfo)
if err != nil {
return nil, err
Expand Down Expand Up @@ -2208,6 +2264,8 @@ func mergePermissions(l *provider.ResourcePermissions, r *provider.ResourcePermi
}

func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
log := appctx.GetLogger(ctx)
log.Debug().Msg("convert")
path, err := fs.unwrap(ctx, eosFileInfo.File)
if err != nil {
return nil, err
Expand Down Expand Up @@ -2243,6 +2301,8 @@ func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (
}
}

log.Debug().Msgf("convert: marshalling the eosFileInfo %s", eosFileInfo)

info := &provider.ResourceInfo{
Id: &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.Inode)},
Path: path,
Expand Down