1
0

CalDAV support (#15)

This commit is contained in:
konrad
2018-11-03 15:05:45 +00:00
committed by Gitea
parent 31a4a1dd00
commit d03fca801b
59 changed files with 2192 additions and 998 deletions

View File

@ -38,6 +38,7 @@ package echo
import (
"bytes"
stdContext "context"
"crypto/tls"
"errors"
"fmt"
@ -45,6 +46,7 @@ import (
stdLog "log"
"net"
"net/http"
"net/url"
"path"
"path/filepath"
"reflect"
@ -72,34 +74,36 @@ type (
TLSServer *http.Server
Listener net.Listener
TLSListener net.Listener
AutoTLSManager autocert.Manager
DisableHTTP2 bool
Debug bool
HideBanner bool
HidePort bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
Validator Validator
Renderer Renderer
AutoTLSManager autocert.Manager
Mutex sync.RWMutex
Logger Logger
}
// Route contains a handler and information for matching against requests.
Route struct {
Method string
Path string
Handler string
Method string `json:"method"`
Path string `json:"path"`
Name string `json:"name"`
}
// HTTPError represents an error that occurred while handling a request.
HTTPError struct {
Code int
Message interface{}
Code int
Message interface{}
Internal error // Stores the error returned by an external dependency
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(HandlerFunc) HandlerFunc
// HandlerFunc defines a function to server HTTP requests.
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
@ -120,21 +124,22 @@ type (
// i is the interface for Echo and Group.
i interface {
GET(string, HandlerFunc, ...MiddlewareFunc)
GET(string, HandlerFunc, ...MiddlewareFunc) *Route
}
)
// HTTP methods
const (
CONNECT = "CONNECT"
DELETE = "DELETE"
GET = "GET"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
POST = "POST"
PUT = "PUT"
TRACE = "TRACE"
CONNECT = "CONNECT"
DELETE = "DELETE"
GET = "GET"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
POST = "POST"
PROPFIND = "PROPFIND"
PUT = "PUT"
TRACE = "TRACE"
)
// MIME types
@ -164,28 +169,35 @@ const (
// Headers
const (
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderServer = "Server"
HeaderOrigin = "Origin"
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
@ -204,6 +216,22 @@ const (
HeaderXCSRFToken = "X-CSRF-Token"
)
const (
Version = "3.3.6"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ %s
High performance, minimalist Go web framework
%s
____________________________________O/_______
O\
`
)
var (
methods = [...]string{
CONNECT,
@ -213,6 +241,7 @@ var (
OPTIONS,
PATCH,
POST,
PROPFIND,
PUT,
TRACE,
}
@ -226,10 +255,10 @@ var (
ErrForbidden = NewHTTPError(http.StatusForbidden)
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrValidatorNotRegistered = errors.New("Validator not registered")
ErrRendererNotRegistered = errors.New("Renderer not registered")
ErrInvalidRedirectCode = errors.New("Invalid redirect status code")
ErrCookieNotFound = errors.New("Cookie not found")
ErrValidatorNotRegistered = errors.New("validator not registered")
ErrRendererNotRegistered = errors.New("renderer not registered")
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
)
// Error handlers
@ -259,7 +288,7 @@ func New() (e *Echo) {
e.TLSServer.Handler = e
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{}
e.Logger.SetLevel(log.OFF)
e.Logger.SetLevel(log.ERROR)
e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
e.pool.New = func() interface{} {
return e.NewContext(nil, nil)
@ -296,6 +325,9 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if he, ok := err.(*HTTPError); ok {
code = he.Code
msg = he.Message
if he.Internal != nil {
err = fmt.Errorf("%v, %v", err, he.Internal)
}
} else if e.Debug {
msg = err.Error()
} else {
@ -305,19 +337,17 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
msg = Map{"message": msg}
}
// Send response
if !c.Response().Committed {
if c.Request().Method == HEAD { // Issue #608
if err := c.NoContent(code); err != nil {
goto ERROR
}
err = c.NoContent(code)
} else {
if err := c.JSON(code, msg); err != nil {
goto ERROR
}
err = c.JSON(code, msg)
}
if err != nil {
e.Logger.Error(err)
}
}
ERROR:
e.Logger.Error(err)
}
// Pre adds middleware to the chain which is run before router.
@ -332,104 +362,114 @@ func (e *Echo) Use(middleware ...MiddlewareFunc) {
// CONNECT registers a new CONNECT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(CONNECT, path, h, m...)
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(CONNECT, path, h, m...)
}
// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(DELETE, path, h, m...)
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(DELETE, path, h, m...)
}
// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(GET, path, h, m...)
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(GET, path, h, m...)
}
// HEAD registers a new HEAD route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(HEAD, path, h, m...)
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(HEAD, path, h, m...)
}
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(OPTIONS, path, h, m...)
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(OPTIONS, path, h, m...)
}
// PATCH registers a new PATCH route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(PATCH, path, h, m...)
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(PATCH, path, h, m...)
}
// POST registers a new POST route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(POST, path, h, m...)
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(POST, path, h, m...)
}
// PUT registers a new PUT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(PUT, path, h, m...)
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(PUT, path, h, m...)
}
// TRACE registers a new TRACE route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(TRACE, path, h, m...)
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(TRACE, path, h, m...)
}
// Any registers a new route for all HTTP methods and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
for _, m := range methods {
e.add(m, path, handler, middleware...)
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = e.Add(m, path, handler, middleware...)
}
return routes
}
// Match registers a new route for multiple HTTP methods and path with matching
// handler in the router with optional route-level middleware.
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
for _, m := range methods {
e.add(m, path, handler, middleware...)
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = e.Add(m, path, handler, middleware...)
}
return routes
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) {
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
static(e, prefix, root)
return static(e, prefix, root)
}
func static(i i, prefix, root string) {
func static(i i, prefix, root string) *Route {
h := func(c Context) error {
name := filepath.Join(root, path.Clean("/"+c.Param("*"))) // "/"+ for security
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
return c.File(name)
}
i.GET(prefix, h)
if prefix == "/" {
i.GET(prefix+"*", h)
} else {
i.GET(prefix+"/*", h)
return i.GET(prefix+"*", h)
}
return i.GET(prefix+"/*", h)
}
// File registers a new route with path to serve a static file.
func (e *Echo) File(path, file string) {
e.GET(path, func(c Context) error {
// File registers a new route with path to serve a static file with optional route-level middleware.
func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
return e.GET(path, func(c Context) error {
return c.File(file)
})
}, m...)
}
func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
// Add registers a new route for an HTTP method and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
name := handlerName(handler)
e.router.Add(method, path, func(c Context) error {
h := handler
@ -439,12 +479,13 @@ func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...Middl
}
return h(c)
})
r := Route{
Method: method,
Path: path,
Handler: name,
r := &Route{
Method: method,
Path: path,
Name: name,
}
e.router.routes[method+path] = r
return r
}
// Group creates a new router group with prefix and optional group-level middleware.
@ -456,12 +497,22 @@ func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
// URI generates a URI from handler.
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
name := handlerName(handler)
return e.Reverse(name, params...)
}
// URL is an alias for `URI` function.
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...)
}
// Reverse generates an URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
n := 0
name := handlerName(handler)
for _, r := range e.router.routes {
if r.Handler == name {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if r.Path[i] == ':' && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
@ -479,14 +530,9 @@ func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
return uri.String()
}
// URL is an alias for `URI` function.
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...)
}
// Routes returns the registered routes.
func (e *Echo) Routes() []Route {
routes := []Route{}
func (e *Echo) Routes() []*Route {
routes := make([]*Route, 0, len(e.router.routes))
for _, v := range e.router.routes {
routes = append(routes, v)
}
@ -507,39 +553,39 @@ func (e *Echo) ReleaseContext(c Context) {
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire lock
e.Mutex.RLock()
defer e.Mutex.RUnlock()
// Acquire context
c := e.pool.Get().(*context)
defer e.pool.Put(c)
c.Reset(r, w)
// Middleware
h := func(c Context) error {
method := r.Method
path := r.URL.RawPath
if path == "" {
path = r.URL.Path
}
e.router.Find(method, path, c)
h := c.Handler()
h := NotFoundHandler
if e.premiddleware == nil {
e.router.Find(r.Method, getPath(r), c)
h = c.Handler()
for i := len(e.middleware) - 1; i >= 0; i-- {
h = e.middleware[i](h)
}
return h(c)
}
// Premiddleware
for i := len(e.premiddleware) - 1; i >= 0; i-- {
h = e.premiddleware[i](h)
} else {
h = func(c Context) error {
e.router.Find(r.Method, getPath(r), c)
h := c.Handler()
for i := len(e.middleware) - 1; i >= 0; i-- {
h = e.middleware[i](h)
}
return h(c)
}
for i := len(e.premiddleware) - 1; i >= 0; i-- {
h = e.premiddleware[i](h)
}
}
// Execute chain
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
// Release context
e.pool.Put(c)
}
// Start starts an HTTP server.
@ -565,6 +611,10 @@ func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
func (e *Echo) StartAutoTLS(address string) error {
if e.Listener == nil {
go http.ListenAndServe(":http", e.AutoTLSManager.HTTPHandler(nil))
}
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
@ -584,8 +634,15 @@ func (e *Echo) startTLS(address string) error {
func (e *Echo) StartServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.Handler = e
s.ErrorLog = e.stdLogger
s.Handler = e
if e.Debug {
e.Logger.SetLevel(log.DEBUG)
}
if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
}
if s.TLSConfig == nil {
if e.Listener == nil {
@ -594,7 +651,9 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return err
}
}
e.colorer.Printf("⇛ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
return s.Serve(e.Listener)
}
if e.TLSListener == nil {
@ -604,10 +663,30 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
}
e.TLSListener = tls.NewListener(l, s.TLSConfig)
}
e.colorer.Printf("⇛ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return s.Serve(e.TLSListener)
}
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
if err := e.TLSServer.Close(); err != nil {
return err
}
return e.Server.Close()
}
// Shutdown stops server the gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
if err := e.TLSServer.Shutdown(ctx); err != nil {
return err
}
return e.Server.Shutdown(ctx)
}
// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, message ...interface{}) *HTTPError {
he := &HTTPError{Code: code, Message: http.StatusText(code)}
@ -619,7 +698,7 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError {
// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
return fmt.Sprintf("code=%d, message=%s", he.Code, he.Message)
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
}
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
@ -643,6 +722,14 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
}
}
func getPath(r *http.Request) string {
path := r.URL.RawPath
if path == "" {
path = r.URL.Path
}
return path
}
func handlerName(h HandlerFunc) string {
t := reflect.ValueOf(h).Type()
if t.Kind() == reflect.Func {
@ -651,6 +738,11 @@ func handlerName(h HandlerFunc) string {
return t.String()
}
// // PathUnescape is wraps `url.PathUnescape`
// func PathUnescape(s string) (string, error) {
// return url.PathUnescape(s)
// }
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually