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

Commit

Permalink
search: basic REPORT functionality
Browse files Browse the repository at this point in the history
- SearchDir() with searchstring and basic input validation
- log pattern on REPORT
- ListFolder() with search capabilities via opaque
- add the searchString from opaque to ListFolder mdKeys
  • Loading branch information
SamuAlfageme committed Dec 5, 2022
1 parent b7e1824 commit 26939be
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 37 deletions.
5 changes: 5 additions & 0 deletions internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,11 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer
}, nil
}

// Ugly hack to enable search
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
135 changes: 100 additions & 35 deletions internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ package ocdav

import (
"encoding/xml"
"fmt"
"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"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
)
Expand All @@ -39,14 +42,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("hugo: 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 @@ -57,21 +63,98 @@ 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("hugo: 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]*typespb.OpaqueEntry{
"search": {
Decoder: "plain",
Value: []byte("search"),
},
"searchString": {
Decoder: "plain",
Value: []byte(sf.Search.Pattern),
},
}

// TODO(salfagem): hardcoded path for the time being:
ref := &provider.Reference{Path: "/eos/project/c/cernbox"}

req := &provider.ListContainerRequest{Opaque: &typespb.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
}

for _, v := range res.Infos {
fmt.Println(v)
}

data := `
<?xml version="1.0" encoding="UTF-8"?>
<d:multistatus xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:s="http://sabredav.org/ns">
<d:response>
<d:href>/remote.php/dav/spaces/1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51/asd.txt</d:href>
<d:propstat>
<d:prop>
<oc:fileid>1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51!d5613880-307c-4e3e-b56d-97839fcf6d03</oc:fileid>
<oc:file-parent>1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51!4c510ada-c86b-4815-8820-42cdf82c3d51</oc:file-parent>
<oc:name>asd.txt</oc:name>
<d:getlastmodified>2022-11-08T10:45:16Z</d:getlastmodified>
<d:getcontenttype>text/plain</d:getcontenttype>
<oc:permissions>RDNVW</oc:permissions>
<d:getetag />
<d:resourcetype />
<d:getcontentlength>0</d:getcontentlength>
<oc:score>0.4809828996658325</oc:score>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/spaces/1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51/asdddddd</d:href>
<d:propstat>
<d:prop>
<oc:fileid>1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51!fa3c7a85-5fc4-46d3-a0b1-293a282da1b7</oc:fileid>
<oc:file-parent>1284d238-aa92-42ce-bdc4-0b0000009157$4c510ada-c86b-4815-8820-42cdf82c3d51!4c510ada-c86b-4815-8820-42cdf82c3d51</oc:file-parent>
<oc:name>asdddddd</oc:name>
<d:getlastmodified>2022-11-08T10:45:32Z</d:getlastmodified>
<d:getcontenttype>httpd/unix-directory</d:getcontenttype>
<oc:permissions>RDNVCK</oc:permissions>
<d:getetag />
<d:resourcetype>
<d:collection />
</d:resourcetype>
<oc:size>0</oc:size>
<oc:score>0.4809828996658325</oc:score>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
`
w.Write([]byte(data))
w.WriteHeader(207)
return
}

func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) {
Expand Down Expand Up @@ -149,7 +232,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 @@ -167,34 +250,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
}
26 changes: 26 additions & 0 deletions pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -742,6 +743,31 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, path st
return c.parseFind(ctx, auth, path, stdout)
}

// 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/c/cernbox") {
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, _, err := c.executeEOS(ctx, args, auth)
if err != nil {
return nil, errors.Wrapf(err, "eosclient: error listing fn=%s", path)
}
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()
Expand Down
1 change: 1 addition & 0 deletions pkg/eosclient/eosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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
10 changes: 8 additions & 2 deletions pkg/eosclient/eosgrpc/eosgrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1236,10 +1236,16 @@ func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath s

}

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
// returning a handle to the body is nice, yet it gives less control on the transaction
// itself, e.g. strange timeouts or TCP issues may be more difficult to trace
//
// returning a handle to the body is nice, yet it gives less control on the transaction
// itself, e.g. strange timeouts or TCP issues may be more difficult to trace
//
// Let's consider this experimental for the moment, maybe I'll like to add a config
// parameter to choose between the two behaviours
func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path string) (io.ReadCloser, error) {
Expand Down
49 changes: 49 additions & 0 deletions pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1274,11 +1274,60 @@ 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")
}

p = fs.wrap(ctx, p)

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" {
// TODO(salfagem): Ugly hack due to mdKeys being an array.
// - also check that searchString not empty:
searchString = mdKeys[i+1]

log.Info().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 {
// filter out sys files
if !fs.conf.ShowHiddenSysFiles {
base := path.Base(eosFileInfo.File)
if hiddenReg.MatchString(base) {
continue
}
}

if finfo, err := fs.convertToFileReference(ctx, eosFileInfo); err == nil {
finfos = append(finfos, finfo)
}
}

return finfos, nil
}
}

if fs.conf.EnableHome {
return fs.listWithHome(ctx, p)
}
Expand Down

0 comments on commit 26939be

Please sign in to comment.