mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-08-07 05:14:57 -04:00
make responses OC10 compatible
This commit is contained in:
15 files changed
+310
-238
No files matched your search
@@ -8,6 +8,7 @@ require (
|
||||
contrib.go.opencensus.io/exporter/zipkin v0.1.2
|
||||
github.com/asim/go-micro/v3 v3.5.1-0.20210217182006-0f0ace1a44a9
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/micro/cli/v2 v2.1.2
|
||||
github.com/oklog/run v1.1.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
|
||||
@@ -380,6 +380,7 @@ github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
|
||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
|
||||
@@ -2,6 +2,7 @@ package requests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
@@ -18,34 +19,43 @@ const (
|
||||
DefaultHeight = 32
|
||||
)
|
||||
|
||||
// Request combines all parameters provided when requesting a thumbnail
|
||||
// ThumbnailRequest combines all parameters provided when requesting a thumbnail
|
||||
type ThumbnailRequest struct {
|
||||
// The file path of the source file
|
||||
Filepath string
|
||||
// The file name of the source file including the extension
|
||||
Filename string
|
||||
// The file extension
|
||||
Extension string
|
||||
// The requested width of the thumbnail
|
||||
Width int32
|
||||
// The requested height of the thumbnail
|
||||
Height int32
|
||||
// In case of a public share the public link token.
|
||||
PublicLinkToken string
|
||||
}
|
||||
|
||||
// NewRequest extracts all required parameters from a http request.
|
||||
func ParseThumbnailRequest(r *http.Request) (ThumbnailRequest, error) {
|
||||
fp := extractFilePath(r)
|
||||
// ParseThumbnailRequest extracts all required parameters from a http request.
|
||||
func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) {
|
||||
fp, err := extractFilePath(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := r.URL.Query()
|
||||
|
||||
width, height, err := parseDimensions(q)
|
||||
if err != nil {
|
||||
return ThumbnailRequest{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := ThumbnailRequest{
|
||||
return &ThumbnailRequest{
|
||||
Filepath: fp,
|
||||
Filename: filepath.Base(fp),
|
||||
Extension: filepath.Ext(fp),
|
||||
Width: int32(width),
|
||||
Height: int32(height),
|
||||
PublicLinkToken: chi.URLParam(r, "token"),
|
||||
}
|
||||
|
||||
return tr, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// the url looks as followed
|
||||
@@ -54,43 +64,40 @@ func ParseThumbnailRequest(r *http.Request) (ThumbnailRequest, error) {
|
||||
//
|
||||
// User and filepath are dynamic and filepath can contain slashes
|
||||
// So using the URLParam function is not possible.
|
||||
func extractFilePath(r *http.Request) string {
|
||||
func extractFilePath(r *http.Request) (string, error) {
|
||||
user := chi.URLParam(r, "user")
|
||||
if user != "" {
|
||||
parts := strings.SplitN(r.URL.Path, user, 2)
|
||||
return parts[1]
|
||||
return parts[1], nil
|
||||
}
|
||||
token := chi.URLParam(r, "token")
|
||||
if token != "" {
|
||||
parts := strings.SplitN(r.URL.Path, token, 2)
|
||||
return parts[1]
|
||||
return parts[1], nil
|
||||
}
|
||||
return ""
|
||||
return "", errors.New("could not extract file path")
|
||||
}
|
||||
|
||||
func parseDimensions(q url.Values) (int64, int64, error) {
|
||||
width, err := parseDimension(q.Get("x"), DefaultWidth)
|
||||
width, err := parseDimension(q.Get("x"), "width", DefaultWidth)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
height, err := parseDimension(q.Get("y"), DefaultHeight)
|
||||
height, err := parseDimension(q.Get("y"), "height", DefaultHeight)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return width, height, nil
|
||||
}
|
||||
|
||||
func parseDimension(d string, defaultValue int64) (int64, error) {
|
||||
func parseDimension(d, name string, defaultValue int64) (int64, error) {
|
||||
if d == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
result, err := strconv.ParseInt(d, 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if err != nil || result < 1 {
|
||||
// The error message doesn't fit but for OC10 API compatibility reasons we have to set this.
|
||||
return 0, fmt.Errorf("Cannot set %s of 0 or smaller!", name) //nolint:golint
|
||||
}
|
||||
if result < 1 {
|
||||
return 0, errors.New("invalid dimension")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
Name: "ocis-public-url",
|
||||
Value: flags.OverrideDefaultString(cfg.OcisPublicURL, "https://127.0.0.1:9200"),
|
||||
Usage: "The domain under which oCIS is reachable",
|
||||
EnvVars: []string{"WEBDAV_OCIS_PUBLIC_URL", "OCIS_URL"},
|
||||
EnvVars: []string{"OCIS_PUBLIC_URL", "OCIS_URL"},
|
||||
Destination: &cfg.OcisPublicURL,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"encoding/xml"
|
||||
merrors "github.com/asim/go-micro/v3/errors"
|
||||
"github.com/go-chi/render"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -19,6 +21,15 @@ const (
|
||||
TokenHeader = "X-Access-Token"
|
||||
)
|
||||
|
||||
var (
|
||||
codesEnum = map[int]string{
|
||||
http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest",
|
||||
http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated",
|
||||
http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound",
|
||||
http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed",
|
||||
}
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
type Service interface {
|
||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
||||
@@ -36,6 +47,7 @@ func NewService(opts ...Option) Service {
|
||||
config: options.Config,
|
||||
log: options.Logger,
|
||||
mux: m,
|
||||
thumbnailsClient: thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient),
|
||||
}
|
||||
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
@@ -52,6 +64,7 @@ type Webdav struct {
|
||||
config *config.Config
|
||||
log log.Logger
|
||||
mux *chi.Mux
|
||||
thumbnailsClient thumbnails.ThumbnailService
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
@@ -64,16 +77,14 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
mustWrite(g.log, w, []byte(err.Error()))
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c := thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient)
|
||||
t := r.Header.Get("X-Access-Token")
|
||||
rsp, err := c.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
t := r.Header.Get(TokenHeader)
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
ThumbnailType: extensionToFiletype(strings.TrimLeft(tr.Extension, ".")),
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_Cs3Source{
|
||||
@@ -85,34 +96,37 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
mustWrite(g.log, w, []byte(err.Error()))
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", rsp.GetMimetype())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustWrite(g.log, w, rsp.Thumbnail)
|
||||
g.mustRender(w, r, newThumbnailResponse(rsp))
|
||||
}
|
||||
|
||||
func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
mustWrite(g.log, w, []byte(err.Error()))
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c := thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient)
|
||||
rsp, err := c.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
ThumbnailType: extensionToFiletype(strings.TrimLeft(tr.Extension, ".")),
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_WebdavSource{
|
||||
@@ -125,33 +139,37 @@ func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
mustWrite(g.log, w, []byte(err.Error()))
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", rsp.GetMimetype())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustWrite(g.log, w, rsp.Thumbnail)
|
||||
g.mustRender(w, r, newThumbnailResponse(rsp))
|
||||
}
|
||||
|
||||
func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c := thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient)
|
||||
rsp, err := c.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
ThumbnailType: extensionToFiletype(strings.TrimLeft(tr.Extension, ".")),
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_WebdavSource{
|
||||
@@ -163,33 +181,103 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", rsp.GetMimetype())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func extensionToFiletype(ext string) thumbnails.GetThumbnailRequest_FileType {
|
||||
func extensionToThumbnailType(ext string) thumbnails.GetThumbnailRequest_ThumbnailType {
|
||||
switch strings.ToUpper(ext) {
|
||||
case "GIF", "PNG":
|
||||
return thumbnails.GetThumbnailRequest_PNG
|
||||
case "JPEG", "JPG":
|
||||
return thumbnails.GetThumbnailRequest_JPG
|
||||
default:
|
||||
return thumbnails.GetThumbnailRequest_FileType(-1)
|
||||
return thumbnails.GetThumbnailRequest_JPG
|
||||
}
|
||||
}
|
||||
|
||||
func mustWrite(logger log.Logger, w io.Writer, val []byte) {
|
||||
if _, err := w.Write(val); err != nil {
|
||||
logger.Error().Err(err).Msg("could not write response")
|
||||
func (g Webdav) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
|
||||
if err := render.Render(w, r, renderer); err != nil {
|
||||
g.log.Err(err).Msg("failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
|
||||
type errResponse struct {
|
||||
HTTPStatusCode int `json:"-" xml:"-"`
|
||||
XMLName xml.Name `xml:"d:error"`
|
||||
Xmlnsd string `xml:"xmlns:d,attr"`
|
||||
Xmlnss string `xml:"xmlns:s,attr"`
|
||||
Exception string `xml:"s:exception"`
|
||||
Message string `xml:"s:message"`
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func newErrResponse(statusCode int, msg string) *errResponse {
|
||||
rsp := &errResponse{
|
||||
HTTPStatusCode: statusCode,
|
||||
Xmlnsd: "DAV",
|
||||
Xmlnss: "http://sabredav.org/ns",
|
||||
Exception: codesEnum[statusCode],
|
||||
}
|
||||
if msg != "" {
|
||||
rsp.Message = msg
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
func errInternalError(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusInternalServerError, msg)
|
||||
}
|
||||
|
||||
func errBadRequest(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusBadRequest, msg)
|
||||
}
|
||||
|
||||
func errNotFound(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusNotFound, msg)
|
||||
}
|
||||
|
||||
type thumbnailResponse struct {
|
||||
contentType string
|
||||
thumbnail []byte
|
||||
}
|
||||
|
||||
func (t *thumbnailResponse) Render(w http.ResponseWriter, _ *http.Request) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", t.contentType)
|
||||
_, err := w.Write(t.thumbnail)
|
||||
return err
|
||||
}
|
||||
|
||||
func newThumbnailResponse(rsp *thumbnails.GetThumbnailResponse) *thumbnailResponse {
|
||||
return &thumbnailResponse{
|
||||
contentType: rsp.Mimetype,
|
||||
thumbnail: rsp.Thumbnail,
|
||||
}
|
||||
}
|
||||
|
||||
func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) {
|
||||
render.Status(r, err.HTTPStatusCode)
|
||||
render.XML(w, r, err)
|
||||
}
|
||||
|
||||
func notFoundMsg(name string) string {
|
||||
return "File with name " + name + " could not be located"
|
||||
}
|
||||
Reference in new issue
Block a user