github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/publicfile.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  	"context"
    23  	"fmt"
    24  	"net/http"
    25  	"path"
    26  
    27  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    28  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    29  	ocdaverrors "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
    30  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    31  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind"
    32  	"github.com/cs3org/reva/v2/pkg/appctx"
    33  	"github.com/cs3org/reva/v2/pkg/rhttp/router"
    34  	"go.opentelemetry.io/otel/codes"
    35  	semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
    36  )
    37  
    38  // PublicFileHandler handles requests on a shared file. it needs to be wrapped in a collection
    39  type PublicFileHandler struct {
    40  	namespace string
    41  }
    42  
    43  func (h *PublicFileHandler) init(ns string) error {
    44  	h.namespace = path.Join("/", ns)
    45  	return nil
    46  }
    47  
    48  // Handler handles requests
    49  func (h *PublicFileHandler) Handler(s *svc) http.Handler {
    50  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    51  		ctx := r.Context()
    52  		log := appctx.GetLogger(ctx)
    53  		token, relativePath := router.ShiftPath(r.URL.Path)
    54  
    55  		base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), token)
    56  		ctx = context.WithValue(ctx, net.CtxKeyBaseURI, base)
    57  		r = r.WithContext(ctx)
    58  
    59  		log.Debug().Str("relativePath", relativePath).Msg("PublicFileHandler func")
    60  
    61  		if relativePath != "" && relativePath != "/" {
    62  			// accessing the file
    63  
    64  			switch r.Method {
    65  			case MethodPropfind:
    66  				s.handlePropfindOnToken(w, r, h.namespace, false)
    67  			case http.MethodGet:
    68  				s.handlePathGet(w, r, h.namespace)
    69  			case http.MethodOptions:
    70  				s.handleOptions(w, r)
    71  			case http.MethodHead:
    72  				s.handlePathHead(w, r, h.namespace)
    73  			case http.MethodPut:
    74  				s.handlePathPut(w, r, h.namespace)
    75  			default:
    76  				w.WriteHeader(http.StatusMethodNotAllowed)
    77  			}
    78  		} else {
    79  			// accessing the virtual parent folder
    80  			switch r.Method {
    81  			case MethodPropfind:
    82  				s.handlePropfindOnToken(w, r, h.namespace, true)
    83  			case http.MethodOptions:
    84  				s.handleOptions(w, r)
    85  			case http.MethodHead:
    86  				s.handlePathHead(w, r, h.namespace)
    87  			default:
    88  				w.WriteHeader(http.StatusMethodNotAllowed)
    89  			}
    90  		}
    91  	})
    92  }
    93  
    94  // ns is the namespace that is prefixed to the path in the cs3 namespace
    95  func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns string, onContainer bool) {
    96  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "token_propfind")
    97  	defer span.End()
    98  
    99  	tokenStatInfo, ok := TokenStatInfoFromContext(ctx)
   100  	if !ok {
   101  		span.RecordError(ocdaverrors.ErrTokenStatInfoMissing)
   102  		span.SetStatus(codes.Error, ocdaverrors.ErrTokenStatInfoMissing.Error())
   103  		span.SetAttributes(semconv.HTTPStatusCodeKey.Int(http.StatusInternalServerError))
   104  		w.WriteHeader(http.StatusInternalServerError)
   105  		b, err := ocdaverrors.Marshal(http.StatusInternalServerError, ocdaverrors.ErrTokenStatInfoMissing.Error(), "", "")
   106  		ocdaverrors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err)
   107  		return
   108  	}
   109  	sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger()
   110  	sublog.Debug().Msg("handlePropfindOnToken")
   111  
   112  	dh := r.Header.Get(net.HeaderDepth)
   113  	depth, err := net.ParseDepth(dh)
   114  	if err != nil {
   115  		span.RecordError(err)
   116  		span.SetStatus(codes.Error, "Invalid Depth header value")
   117  		span.SetAttributes(semconv.HTTPStatusCodeKey.Int(http.StatusBadRequest))
   118  		sublog.Debug().Str("depth", dh).Msg(err.Error())
   119  		w.WriteHeader(http.StatusBadRequest)
   120  		m := fmt.Sprintf("Invalid Depth header value: %v", dh)
   121  		b, err := ocdaverrors.Marshal(http.StatusBadRequest, m, "", "")
   122  		ocdaverrors.HandleWebdavError(&sublog, w, b, err)
   123  		return
   124  	}
   125  
   126  	if depth == net.DepthInfinity && !s.c.AllowPropfindDepthInfinitiy {
   127  		span.RecordError(ocdaverrors.ErrInvalidDepth)
   128  		span.SetStatus(codes.Error, "DEPTH: infinity is not supported")
   129  		span.SetAttributes(semconv.HTTPStatusCodeKey.Int(http.StatusBadRequest))
   130  		sublog.Debug().Str("depth", dh).Msg(ocdaverrors.ErrInvalidDepth.Error())
   131  		w.WriteHeader(http.StatusBadRequest)
   132  		m := fmt.Sprintf("Invalid Depth header value: %v", dh)
   133  		b, err := ocdaverrors.Marshal(http.StatusBadRequest, m, "", "")
   134  		ocdaverrors.HandleWebdavError(&sublog, w, b, err)
   135  		return
   136  	}
   137  
   138  	pf, status, err := propfind.ReadPropfind(r.Body)
   139  	if err != nil {
   140  		sublog.Debug().Err(err).Msg("error reading propfind request")
   141  		w.WriteHeader(status)
   142  		return
   143  	}
   144  
   145  	infos := s.getPublicFileInfos(onContainer, depth == net.DepthZero, tokenStatInfo)
   146  
   147  	prefer := net.ParsePrefer(r.Header.Get("prefer"))
   148  	returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
   149  
   150  	propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil, returnMinimal)
   151  	if err != nil {
   152  		sublog.Error().Err(err).Msg("error formatting propfind")
   153  		w.WriteHeader(http.StatusInternalServerError)
   154  		return
   155  	}
   156  
   157  	w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
   158  	w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
   159  	w.Header().Set(net.HeaderVary, net.HeaderPrefer)
   160  	if returnMinimal {
   161  		w.Header().Set(net.HeaderPreferenceApplied, "return=minimal")
   162  	}
   163  	w.WriteHeader(http.StatusMultiStatus)
   164  	if _, err := w.Write(propRes); err != nil {
   165  		sublog.Err(err).Msg("error writing response")
   166  	}
   167  }
   168  
   169  // there are only two possible entries
   170  // 1. the non existing collection
   171  // 2. the shared file
   172  func (s *svc) getPublicFileInfos(onContainer, onlyRoot bool, i *provider.ResourceInfo) []*provider.ResourceInfo {
   173  	infos := []*provider.ResourceInfo{}
   174  	if onContainer {
   175  		// copy link-share data if present
   176  		// we don't copy everything because the checksum should not be present
   177  		var o *typesv1beta1.Opaque
   178  		if i.Opaque != nil && i.Opaque.Map != nil && i.Opaque.Map["link-share"] != nil {
   179  			o = &typesv1beta1.Opaque{
   180  				Map: map[string]*typesv1beta1.OpaqueEntry{
   181  					"link-share": i.Opaque.Map["link-share"],
   182  				},
   183  			}
   184  		}
   185  		// always add collection
   186  		infos = append(infos, &provider.ResourceInfo{
   187  			// Opaque carries the link-share data we need when rendering the collection root href
   188  			Opaque: o,
   189  			Path:   path.Dir(i.Path),
   190  			Type:   provider.ResourceType_RESOURCE_TYPE_CONTAINER,
   191  		})
   192  		if onlyRoot {
   193  			return infos
   194  		}
   195  	}
   196  
   197  	// add the file info
   198  	infos = append(infos, i)
   199  
   200  	return infos
   201  }