make responses OC10 compatible

This commit is contained in:
David Christofas committed 2021-04-27 22:09:49 +02:00
1 parent 5d6b801b8c
commit af03099aba
15 files changed
+310 -238

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -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=
+29 -22
View File
@@ -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
}
+1 -1
View File
@@ -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,
},
}
+127 -39
View File
@@ -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"
}