github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/meta.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  	"fmt"
    24  	"net/http"
    25  	"path"
    26  	"strings"
    27  
    28  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    29  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    30  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config"
    31  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
    32  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    33  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop"
    34  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind"
    35  	"github.com/cs3org/reva/v2/pkg/appctx"
    36  	"github.com/cs3org/reva/v2/pkg/rhttp/router"
    37  	"github.com/cs3org/reva/v2/pkg/storagespace"
    38  )
    39  
    40  // MetaHandler handles meta requests
    41  type MetaHandler struct {
    42  	VersionsHandler *VersionsHandler
    43  }
    44  
    45  func (h *MetaHandler) init(c *config.Config) error {
    46  	h.VersionsHandler = new(VersionsHandler)
    47  	return h.VersionsHandler.init(c)
    48  }
    49  
    50  // Handler handles requests
    51  func (h *MetaHandler) Handler(s *svc) http.Handler {
    52  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    53  
    54  		var id string
    55  		id, r.URL.Path = router.ShiftPath(r.URL.Path)
    56  		if id == "" {
    57  			if r.Method != MethodPropfind {
    58  				w.WriteHeader(http.StatusBadRequest)
    59  				return
    60  			}
    61  			h.handleEmptyID(w, r)
    62  			return
    63  		}
    64  
    65  		did, err := storagespace.ParseID(id)
    66  		if err != nil {
    67  			logger := appctx.GetLogger(r.Context())
    68  			logger.Debug().Str("prop", net.PropOcMetaPathForUser).Msg("invalid resource id")
    69  			w.WriteHeader(http.StatusBadRequest)
    70  			m := fmt.Sprintf("Invalid resource id %v", id)
    71  			b, err := errors.Marshal(http.StatusBadRequest, m, "", "")
    72  			errors.HandleWebdavError(logger, w, b, err)
    73  			return
    74  		}
    75  		if did.StorageId == "" && did.OpaqueId == "" && strings.Count(id, ":") >= 2 {
    76  			logger := appctx.GetLogger(r.Context())
    77  			logger.Warn().Str("id", id).Msg("detected invalid : separated resourceid id, trying to split it ... but fix the client that made the request")
    78  			// try splitting with :
    79  			parts := strings.SplitN(id, ":", 3)
    80  			did.StorageId = parts[0]
    81  			did.SpaceId = parts[1]
    82  			did.OpaqueId = parts[2]
    83  		}
    84  
    85  		var head string
    86  		head, r.URL.Path = router.ShiftPath(r.URL.Path)
    87  		switch head {
    88  		case "":
    89  			if r.Method != MethodPropfind {
    90  				w.WriteHeader(http.StatusBadRequest)
    91  				return
    92  			}
    93  			h.handlePathForUser(w, r, s, &did)
    94  		case "v":
    95  			h.VersionsHandler.Handler(s, &did).ServeHTTP(w, r)
    96  		default:
    97  			w.WriteHeader(http.StatusNotFound)
    98  		}
    99  
   100  	})
   101  }
   102  
   103  func (h *MetaHandler) handlePathForUser(w http.ResponseWriter, r *http.Request, s *svc, rid *provider.ResourceId) {
   104  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "meta_propfind")
   105  	defer span.End()
   106  
   107  	id := storagespace.FormatResourceID(rid)
   108  	sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("resourceid", id).Logger()
   109  	sublog.Info().Msg("calling get path for user")
   110  
   111  	pf, status, err := propfind.ReadPropfind(r.Body)
   112  	if err != nil {
   113  		sublog.Debug().Err(err).Msg("error reading propfind request")
   114  		w.WriteHeader(status)
   115  		return
   116  	}
   117  
   118  	if ok := hasProp(&pf, net.PropOcMetaPathForUser); !ok {
   119  		sublog.Debug().Str("prop", net.PropOcMetaPathForUser).Msg("error finding prop in request")
   120  		w.WriteHeader(http.StatusBadRequest)
   121  		return
   122  	}
   123  	client, err := s.gatewaySelector.Next()
   124  	if err != nil {
   125  		sublog.Error().Err(err).Msg("error selecting next client")
   126  		w.WriteHeader(http.StatusInternalServerError)
   127  		return
   128  	}
   129  	pathReq := &provider.GetPathRequest{ResourceId: rid}
   130  	pathRes, err := client.GetPath(ctx, pathReq)
   131  	if err != nil {
   132  		sublog.Error().Err(err).Msg("could not send GetPath grpc request: transport error")
   133  		w.WriteHeader(http.StatusInternalServerError)
   134  		return
   135  	}
   136  
   137  	switch pathRes.Status.Code {
   138  	case rpc.Code_CODE_NOT_FOUND:
   139  		sublog.Debug().Str("code", string(pathRes.Status.Code)).Msg("resource not found")
   140  		w.WriteHeader(http.StatusNotFound)
   141  		m := fmt.Sprintf("Resource %s not found", id)
   142  		b, err := errors.Marshal(http.StatusNotFound, m, "", "")
   143  		errors.HandleWebdavError(&sublog, w, b, err)
   144  		return
   145  	case rpc.Code_CODE_PERMISSION_DENIED:
   146  		// raise StatusNotFound so that resources can't be enumerated
   147  		sublog.Debug().Str("code", string(pathRes.Status.Code)).Msg("resource access denied")
   148  		w.WriteHeader(http.StatusNotFound)
   149  		m := fmt.Sprintf("Resource %s not found", id)
   150  		b, err := errors.Marshal(http.StatusNotFound, m, "", "")
   151  		errors.HandleWebdavError(&sublog, w, b, err)
   152  		return
   153  	}
   154  
   155  	propstatOK := propfind.PropstatXML{
   156  		Status: "HTTP/1.1 200 OK",
   157  		Prop: []prop.PropertyXML{
   158  			prop.Escaped("oc:meta-path-for-user", pathRes.Path),
   159  			prop.Escaped("oc:id", id),
   160  			prop.Escaped("oc:fileid", id),
   161  			prop.Escaped("oc:spaceid", storagespace.FormatStorageID(rid.GetStorageId(), rid.GetSpaceId())),
   162  		},
   163  	}
   164  	baseURI := ctx.Value(net.CtxKeyBaseURI).(string)
   165  	msr := propfind.NewMultiStatusResponseXML()
   166  	msr.Responses = []*propfind.ResponseXML{
   167  		{
   168  			Href: net.EncodePath(path.Join(baseURI, id) + "/"),
   169  			Propstat: []propfind.PropstatXML{
   170  				propstatOK,
   171  			},
   172  		},
   173  	}
   174  	propRes, err := xml.Marshal(msr)
   175  	if err != nil {
   176  		sublog.Error().Err(err).Msg("error marshalling propfind response xml")
   177  		w.WriteHeader(http.StatusInternalServerError)
   178  		return
   179  	}
   180  
   181  	w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
   182  	w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
   183  	w.WriteHeader(http.StatusMultiStatus)
   184  	if _, err := w.Write(propRes); err != nil {
   185  		sublog.Error().Err(err).Msg("error writing propfind response")
   186  		return
   187  	}
   188  }
   189  
   190  func (h *MetaHandler) handleEmptyID(w http.ResponseWriter, r *http.Request) {
   191  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "meta_propfind")
   192  	defer span.End()
   193  
   194  	sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Logger()
   195  	pf, status, err := propfind.ReadPropfind(r.Body)
   196  	if err != nil {
   197  		sublog.Debug().Err(err).Msg("error reading propfind request")
   198  		w.WriteHeader(status)
   199  		return
   200  	}
   201  
   202  	if ok := hasProp(&pf, net.PropOcMetaPathForUser); !ok {
   203  		sublog.Debug().Str("prop", net.PropOcMetaPathForUser).Msg("error finding prop in request")
   204  		w.WriteHeader(http.StatusBadRequest)
   205  		return
   206  	}
   207  
   208  	propstatNotFound := propfind.PropstatXML{
   209  		Status: "HTTP/1.1 404 Not Found",
   210  	}
   211  	baseURI := ctx.Value(net.CtxKeyBaseURI).(string)
   212  	msr := propfind.NewMultiStatusResponseXML()
   213  	msr.Responses = []*propfind.ResponseXML{
   214  		{
   215  			Href: net.EncodePath(baseURI + "/"),
   216  			Propstat: []propfind.PropstatXML{
   217  				propstatNotFound,
   218  			},
   219  		},
   220  	}
   221  	propRes, err := xml.Marshal(msr)
   222  	if err != nil {
   223  		sublog.Error().Err(err).Msg("error marshalling propfind response xml")
   224  		w.WriteHeader(http.StatusInternalServerError)
   225  		return
   226  	}
   227  
   228  	w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
   229  	w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
   230  	w.WriteHeader(http.StatusMultiStatus)
   231  	if _, err := w.Write(propRes); err != nil {
   232  		sublog.Error().Err(err).Msg("error writing propfind response")
   233  		return
   234  	}
   235  }
   236  
   237  func hasProp(pf *propfind.XML, key string) bool {
   238  	for i := range pf.Prop {
   239  		k := fmt.Sprintf("%s/%s", pf.Prop[i].Space, pf.Prop[i].Local)
   240  		if k == key {
   241  			return true
   242  		}
   243  	}
   244  	return false
   245  }