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 }