diff --git a/changelog/unreleased/fix-thumbnails-dav.md b/changelog/unreleased/fix-thumbnails-dav.md new file mode 100644 index 00000000000..0db2fb4f30a --- /dev/null +++ b/changelog/unreleased/fix-thumbnails-dav.md @@ -0,0 +1,6 @@ +Bugfix: Thumbnails for `/dav/xxx?preview=1` requests + +We've added the thumbnail rendering for `/dav/xxx?preview=1`, `/remote.php/webdav/{relative path}?preview=1` and `/webdav/{relative path}?preview=1` requests, which was previously not supported because of missing routes. It now returns the same thumbnails as for +`/remote.php/dav/xxx?preview=1`. + +https://github.com/owncloud/ocis/pull/3567 diff --git a/extensions/proxy/pkg/config/config.go b/extensions/proxy/pkg/config/config.go index a8c394f36c8..c0d6ea4e8db 100644 --- a/extensions/proxy/pkg/config/config.go +++ b/extensions/proxy/pkg/config/config.go @@ -45,15 +45,15 @@ type Policy struct { // Route defines forwarding routes type Route struct { - Type RouteType `yaml:"type"` + Type RouteType `yaml:"type,omitempty"` // Method optionally limits the route to this HTTP method - Method string `yaml:"method"` - Endpoint string `yaml:"endpoint"` + Method string `yaml:"method,omitempty"` + Endpoint string `yaml:"endpoint,omitempty"` // Backend is a static URL to forward the request to - Backend string `yaml:"backend"` + Backend string `yaml:"backend,omitempty"` // Service name to look up in the registry - Service string `yaml:"service"` - ApacheVHost bool `yaml:"apache_vhost"` + Service string `yaml:"service,omitempty"` + ApacheVHost bool `yaml:"apache_vhost,omitempty"` } // RouteType defines the type of a route diff --git a/extensions/proxy/pkg/config/defaults/defaultconfig.go b/extensions/proxy/pkg/config/defaults/defaultconfig.go index ec1bd9af8ee..d43dda36fb9 100644 --- a/extensions/proxy/pkg/config/defaults/defaultconfig.go +++ b/extensions/proxy/pkg/config/defaults/defaultconfig.go @@ -106,6 +106,16 @@ func DefaultPolicies() []config.Policy { Endpoint: "/remote.php/dav/", Backend: "http://localhost:9115", // TODO use registry? }, + { + Type: config.QueryRoute, + Endpoint: "/dav/?preview=1", + Backend: "http://localhost:9115", + }, + { + Type: config.QueryRoute, + Endpoint: "/webdav/?preview=1", + Backend: "http://localhost:9115", + }, { Endpoint: "/remote.php/", Service: "ocdav", diff --git a/extensions/proxy/pkg/middleware/basic_auth.go b/extensions/proxy/pkg/middleware/basic_auth.go index 425fc4ca0a4..4b810ae2791 100644 --- a/extensions/proxy/pkg/middleware/basic_auth.go +++ b/extensions/proxy/pkg/middleware/basic_auth.go @@ -115,6 +115,7 @@ func (m basicAuth) isPublicLink(req *http.Request) bool { } publicPaths := []string{ + "/dav/public-files/", "/remote.php/dav/public-files/", "/remote.php/ocs/apps/files_sharing/api/v1/tokeninfo/unprotected", "/ocs/v1.php/cloud/capabilities", diff --git a/extensions/webdav/pkg/constants/constants.go b/extensions/webdav/pkg/constants/constants.go new file mode 100644 index 00000000000..3456ab83093 --- /dev/null +++ b/extensions/webdav/pkg/constants/constants.go @@ -0,0 +1,8 @@ +package constants + +type contextKey int + +const ( + ContextKeyID contextKey = iota + ContextKeyPath +) diff --git a/extensions/webdav/pkg/dav/requests/thumbnail.go b/extensions/webdav/pkg/dav/requests/thumbnail.go index 48b80ad4882..c34da246661 100644 --- a/extensions/webdav/pkg/dav/requests/thumbnail.go +++ b/extensions/webdav/pkg/dav/requests/thumbnail.go @@ -7,9 +7,10 @@ import ( "net/url" "path/filepath" "strconv" - "strings" "github.com/go-chi/chi/v5" + + "github.com/owncloud/ocis/v2/extensions/webdav/pkg/constants" ) const ( @@ -39,12 +40,20 @@ type ThumbnailRequest struct { // ParseThumbnailRequest extracts all required parameters from a http request. func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { - fp, id, err := extractFilePath(r) - if err != nil { - return nil, err + ctx := r.Context() + + fp := ctx.Value(constants.ContextKeyPath).(string) + if fp == "" { + return nil, errors.New("invalid file path") + } + + id := "" + v := ctx.Value(constants.ContextKeyID) + if v != nil { + id = v.(string) } - q := r.URL.Query() + q := r.URL.Query() width, height, err := parseDimensions(q) if err != nil { return nil, err @@ -61,32 +70,6 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { }, nil } -// the url looks as followed -// -// /remote.php/dav/files// -// -// User and filepath are dynamic and filepath can contain slashes -// So using the URLParam function is not possible. -func extractFilePath(r *http.Request) (string, string, error) { - id := chi.URLParam(r, "id") - id, err := url.QueryUnescape(id) - if err != nil { - return "", "", errors.New("could not unescape user") - } - if id != "" { - parts := strings.SplitN(r.URL.Path, id, 2) - return parts[1], id, nil - } - - // This is for public links - token := chi.URLParam(r, "token") - if token != "" { - parts := strings.SplitN(r.URL.Path, token, 2) - return parts[1], "", nil - } - return "", "", errors.New("could not extract file path") -} - func parseDimensions(q url.Values) (int64, int64, error) { width, err := parseDimension(q.Get("x"), "width", DefaultWidth) if err != nil { diff --git a/extensions/webdav/pkg/service/v0/service.go b/extensions/webdav/pkg/service/v0/service.go index 43d9929b70d..dd3fbb66fd2 100644 --- a/extensions/webdav/pkg/service/v0/service.go +++ b/extensions/webdav/pkg/service/v0/service.go @@ -1,9 +1,11 @@ package svc import ( + "context" "encoding/xml" "io" "net/http" + "net/url" "path" "path/filepath" "strings" @@ -13,16 +15,16 @@ import ( rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + "github.com/go-chi/chi/v5" "github.com/go-chi/render" merrors "go-micro.dev/v4/errors" "google.golang.org/grpc/metadata" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - - "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" + "github.com/owncloud/ocis/v2/extensions/webdav/pkg/constants" "github.com/owncloud/ocis/v2/extensions/webdav/pkg/dav/requests" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" @@ -73,10 +75,32 @@ func NewService(opts ...Option) (Service, error) { } m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) - r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) - r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) + + r.Group(func(r chi.Router) { + r.Use(svc.DavUserContext()) + + r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) + + r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) + r.Get("/dav/files/{id}/*", svc.Thumbnail) + }) + + r.Group(func(r chi.Router) { + r.Use(svc.DavPublicContext()) + + r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) + + r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) + r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) + }) + + r.Group(func(r chi.Router) { + r.Use(svc.WebDAVContext()) + r.Get("/remote.php/webdav/*", svc.Thumbnail) + r.Get("/webdav/*", svc.Thumbnail) + }) // r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search) @@ -114,6 +138,67 @@ func (g Webdav) ServeHTTP(w http.ResponseWriter, r *http.Request) { g.mux.ServeHTTP(w, r) } +func (g Webdav) DavUserContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + filePath := r.URL.Path + + id := chi.URLParam(r, "id") + id, err := url.QueryUnescape(id) + if err == nil && id != "" { + ctx = context.WithValue(ctx, constants.ContextKeyID, id) + } + + if id != "" { + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/spaces", id)+"/") + filePath = strings.TrimPrefix(filePath, path.Join("/dav/spaces", id)+"/") + + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/files", id)+"/") + filePath = strings.TrimPrefix(filePath, path.Join("/dav/files", id)+"/") + } + + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} + +func (g Webdav) DavPublicContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + filePath := r.URL.Path + + if token := chi.URLParam(r, "token"); token != "" { + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/public-files", token)+"/") + filePath = strings.TrimPrefix(filePath, path.Join("/dav/public-files", token)+"/") + } + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} +func (g Webdav) WebDAVContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + filePath := r.URL.Path + filePath = strings.TrimPrefix(filePath, "/remote.php") + filePath = strings.TrimPrefix(filePath, "/webdav/") + + ctx := context.WithValue(r.Context(), constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} + // SpacesThumbnail is the endpoint for retrieving thumbnails inside of spaces. func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) { tr, err := requests.ParseThumbnailRequest(r) @@ -166,18 +251,36 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { } t := r.Header.Get(TokenHeader) - ctx := metadata.AppendToOutgoingContext(r.Context(), TokenHeader, t) - userRes, err := g.revaClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ - Claim: "username", - Value: tr.Identifier, - }) - if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { - g.log.Error().Err(err).Msg("could not get user") - renderError(w, r, errInternalError("could not get user")) - return + + var user *userv1beta1.User + + if tr.Identifier == "" { + // look up user from token via WhoAmI + userRes, err := g.revaClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{ + Token: t, + }) + if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { + g.log.Error().Err(err).Msg("could not get user") + renderError(w, r, errInternalError("could not get user")) + return + } + user = userRes.GetUser() + } else { + // look up user from URL via GetUserByClaim + ctx := metadata.AppendToOutgoingContext(r.Context(), TokenHeader, t) + userRes, err := g.revaClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ + Claim: "username", + Value: tr.Identifier, + }) + if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { + g.log.Error().Err(err).Msg("could not get user") + renderError(w, r, errInternalError("could not get user")) + return + } + user = userRes.GetUser() } - fullPath := filepath.Join(templates.WithUser(userRes.User, g.config.WebdavNamespace), tr.Filepath) + fullPath := filepath.Join(templates.WithUser(user, g.config.WebdavNamespace), tr.Filepath) rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ Filepath: strings.TrimLeft(tr.Filepath, "/"), ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),