Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more proxy routes for thumbnails #3567

Merged
merged 8 commits into from
May 5, 2022
6 changes: 6 additions & 0 deletions changelog/unreleased/fix-thumbnails-dav.md
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions extensions/proxy/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions extensions/proxy/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions extensions/proxy/pkg/middleware/basic_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions extensions/webdav/pkg/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package constants

type contextKey int

const (
ContextKeyID contextKey = iota
ContextKeyPath
)
45 changes: 14 additions & 31 deletions extensions/webdav/pkg/dav/requests/thumbnail.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand All @@ -61,32 +70,6 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) {
}, nil
}

// the url looks as followed
//
// /remote.php/dav/files/<user>/<filepath>
//
// 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 {
Expand Down
139 changes: 121 additions & 18 deletions extensions/webdav/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package svc

import (
"context"
"encoding/xml"
"io"
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
Expand All @@ -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"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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, ".")),
Expand Down