Files
opencloud/services/webdav/pkg/errors/error.go
2025-01-21 11:16:38 +01:00

171 lines
7.4 KiB
Go

package errors
import (
"bytes"
"encoding/xml"
"net/http"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
var sabreException = map[int]string{
// the commented states have no corresponding exception in sabre/dav,
// see https://github.com/sabre-io/dav/tree/master/lib/DAV/Exception
// http.StatusMultipleChoices: "Multiple Choices",
// http.StatusMovedPermanently: "Moved Permanently",
// http.StatusFound: "Found",
// http.StatusSeeOther: "See Other",
// http.StatusNotModified: "Not Modified",
// http.StatusUseProxy: "Use Proxy",
// http.StatusTemporaryRedirect: "Temporary Redirect",
// http.StatusPermanentRedirect: "Permanent Redirect",
http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest",
http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated",
http.StatusPaymentRequired: "Sabre\\DAV\\Exception\\PaymentRequired",
http.StatusForbidden: "Sabre\\DAV\\Exception\\Forbidden", // InvalidResourceType, InvalidSyncToken, TooManyMatches
http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound",
http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed",
// http.StatusNotAcceptable: "Not Acceptable",
// http.StatusProxyAuthRequired: "Proxy Authentication Required",
// http.StatusRequestTimeout: "Request Timeout",
http.StatusConflict: "Sabre\\DAV\\Exception\\Conflict", // LockTokenMatchesRequestUri
// http.StatusGone: "Gone",
http.StatusLengthRequired: "Sabre\\DAV\\Exception\\LengthRequired",
http.StatusPreconditionFailed: "Sabre\\DAV\\Exception\\PreconditionFailed",
// http.StatusRequestEntityTooLarge: "Request Entity Too Large",
// http.StatusRequestURITooLong: "Request URI Too Long",
http.StatusUnsupportedMediaType: "Sabre\\DAV\\Exception\\UnsupportedMediaType", // ReportNotSupported
http.StatusRequestedRangeNotSatisfiable: "Sabre\\DAV\\Exception\\RequestedRangeNotSatisfiable",
// http.StatusExpectationFailed: "Expectation Failed",
// http.StatusTeapot: "I'm a teapot",
// http.StatusMisdirectedRequest: "Misdirected Request",
// http.StatusUnprocessableEntity: "Unprocessable Entity",
http.StatusLocked: "Sabre\\DAV\\Exception\\Locked", // ConflictingLock
// http.StatusFailedDependency: "Failed Dependency",
// http.StatusTooEarly: "Too Early",
// http.StatusUpgradeRequired: "Upgrade Required",
// http.StatusPreconditionRequired: "Precondition Required",
// http.StatusTooManyRequests: "Too Many Requests",
// http.StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
// http.StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
// http.StatusInternalServerError: "Internal Server Error",
http.StatusNotImplemented: "Sabre\\DAV\\Exception\\NotImplemented",
// http.StatusBadGateway: "Bad Gateway",
http.StatusServiceUnavailable: "Sabre\\DAV\\Exception\\ServiceUnavailable",
// http.StatusGatewayTimeout: "Gateway Timeout",
// http.StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
// http.StatusVariantAlsoNegotiates: "Variant Also Negotiates",
http.StatusInsufficientStorage: "Sabre\\DAV\\Exception\\InsufficientStorage",
// http.StatusLoopDetected: "Loop Detected",
// http.StatusNotExtended: "Not Extended",
// http.StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
// SabreException returns a sabre exception text for the HTTP status code. It returns the empty
// string if the code is unknown.
func SabreException(code int) string {
return sabreException[code]
}
// Exception represents a ocdav exception
type Exception struct {
Code int
Message string
Header string
}
// Marshal just calls the xml marshaller for a given exception.
func Marshal(code int, message string, header string) ([]byte, error) {
xmlstring, err := xml.Marshal(&ErrorXML{
Xmlnsd: "DAV",
Xmlnss: "http://sabredav.org/ns",
Exception: sabreException[code],
Message: message,
Header: header,
})
if err != nil {
return nil, err
}
var buf bytes.Buffer
buf.WriteString(xml.Header)
buf.Write(xmlstring)
return buf.Bytes(), err
}
// ErrorXML holds the xml representation of an error
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
type ErrorXML struct {
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"`
// Header is used to indicate the conflicting request header
Header string `xml:"s:header,omitempty"`
}
var (
// ErrInvalidDepth is an invalid depth header error
ErrInvalidDepth = errors.New("webdav: invalid depth")
// ErrInvalidPropfind is an invalid propfind error
ErrInvalidPropfind = errors.New("webdav: invalid propfind")
// ErrInvalidProppatch is an invalid proppatch error
ErrInvalidProppatch = errors.New("webdav: invalid proppatch")
// ErrInvalidLockInfo is an invalid lock error
ErrInvalidLockInfo = errors.New("webdav: invalid lock info")
// ErrUnsupportedLockInfo is an unsupported lock error
ErrUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
// ErrInvalidTimeout is an invalid timeout error
ErrInvalidTimeout = errors.New("webdav: invalid timeout")
// ErrInvalidIfHeader is an invalid if header error
ErrInvalidIfHeader = errors.New("webdav: invalid If header")
// ErrUnsupportedMethod is an unsupported method error
ErrUnsupportedMethod = errors.New("webdav: unsupported method")
// ErrInvalidLockToken is an invalid lock token error
ErrInvalidLockToken = errors.New("webdav: invalid lock token")
// ErrConfirmationFailed is returned by a LockSystem's Confirm method.
ErrConfirmationFailed = errors.New("webdav: confirmation failed")
// ErrForbidden is returned by a LockSystem's Unlock method.
ErrForbidden = errors.New("webdav: forbidden")
// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods.
ErrLocked = errors.New("webdav: locked")
// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
ErrNoSuchLock = errors.New("webdav: no such lock")
// ErrNotImplemented is returned when hitting not implemented code paths
ErrNotImplemented = errors.New("webdav: not implemented")
)
// HandleErrorStatus checks the status code, logs a Debug or Error level message
// and writes an appropriate http status
func HandleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status) {
hsc := status.HTTPStatusFromCode(s.Code)
if hsc == http.StatusInternalServerError {
log.Error().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc))
} else {
log.Debug().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc))
}
w.WriteHeader(hsc)
}
// HandleWebdavError checks the status code, logs an error and creates a webdav response body
// if needed
func HandleWebdavError(log *zerolog.Logger, w http.ResponseWriter, b []byte, err error) {
if err != nil {
log.Error().Msgf("error marshaling xml response: %s", b)
w.WriteHeader(http.StatusInternalServerError)
return
}
_, err = w.Write(b)
if err != nil {
log.Err(err).Msg("error writing response")
}
}