mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
Middleware in the webdav service was missing. Telemetry for webdav was relying on the otelchi middleware that was added afterwards and removed in the previous commit. New (otelhttp) telemetry has been added to the middlewares for the service. Incoming telemetry data from HTTP (captured with otelhttp) with also be stored so the telemetry for go-micro's requests can be picked up and propagate properly.
572 lines
18 KiB
Go
572 lines
18 KiB
Go
package svc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"io"
|
|
"math/rand/v2"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
|
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/render"
|
|
revactx "github.com/owncloud/reva/v2/pkg/ctx"
|
|
"github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool"
|
|
"github.com/owncloud/reva/v2/pkg/storage/utils/templates"
|
|
merrors "go-micro.dev/v4/errors"
|
|
grpcmetadata "google.golang.org/grpc/metadata"
|
|
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
|
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"
|
|
"github.com/owncloud/ocis/v2/services/webdav/pkg/config"
|
|
"github.com/owncloud/ocis/v2/services/webdav/pkg/constants"
|
|
"github.com/owncloud/ocis/v2/services/webdav/pkg/dav/requests"
|
|
)
|
|
|
|
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(w http.ResponseWriter, r *http.Request)
|
|
Thumbnail(w http.ResponseWriter, r *http.Request)
|
|
}
|
|
|
|
// NewService returns a service implementation for Service.
|
|
func NewService(opts ...Option) (Service, error) {
|
|
options := newOptions(opts...)
|
|
conf := options.Config
|
|
|
|
m := chi.NewMux()
|
|
|
|
tm, err := pool.StringToTLSMode(conf.GRPCClientTLS.Mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gatewaySelector, err := pool.GatewaySelector(conf.RevaGateway,
|
|
pool.WithTLSCACert(conf.GRPCClientTLS.CACert),
|
|
pool.WithTLSMode(tm),
|
|
pool.WithRegistry(registry.GetRegistry()),
|
|
pool.WithTracerProvider(options.TraceProvider),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
svc := Webdav{
|
|
config: conf,
|
|
log: options.Logger,
|
|
mux: m,
|
|
searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", conf.GrpcClient),
|
|
thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", conf.GrpcClient),
|
|
gatewaySelector: gatewaySelector,
|
|
}
|
|
|
|
if svc.config.DisablePreviews {
|
|
svc.thumbnailsClient = nil
|
|
}
|
|
|
|
// register method with chi before any routing is set up
|
|
chi.RegisterMethod("REPORT")
|
|
|
|
m.Use(options.Middleware...)
|
|
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
|
|
|
if !svc.config.DisablePreviews {
|
|
r.Group(func(r chi.Router) {
|
|
r.Use(svc.DavUserContext())
|
|
|
|
r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail)
|
|
r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail)
|
|
r.Get("/dav/spaces/{id}", svc.SpacesThumbnail)
|
|
r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail)
|
|
r.MethodFunc("REPORT", "/remote.php/dav/spaces*", svc.Search)
|
|
r.MethodFunc("REPORT", "/dav/spaces*", svc.Search)
|
|
|
|
r.Get("/remote.php/dav/files/{id}", svc.Thumbnail)
|
|
r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail)
|
|
r.Get("/dav/files/{id}", svc.Thumbnail)
|
|
r.Get("/dav/files/{id}/*", svc.Thumbnail)
|
|
|
|
r.MethodFunc("REPORT", "/remote.php/dav/files*", svc.Search)
|
|
r.MethodFunc("REPORT", "/dav/files*", svc.Search)
|
|
})
|
|
|
|
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/webdav*", svc.Search)
|
|
r.MethodFunc("REPORT", "/webdav*", svc.Search)
|
|
})
|
|
}
|
|
|
|
})
|
|
|
|
_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
|
options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
|
|
return nil
|
|
})
|
|
|
|
return svc, nil
|
|
}
|
|
|
|
// Webdav implements the business logic for Service.
|
|
type Webdav struct {
|
|
config *config.Config
|
|
log log.Logger
|
|
mux *chi.Mux
|
|
searchClient searchsvc.SearchProviderService
|
|
thumbnailsClient thumbnailssvc.ThumbnailService
|
|
gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient]
|
|
}
|
|
|
|
// ServeHTTP implements the Service interface.
|
|
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))
|
|
filePath = strings.TrimPrefix(filePath, "/")
|
|
}
|
|
|
|
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) {
|
|
logger := g.log.SubloggerWithRequestID(r.Context())
|
|
tr, err := requests.ParseThumbnailRequest(r)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not create Request")
|
|
renderError(w, r, errBadRequest(err.Error()))
|
|
return
|
|
}
|
|
t := r.Header.Get(revactx.TokenHeader)
|
|
|
|
fullPath := filepath.Join(tr.Identifier, tr.Filepath)
|
|
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{
|
|
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
|
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
|
Width: tr.Width,
|
|
Height: tr.Height,
|
|
Processor: tr.Processor,
|
|
Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{
|
|
Cs3Source: &thumbnailsmsg.CS3Source{
|
|
Path: fullPath,
|
|
Authorization: t,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
e := merrors.Parse(err.Error())
|
|
switch e.Code {
|
|
case http.StatusNotFound:
|
|
// StatusNotFound is expected for unsupported files
|
|
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
|
return
|
|
case http.StatusTooEarly:
|
|
// StatusTooEarly if file is processing
|
|
renderError(w, r, errTooEarly(e.Detail))
|
|
return
|
|
case http.StatusTooManyRequests:
|
|
addRetryAfterHeader(w)
|
|
renderError(w, r, errTooManyRequests(e.Detail))
|
|
case http.StatusBadRequest:
|
|
renderError(w, r, errBadRequest(e.Detail))
|
|
case http.StatusForbidden:
|
|
renderError(w, r, errPermissionDenied(e.Detail))
|
|
default:
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
}
|
|
logger.Debug().Err(err).Msg("could not get thumbnail")
|
|
return
|
|
}
|
|
|
|
g.sendThumbnailResponse(rsp, w, r)
|
|
}
|
|
|
|
// Thumbnail implements the Service interface.
|
|
func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.log.SubloggerWithRequestID(r.Context())
|
|
tr, err := requests.ParseThumbnailRequest(r)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not create Request")
|
|
renderError(w, r, errBadRequest(err.Error()))
|
|
return
|
|
}
|
|
|
|
t := r.Header.Get(revactx.TokenHeader)
|
|
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not get reva gatewayClient")
|
|
renderError(w, r, errInternalError("could not get reva gatewayClient"))
|
|
return
|
|
}
|
|
|
|
var user *userv1beta1.User
|
|
if tr.Identifier == "" {
|
|
// look up user from token via WhoAmI
|
|
userRes, err := gatewayClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{
|
|
Token: t,
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not get user: transport error")
|
|
renderError(w, r, errInternalError("could not get user"))
|
|
return
|
|
}
|
|
if userRes.Status.Code != rpcv1beta1.Code_CODE_OK {
|
|
logger.Debug().Str("grpcmessage", userRes.GetStatus().GetMessage()).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 := grpcmetadata.AppendToOutgoingContext(r.Context(), revactx.TokenHeader, t)
|
|
userRes, err := gatewayClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{
|
|
Claim: "username",
|
|
Value: tr.Identifier,
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not get user: transport error")
|
|
renderError(w, r, errInternalError("could not get user"))
|
|
return
|
|
}
|
|
if userRes.Status.Code != rpcv1beta1.Code_CODE_OK {
|
|
logger.Debug().Str("grpcmessage", userRes.GetStatus().GetMessage()).Msg("could not get user")
|
|
renderError(w, r, errInternalError("could not get user"))
|
|
return
|
|
}
|
|
user = userRes.GetUser()
|
|
}
|
|
|
|
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, ".")),
|
|
Width: tr.Width,
|
|
Height: tr.Height,
|
|
Processor: tr.Processor,
|
|
Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{
|
|
Cs3Source: &thumbnailsmsg.CS3Source{
|
|
Path: fullPath,
|
|
Authorization: t,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
e := merrors.Parse(err.Error())
|
|
switch e.Code {
|
|
case http.StatusNotFound:
|
|
// StatusNotFound is expected for unsupported files
|
|
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
|
return
|
|
case http.StatusTooEarly:
|
|
// StatusTooEarly if file is processing
|
|
renderError(w, r, errTooEarly(e.Detail))
|
|
return
|
|
case http.StatusTooManyRequests:
|
|
addRetryAfterHeader(w)
|
|
renderError(w, r, errTooManyRequests(e.Detail))
|
|
case http.StatusBadRequest:
|
|
renderError(w, r, errBadRequest(e.Detail))
|
|
case http.StatusForbidden:
|
|
renderError(w, r, errPermissionDenied(e.Detail))
|
|
default:
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
}
|
|
g.log.Error().Err(err).Msg("could not get thumbnail")
|
|
return
|
|
}
|
|
|
|
g.sendThumbnailResponse(rsp, w, r)
|
|
}
|
|
|
|
func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.log.SubloggerWithRequestID(r.Context())
|
|
tr, err := requests.ParseThumbnailRequest(r)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not create Request")
|
|
renderError(w, r, errBadRequest(err.Error()))
|
|
return
|
|
}
|
|
|
|
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{
|
|
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
|
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
|
Width: tr.Width,
|
|
Height: tr.Height,
|
|
Processor: tr.Processor,
|
|
Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{
|
|
WebdavSource: &thumbnailsmsg.WebdavSource{
|
|
Url: g.config.OcisPublicURL + r.URL.RequestURI(),
|
|
IsPublicLink: true,
|
|
PublicLinkToken: tr.PublicLinkToken,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
e := merrors.Parse(err.Error())
|
|
switch e.Code {
|
|
case http.StatusNotFound:
|
|
// StatusNotFound is expected for unsupported files
|
|
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
|
return
|
|
case http.StatusBadRequest:
|
|
renderError(w, r, errBadRequest(e.Detail))
|
|
case http.StatusTooManyRequests:
|
|
addRetryAfterHeader(w)
|
|
renderError(w, r, errTooManyRequests(e.Detail))
|
|
default:
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
}
|
|
g.log.Error().Err(err).Msg("could not get thumbnail")
|
|
return
|
|
}
|
|
|
|
g.sendThumbnailResponse(rsp, w, r)
|
|
}
|
|
|
|
func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.log.SubloggerWithRequestID(r.Context())
|
|
tr, err := requests.ParseThumbnailRequest(r)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not create Request")
|
|
renderError(w, r, errBadRequest(err.Error()))
|
|
return
|
|
}
|
|
|
|
_, err = g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{
|
|
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
|
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
|
Width: tr.Width,
|
|
Height: tr.Height,
|
|
Processor: tr.Processor,
|
|
Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{
|
|
WebdavSource: &thumbnailsmsg.WebdavSource{
|
|
Url: g.config.OcisPublicURL + r.URL.RequestURI(),
|
|
IsPublicLink: true,
|
|
PublicLinkToken: tr.PublicLinkToken,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
e := merrors.Parse(err.Error())
|
|
switch e.Code {
|
|
case http.StatusNotFound:
|
|
// StatusNotFound is expected for unsupported files
|
|
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
|
return
|
|
case http.StatusBadRequest:
|
|
renderError(w, r, errBadRequest(e.Detail))
|
|
case http.StatusTooManyRequests:
|
|
addRetryAfterHeader(w)
|
|
renderError(w, r, errTooManyRequests(e.Detail))
|
|
default:
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
}
|
|
logger.Debug().Err(err).Msg("could not get thumbnail")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (g Webdav) sendThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse, w http.ResponseWriter, r *http.Request) {
|
|
logger := g.log.SubloggerWithRequestID(r.Context())
|
|
client := &http.Client{
|
|
// Timeout: time.Second * 5,
|
|
}
|
|
|
|
dlReq, err := tracing.GetNewRequest(r.Context(), http.MethodGet, rsp.DataEndpoint, http.NoBody)
|
|
if err != nil {
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
logger.Error().Err(err).Msg("could not create download thumbnail request")
|
|
return
|
|
}
|
|
dlReq.Header.Set("Transfer-Token", rsp.TransferToken)
|
|
|
|
dlRsp, err := client.Do(dlReq)
|
|
if err != nil {
|
|
renderError(w, r, errInternalError(err.Error()))
|
|
logger.Error().Err(err).Msg("could not download thumbnail: transport error")
|
|
return
|
|
}
|
|
defer dlRsp.Body.Close()
|
|
|
|
if dlRsp.StatusCode != http.StatusOK {
|
|
logger.Debug().
|
|
Str("transfer_token", rsp.GetTransferToken()).
|
|
Str("data_endpoint", rsp.GetDataEndpoint()).
|
|
Str("response_status", dlRsp.Status).
|
|
Msg("could not download thumbnail")
|
|
renderError(w, r, newErrResponse(dlRsp.StatusCode, "could not download thumbnail"))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", dlRsp.Header.Get("Content-Type"))
|
|
_, err = io.Copy(w, dlRsp.Body)
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("failed to write thumbnail to response writer")
|
|
}
|
|
}
|
|
|
|
func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType {
|
|
switch strings.ToUpper(ext) {
|
|
case "GIF":
|
|
return thumbnailsmsg.ThumbnailType_GIF
|
|
case "PNG":
|
|
return thumbnailsmsg.ThumbnailType_PNG
|
|
default:
|
|
return thumbnailsmsg.ThumbnailType_JPG
|
|
}
|
|
}
|
|
|
|
// 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 errPermissionDenied(msg string) *errResponse {
|
|
return newErrResponse(http.StatusForbidden, msg)
|
|
}
|
|
|
|
func errNotFound(msg string) *errResponse {
|
|
return newErrResponse(http.StatusNotFound, msg)
|
|
}
|
|
|
|
func errTooEarly(msg string) *errResponse {
|
|
return newErrResponse(http.StatusTooEarly, msg)
|
|
}
|
|
|
|
func errTooManyRequests(msg string) *errResponse {
|
|
return newErrResponse(http.StatusTooManyRequests, msg)
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
func addRetryAfterHeader(w http.ResponseWriter) {
|
|
after := rand.IntN(14) + 1
|
|
w.Header().Set("Retry-After", strconv.Itoa(after))
|
|
}
|