github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/report.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package ocdav
    20  
    21  import (
    22  	"encoding/xml"
    23  	"io"
    24  	"net/http"
    25  
    26  	rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    27  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    28  	providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    29  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    30  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind"
    31  	"github.com/cs3org/reva/v2/pkg/appctx"
    32  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    33  	"github.com/cs3org/reva/v2/pkg/permission"
    34  	"github.com/cs3org/reva/v2/pkg/utils"
    35  )
    36  
    37  const (
    38  	elementNameSearchFiles = "search-files"
    39  	elementNameFilterFiles = "filter-files"
    40  )
    41  
    42  func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) {
    43  	ctx := r.Context()
    44  	log := appctx.GetLogger(ctx)
    45  	// fn := path.Join(ns, r.URL.Path)
    46  
    47  	rep, status, err := readReport(r.Body)
    48  	if err != nil {
    49  		log.Error().Err(err).Msg("error reading report")
    50  		w.WriteHeader(status)
    51  		return
    52  	}
    53  	if rep.SearchFiles != nil {
    54  		s.doSearchFiles(w, r, rep.SearchFiles)
    55  		return
    56  	}
    57  
    58  	if rep.FilterFiles != nil {
    59  		s.doFilterFiles(w, r, rep.FilterFiles, ns)
    60  		return
    61  	}
    62  
    63  	// TODO(jfd): implement report
    64  
    65  	w.WriteHeader(http.StatusNotImplemented)
    66  }
    67  
    68  func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSearchFiles) {
    69  	w.WriteHeader(http.StatusNotImplemented)
    70  }
    71  
    72  func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) {
    73  	ctx := r.Context()
    74  	log := appctx.GetLogger(ctx)
    75  
    76  	if ff.Rules.Favorite {
    77  		// List the users favorite resources.
    78  		client, err := s.gatewaySelector.Next()
    79  		if err != nil {
    80  			log.Error().Err(err).Msg("error selecting next gateway client")
    81  			w.WriteHeader(http.StatusInternalServerError)
    82  			return
    83  		}
    84  		currentUser := ctxpkg.ContextMustGetUser(ctx)
    85  		ok, err := utils.CheckPermission(ctx, permission.ListFavorites, client)
    86  		if err != nil {
    87  			log.Error().Err(err).Msg("error checking permission")
    88  			w.WriteHeader(http.StatusInternalServerError)
    89  			return
    90  		}
    91  		if !ok {
    92  			log.Info().Interface("user", currentUser).Msg("user not allowed to list favorites")
    93  			w.WriteHeader(http.StatusForbidden)
    94  			return
    95  		}
    96  		favorites, err := s.favoritesManager.ListFavorites(ctx, currentUser.Id)
    97  		if err != nil {
    98  			log.Error().Err(err).Msg("error getting favorites")
    99  			w.WriteHeader(http.StatusInternalServerError)
   100  			return
   101  		}
   102  
   103  		infos := make([]*provider.ResourceInfo, 0, len(favorites))
   104  		for i := range favorites {
   105  			statRes, err := client.Stat(ctx, &providerv1beta1.StatRequest{Ref: &providerv1beta1.Reference{ResourceId: favorites[i]}})
   106  			if err != nil {
   107  				log.Error().Err(err).Msg("error getting resource info")
   108  				continue
   109  			}
   110  			if statRes.Status.Code != rpcv1beta1.Code_CODE_OK {
   111  				log.Error().Interface("stat_response", statRes).Msg("error getting resource info")
   112  				continue
   113  			}
   114  			infos = append(infos, statRes.Info)
   115  		}
   116  
   117  		prefer := net.ParsePrefer(r.Header.Get("prefer"))
   118  		returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
   119  
   120  		responsesXML, err := propfind.MultistatusResponse(ctx, &propfind.XML{Prop: ff.Prop}, infos, s.c.PublicURL, namespace, nil, returnMinimal)
   121  		if err != nil {
   122  			log.Error().Err(err).Msg("error formatting propfind")
   123  			w.WriteHeader(http.StatusInternalServerError)
   124  			return
   125  		}
   126  		w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
   127  		w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
   128  		w.Header().Set(net.HeaderVary, net.HeaderPrefer)
   129  		if returnMinimal {
   130  			w.Header().Set(net.HeaderPreferenceApplied, "return=minimal")
   131  		}
   132  		w.WriteHeader(http.StatusMultiStatus)
   133  		if _, err := w.Write(responsesXML); err != nil {
   134  			log.Err(err).Msg("error writing response")
   135  		}
   136  	}
   137  }
   138  
   139  type report struct {
   140  	SearchFiles *reportSearchFiles
   141  	// FilterFiles TODO add this for tag based search
   142  	FilterFiles *reportFilterFiles `xml:"filter-files"`
   143  }
   144  type reportSearchFiles struct {
   145  	XMLName xml.Name                `xml:"search-files"`
   146  	Lang    string                  `xml:"xml:lang,attr,omitempty"`
   147  	Prop    propfind.Props          `xml:"DAV: prop"`
   148  	Search  reportSearchFilesSearch `xml:"search"`
   149  }
   150  type reportSearchFilesSearch struct {
   151  	Pattern string `xml:"search"`
   152  	Limit   int    `xml:"limit"`
   153  	Offset  int    `xml:"offset"`
   154  }
   155  
   156  type reportFilterFiles struct {
   157  	XMLName xml.Name               `xml:"filter-files"`
   158  	Lang    string                 `xml:"xml:lang,attr,omitempty"`
   159  	Prop    propfind.Props         `xml:"DAV: prop"`
   160  	Rules   reportFilterFilesRules `xml:"filter-rules"`
   161  }
   162  
   163  type reportFilterFilesRules struct {
   164  	Favorite  bool `xml:"favorite"`
   165  	SystemTag int  `xml:"systemtag"`
   166  }
   167  
   168  func readReport(r io.Reader) (rep *report, status int, err error) {
   169  	decoder := xml.NewDecoder(r)
   170  	rep = &report{}
   171  	for {
   172  		t, err := decoder.Token()
   173  		if err == io.EOF {
   174  			// io.EOF is a successful end
   175  			return rep, 0, nil
   176  		}
   177  		if err != nil {
   178  			return nil, http.StatusBadRequest, err
   179  		}
   180  
   181  		if v, ok := t.(xml.StartElement); ok {
   182  			if v.Name.Local == elementNameSearchFiles {
   183  				var repSF reportSearchFiles
   184  				err = decoder.DecodeElement(&repSF, &v)
   185  				if err != nil {
   186  					return nil, http.StatusBadRequest, err
   187  				}
   188  				rep.SearchFiles = &repSF
   189  			} else if v.Name.Local == elementNameFilterFiles {
   190  				var repFF reportFilterFiles
   191  				err = decoder.DecodeElement(&repFF, &v)
   192  				if err != nil {
   193  					return nil, http.StatusBadRequest, err
   194  				}
   195  				rep.FilterFiles = &repFF
   196  			}
   197  		}
   198  	}
   199  }