diff options
author | Wim <wim@42.be> | 2019-01-31 17:06:36 +0100 |
---|---|---|
committer | Wim <wim@42.be> | 2019-01-31 17:06:36 +0100 |
commit | c81c0dd22a7779148c4890cfd4bbf490054f06f1 (patch) | |
tree | 06ce6fcdc8f3a2278a2f3050ba42088dd2e64485 /vendor/github.com/labstack/echo/v4/middleware | |
parent | f8a1ab4622a5b833282e9ee42f382451d17c1a06 (diff) | |
download | matterbridge-msglm-c81c0dd22a7779148c4890cfd4bbf490054f06f1.tar.gz matterbridge-msglm-c81c0dd22a7779148c4890cfd4bbf490054f06f1.tar.bz2 matterbridge-msglm-c81c0dd22a7779148c4890cfd4bbf490054f06f1.zip |
Update vendor, move to labstack/echo/v4 Fixes #698
Diffstat (limited to 'vendor/github.com/labstack/echo/v4/middleware')
21 files changed, 2668 insertions, 0 deletions
diff --git a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go new file mode 100644 index 00000000..76ba2420 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go @@ -0,0 +1,106 @@ +package middleware + +import ( + "encoding/base64" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // BasicAuthConfig defines the config for BasicAuth middleware. + BasicAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Validator is a function to validate BasicAuth credentials. + // Required. + Validator BasicAuthValidator + + // Realm is a string to define realm attribute of BasicAuth. + // Default value "Restricted". + Realm string + } + + // BasicAuthValidator defines a function to validate BasicAuth credentials. + BasicAuthValidator func(string, string, echo.Context) (bool, error) +) + +const ( + basic = "basic" + defaultRealm = "Restricted" +) + +var ( + // DefaultBasicAuthConfig is the default BasicAuth middleware config. + DefaultBasicAuthConfig = BasicAuthConfig{ + Skipper: DefaultSkipper, + Realm: defaultRealm, + } +) + +// BasicAuth returns an BasicAuth middleware. +// +// For valid credentials it calls the next handler. +// For missing or invalid credentials, it sends "401 - Unauthorized" response. +func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc { + c := DefaultBasicAuthConfig + c.Validator = fn + return BasicAuthWithConfig(c) +} + +// BasicAuthWithConfig returns an BasicAuth middleware with config. +// See `BasicAuth()`. +func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { + // Defaults + if config.Validator == nil { + panic("echo: basic-auth middleware requires a validator function") + } + if config.Skipper == nil { + config.Skipper = DefaultBasicAuthConfig.Skipper + } + if config.Realm == "" { + config.Realm = defaultRealm + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + auth := c.Request().Header.Get(echo.HeaderAuthorization) + l := len(basic) + + if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic { + b, err := base64.StdEncoding.DecodeString(auth[l+1:]) + if err != nil { + return err + } + cred := string(b) + for i := 0; i < len(cred); i++ { + if cred[i] == ':' { + // Verify credentials + valid, err := config.Validator(cred[:i], cred[i+1:], c) + if err != nil { + return err + } else if valid { + return next(c) + } + break + } + } + } + + realm := defaultRealm + if config.Realm != defaultRealm { + realm = strconv.Quote(config.Realm) + } + + // Need to return `401` for browsers to pop-up login box. + c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm) + return echo.ErrUnauthorized + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_dump.go b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go new file mode 100644 index 00000000..418d279d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go @@ -0,0 +1,107 @@ +package middleware + +import ( + "bufio" + "bytes" + "io" + "io/ioutil" + "net" + "net/http" + + "github.com/labstack/echo/v4" +) + +type ( + // BodyDumpConfig defines the config for BodyDump middleware. + BodyDumpConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Handler receives request and response payload. + // Required. + Handler BodyDumpHandler + } + + // BodyDumpHandler receives the request and response payload. + BodyDumpHandler func(echo.Context, []byte, []byte) + + bodyDumpResponseWriter struct { + io.Writer + http.ResponseWriter + } +) + +var ( + // DefaultBodyDumpConfig is the default BodyDump middleware config. + DefaultBodyDumpConfig = BodyDumpConfig{ + Skipper: DefaultSkipper, + } +) + +// BodyDump returns a BodyDump middleware. +// +// BodyLimit middleware captures the request and response payload and calls the +// registered handler. +func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc { + c := DefaultBodyDumpConfig + c.Handler = handler + return BodyDumpWithConfig(c) +} + +// BodyDumpWithConfig returns a BodyDump middleware with config. +// See: `BodyDump()`. +func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc { + // Defaults + if config.Handler == nil { + panic("echo: body-dump middleware requires a handler function") + } + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + // Request + reqBody := []byte{} + if c.Request().Body != nil { // Read + reqBody, _ = ioutil.ReadAll(c.Request().Body) + } + c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset + + // Response + resBody := new(bytes.Buffer) + mw := io.MultiWriter(c.Response().Writer, resBody) + writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer} + c.Response().Writer = writer + + if err = next(c); err != nil { + c.Error(err) + } + + // Callback + config.Handler(c, reqBody, resBody.Bytes()) + + return + } + } +} + +func (w *bodyDumpResponseWriter) WriteHeader(code int) { + w.ResponseWriter.WriteHeader(code) +} + +func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func (w *bodyDumpResponseWriter) Flush() { + w.ResponseWriter.(http.Flusher).Flush() +} + +func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return w.ResponseWriter.(http.Hijacker).Hijack() +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_limit.go b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go new file mode 100644 index 00000000..b436bd59 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go @@ -0,0 +1,117 @@ +package middleware + +import ( + "fmt" + "io" + "sync" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/bytes" +) + +type ( + // BodyLimitConfig defines the config for BodyLimit middleware. + BodyLimitConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Maximum allowed size for a request body, it can be specified + // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. + Limit string `yaml:"limit"` + limit int64 + } + + limitedReader struct { + BodyLimitConfig + reader io.ReadCloser + read int64 + context echo.Context + } +) + +var ( + // DefaultBodyLimitConfig is the default BodyLimit middleware config. + DefaultBodyLimitConfig = BodyLimitConfig{ + Skipper: DefaultSkipper, + } +) + +// BodyLimit returns a BodyLimit middleware. +// +// BodyLimit middleware sets the maximum allowed size for a request body, if the +// size exceeds the configured limit, it sends "413 - Request Entity Too Large" +// response. The BodyLimit is determined based on both `Content-Length` request +// header and actual content read, which makes it super secure. +// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M, +// G, T or P. +func BodyLimit(limit string) echo.MiddlewareFunc { + c := DefaultBodyLimitConfig + c.Limit = limit + return BodyLimitWithConfig(c) +} + +// BodyLimitWithConfig returns a BodyLimit middleware with config. +// See: `BodyLimit()`. +func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultBodyLimitConfig.Skipper + } + + limit, err := bytes.Parse(config.Limit) + if err != nil { + panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit)) + } + config.limit = limit + pool := limitedReaderPool(config) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + + // Based on content length + if req.ContentLength > config.limit { + return echo.ErrStatusRequestEntityTooLarge + } + + // Based on content read + r := pool.Get().(*limitedReader) + r.Reset(req.Body, c) + defer pool.Put(r) + req.Body = r + + return next(c) + } + } +} + +func (r *limitedReader) Read(b []byte) (n int, err error) { + n, err = r.reader.Read(b) + r.read += int64(n) + if r.read > r.limit { + return n, echo.ErrStatusRequestEntityTooLarge + } + return +} + +func (r *limitedReader) Close() error { + return r.reader.Close() +} + +func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) { + r.reader = reader + r.context = context + r.read = 0 +} + +func limitedReaderPool(c BodyLimitConfig) sync.Pool { + return sync.Pool{ + New: func() interface{} { + return &limitedReader{BodyLimitConfig: c} + }, + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/compress.go b/vendor/github.com/labstack/echo/v4/middleware/compress.go new file mode 100644 index 00000000..19052064 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/compress.go @@ -0,0 +1,118 @@ +package middleware + +import ( + "bufio" + "compress/gzip" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // GzipConfig defines the config for Gzip middleware. + GzipConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Gzip compression level. + // Optional. Default value -1. + Level int `yaml:"level"` + } + + gzipResponseWriter struct { + io.Writer + http.ResponseWriter + } +) + +const ( + gzipScheme = "gzip" +) + +var ( + // DefaultGzipConfig is the default Gzip middleware config. + DefaultGzipConfig = GzipConfig{ + Skipper: DefaultSkipper, + Level: -1, + } +) + +// Gzip returns a middleware which compresses HTTP response using gzip compression +// scheme. +func Gzip() echo.MiddlewareFunc { + return GzipWithConfig(DefaultGzipConfig) +} + +// GzipWithConfig return Gzip middleware with config. +// See: `Gzip()`. +func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultGzipConfig.Skipper + } + if config.Level == 0 { + config.Level = DefaultGzipConfig.Level + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + res := c.Response() + res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) + if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) { + res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806 + rw := res.Writer + w, err := gzip.NewWriterLevel(rw, config.Level) + if err != nil { + return err + } + defer func() { + if res.Size == 0 { + if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme { + res.Header().Del(echo.HeaderContentEncoding) + } + // We have to reset response to it's pristine state when + // nothing is written to body or error is returned. + // See issue #424, #407. + res.Writer = rw + w.Reset(ioutil.Discard) + } + w.Close() + }() + grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw} + res.Writer = grw + } + return next(c) + } + } +} + +func (w *gzipResponseWriter) WriteHeader(code int) { + if code == http.StatusNoContent { // Issue #489 + w.ResponseWriter.Header().Del(echo.HeaderContentEncoding) + } + w.Header().Del(echo.HeaderContentLength) // Issue #444 + w.ResponseWriter.WriteHeader(code) +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + if w.Header().Get(echo.HeaderContentType) == "" { + w.Header().Set(echo.HeaderContentType, http.DetectContentType(b)) + } + return w.Writer.Write(b) +} + +func (w *gzipResponseWriter) Flush() { + w.Writer.(*gzip.Writer).Flush() +} + +func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return w.ResponseWriter.(http.Hijacker).Hijack() +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go new file mode 100644 index 00000000..c61c7125 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go @@ -0,0 +1,143 @@ +package middleware + +import ( + "net/http" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // CORSConfig defines the config for CORS middleware. + CORSConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // AllowOrigin defines a list of origins that may access the resource. + // Optional. Default value []string{"*"}. + AllowOrigins []string `yaml:"allow_origins"` + + // AllowMethods defines a list methods allowed when accessing the resource. + // This is used in response to a preflight request. + // Optional. Default value DefaultCORSConfig.AllowMethods. + AllowMethods []string `yaml:"allow_methods"` + + // AllowHeaders defines a list of request headers that can be used when + // making the actual request. This is in response to a preflight request. + // Optional. Default value []string{}. + AllowHeaders []string `yaml:"allow_headers"` + + // AllowCredentials indicates whether or not the response to the request + // can be exposed when the credentials flag is true. When used as part of + // a response to a preflight request, this indicates whether or not the + // actual request can be made using credentials. + // Optional. Default value false. + AllowCredentials bool `yaml:"allow_credentials"` + + // ExposeHeaders defines a whitelist headers that clients are allowed to + // access. + // Optional. Default value []string{}. + ExposeHeaders []string `yaml:"expose_headers"` + + // MaxAge indicates how long (in seconds) the results of a preflight request + // can be cached. + // Optional. Default value 0. + MaxAge int `yaml:"max_age"` + } +) + +var ( + // DefaultCORSConfig is the default CORS middleware config. + DefaultCORSConfig = CORSConfig{ + Skipper: DefaultSkipper, + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, + } +) + +// CORS returns a Cross-Origin Resource Sharing (CORS) middleware. +// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS +func CORS() echo.MiddlewareFunc { + return CORSWithConfig(DefaultCORSConfig) +} + +// CORSWithConfig returns a CORS middleware with config. +// See: `CORS()`. +func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultCORSConfig.Skipper + } + if len(config.AllowOrigins) == 0 { + config.AllowOrigins = DefaultCORSConfig.AllowOrigins + } + if len(config.AllowMethods) == 0 { + config.AllowMethods = DefaultCORSConfig.AllowMethods + } + + allowMethods := strings.Join(config.AllowMethods, ",") + allowHeaders := strings.Join(config.AllowHeaders, ",") + exposeHeaders := strings.Join(config.ExposeHeaders, ",") + maxAge := strconv.Itoa(config.MaxAge) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + origin := req.Header.Get(echo.HeaderOrigin) + allowOrigin := "" + + // Check allowed origins + for _, o := range config.AllowOrigins { + if o == "*" && config.AllowCredentials { + allowOrigin = origin + break + } + if o == "*" || o == origin { + allowOrigin = o + break + } + } + + // Simple request + if req.Method != http.MethodOptions { + res.Header().Add(echo.HeaderVary, echo.HeaderOrigin) + res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin) + if config.AllowCredentials { + res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") + } + if exposeHeaders != "" { + res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders) + } + return next(c) + } + + // Preflight request + res.Header().Add(echo.HeaderVary, echo.HeaderOrigin) + res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod) + res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders) + res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin) + res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods) + if config.AllowCredentials { + res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") + } + if allowHeaders != "" { + res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders) + } else { + h := req.Header.Get(echo.HeaderAccessControlRequestHeaders) + if h != "" { + res.Header().Set(echo.HeaderAccessControlAllowHeaders, h) + } + } + if config.MaxAge > 0 { + res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge) + } + return c.NoContent(http.StatusNoContent) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf.go b/vendor/github.com/labstack/echo/v4/middleware/csrf.go new file mode 100644 index 00000000..09a66bb6 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/csrf.go @@ -0,0 +1,210 @@ +package middleware + +import ( + "crypto/subtle" + "errors" + "net/http" + "strings" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/random" +) + +type ( + // CSRFConfig defines the config for CSRF middleware. + CSRFConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // TokenLength is the length of the generated token. + TokenLength uint8 `yaml:"token_length"` + // Optional. Default value 32. + + // TokenLookup is a string in the form of "<source>:<key>" that is used + // to extract token from the request. + // Optional. Default value "header:X-CSRF-Token". + // Possible values: + // - "header:<name>" + // - "form:<name>" + // - "query:<name>" + TokenLookup string `yaml:"token_lookup"` + + // Context key to store generated CSRF token into context. + // Optional. Default value "csrf". + ContextKey string `yaml:"context_key"` + + // Name of the CSRF cookie. This cookie will store CSRF token. + // Optional. Default value "csrf". + CookieName string `yaml:"cookie_name"` + + // Domain of the CSRF cookie. + // Optional. Default value none. + CookieDomain string `yaml:"cookie_domain"` + + // Path of the CSRF cookie. + // Optional. Default value none. + CookiePath string `yaml:"cookie_path"` + + // Max age (in seconds) of the CSRF cookie. + // Optional. Default value 86400 (24hr). + CookieMaxAge int `yaml:"cookie_max_age"` + + // Indicates if CSRF cookie is secure. + // Optional. Default value false. + CookieSecure bool `yaml:"cookie_secure"` + + // Indicates if CSRF cookie is HTTP only. + // Optional. Default value false. + CookieHTTPOnly bool `yaml:"cookie_http_only"` + } + + // csrfTokenExtractor defines a function that takes `echo.Context` and returns + // either a token or an error. + csrfTokenExtractor func(echo.Context) (string, error) +) + +var ( + // DefaultCSRFConfig is the default CSRF middleware config. + DefaultCSRFConfig = CSRFConfig{ + Skipper: DefaultSkipper, + TokenLength: 32, + TokenLookup: "header:" + echo.HeaderXCSRFToken, + ContextKey: "csrf", + CookieName: "_csrf", + CookieMaxAge: 86400, + } +) + +// CSRF returns a Cross-Site Request Forgery (CSRF) middleware. +// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery +func CSRF() echo.MiddlewareFunc { + c := DefaultCSRFConfig + return CSRFWithConfig(c) +} + +// CSRFWithConfig returns a CSRF middleware with config. +// See `CSRF()`. +func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultCSRFConfig.Skipper + } + if config.TokenLength == 0 { + config.TokenLength = DefaultCSRFConfig.TokenLength + } + if config.TokenLookup == "" { + config.TokenLookup = DefaultCSRFConfig.TokenLookup + } + if config.ContextKey == "" { + config.ContextKey = DefaultCSRFConfig.ContextKey + } + if config.CookieName == "" { + config.CookieName = DefaultCSRFConfig.CookieName + } + if config.CookieMaxAge == 0 { + config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge + } + + // Initialize + parts := strings.Split(config.TokenLookup, ":") + extractor := csrfTokenFromHeader(parts[1]) + switch parts[0] { + case "form": + extractor = csrfTokenFromForm(parts[1]) + case "query": + extractor = csrfTokenFromQuery(parts[1]) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + k, err := c.Cookie(config.CookieName) + token := "" + + // Generate token + if err != nil { + token = random.String(config.TokenLength) + } else { + // Reuse token + token = k.Value + } + + switch req.Method { + case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace: + default: + // Validate token only for requests which are not defined as 'safe' by RFC7231 + clientToken, err := extractor(c) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + if !validateCSRFToken(token, clientToken) { + return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token") + } + } + + // Set CSRF cookie + cookie := new(http.Cookie) + cookie.Name = config.CookieName + cookie.Value = token + if config.CookiePath != "" { + cookie.Path = config.CookiePath + } + if config.CookieDomain != "" { + cookie.Domain = config.CookieDomain + } + cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second) + cookie.Secure = config.CookieSecure + cookie.HttpOnly = config.CookieHTTPOnly + c.SetCookie(cookie) + + // Store token in the context + c.Set(config.ContextKey, token) + + // Protect clients from caching the response + c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie) + + return next(c) + } + } +} + +// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the +// provided request header. +func csrfTokenFromHeader(header string) csrfTokenExtractor { + return func(c echo.Context) (string, error) { + return c.Request().Header.Get(header), nil + } +} + +// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the +// provided form parameter. +func csrfTokenFromForm(param string) csrfTokenExtractor { + return func(c echo.Context) (string, error) { + token := c.FormValue(param) + if token == "" { + return "", errors.New("missing csrf token in the form parameter") + } + return token, nil + } +} + +// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the +// provided query parameter. +func csrfTokenFromQuery(param string) csrfTokenExtractor { + return func(c echo.Context) (string, error) { + token := c.QueryParam(param) + if token == "" { + return "", errors.New("missing csrf token in the query string") + } + return token, nil + } +} + +func validateCSRFToken(token, clientToken string) bool { + return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1 +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/jwt.go b/vendor/github.com/labstack/echo/v4/middleware/jwt.go new file mode 100644 index 00000000..861d3142 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/jwt.go @@ -0,0 +1,227 @@ +package middleware + +import ( + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/dgrijalva/jwt-go" + "github.com/labstack/echo/v4" +) + +type ( + // JWTConfig defines the config for JWT middleware. + JWTConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc BeforeFunc + + // SuccessHandler defines a function which is executed for a valid token. + SuccessHandler JWTSuccessHandler + + // ErrorHandler defines a function which is executed for an invalid token. + // It may be used to define a custom JWT error. + ErrorHandler JWTErrorHandler + + // Signing key to validate token. + // Required. + SigningKey interface{} + + // Signing method, used to check token signing method. + // Optional. Default value HS256. + SigningMethod string + + // Context key to store user information from the token into context. + // Optional. Default value "user". + ContextKey string + + // Claims are extendable claims data defining token content. + // Optional. Default value jwt.MapClaims + Claims jwt.Claims + + // TokenLookup is a string in the form of "<source>:<name>" that is used + // to extract token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:<name>" + // - "query:<name>" + // - "cookie:<name>" + TokenLookup string + + // AuthScheme to be used in the Authorization header. + // Optional. Default value "Bearer". + AuthScheme string + + keyFunc jwt.Keyfunc + } + + // JWTSuccessHandler defines a function which is executed for a valid token. + JWTSuccessHandler func(echo.Context) + + // JWTErrorHandler defines a function which is executed for an invalid token. + JWTErrorHandler func(error) error + + jwtExtractor func(echo.Context) (string, error) +) + +// Algorithms +const ( + AlgorithmHS256 = "HS256" +) + +// Errors +var ( + ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") +) + +var ( + // DefaultJWTConfig is the default JWT auth middleware config. + DefaultJWTConfig = JWTConfig{ + Skipper: DefaultSkipper, + SigningMethod: AlgorithmHS256, + ContextKey: "user", + TokenLookup: "header:" + echo.HeaderAuthorization, + AuthScheme: "Bearer", + Claims: jwt.MapClaims{}, + } +) + +// JWT returns a JSON Web Token (JWT) auth middleware. +// +// For valid token, it sets the user in context and calls next handler. +// For invalid token, it returns "401 - Unauthorized" error. +// For missing token, it returns "400 - Bad Request" error. +// +// See: https://jwt.io/introduction +// See `JWTConfig.TokenLookup` +func JWT(key interface{}) echo.MiddlewareFunc { + c := DefaultJWTConfig + c.SigningKey = key + return JWTWithConfig(c) +} + +// JWTWithConfig returns a JWT auth middleware with config. +// See: `JWT()`. +func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultJWTConfig.Skipper + } + if config.SigningKey == nil { + panic("echo: jwt middleware requires signing key") + } + if config.SigningMethod == "" { + config.SigningMethod = DefaultJWTConfig.SigningMethod + } + if config.ContextKey == "" { + config.ContextKey = DefaultJWTConfig.ContextKey + } + if config.Claims == nil { + config.Claims = DefaultJWTConfig.Claims + } + if config.TokenLookup == "" { + config.TokenLookup = DefaultJWTConfig.TokenLookup + } + if config.AuthScheme == "" { + config.AuthScheme = DefaultJWTConfig.AuthScheme + } + config.keyFunc = func(t *jwt.Token) (interface{}, error) { + // Check the signing method + if t.Method.Alg() != config.SigningMethod { + return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"]) + } + return config.SigningKey, nil + } + + // Initialize + parts := strings.Split(config.TokenLookup, ":") + extractor := jwtFromHeader(parts[1], config.AuthScheme) + switch parts[0] { + case "query": + extractor = jwtFromQuery(parts[1]) + case "cookie": + extractor = jwtFromCookie(parts[1]) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + if config.BeforeFunc != nil { + config.BeforeFunc(c) + } + + auth, err := extractor(c) + if err != nil { + if config.ErrorHandler != nil { + return config.ErrorHandler(err) + } + return err + } + token := new(jwt.Token) + // Issue #647, #656 + if _, ok := config.Claims.(jwt.MapClaims); ok { + token, err = jwt.Parse(auth, config.keyFunc) + } else { + t := reflect.ValueOf(config.Claims).Type().Elem() + claims := reflect.New(t).Interface().(jwt.Claims) + token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc) + } + if err == nil && token.Valid { + // Store user information from token into context. + c.Set(config.ContextKey, token) + if config.SuccessHandler != nil { + config.SuccessHandler(c) + } + return next(c) + } + if config.ErrorHandler != nil { + return config.ErrorHandler(err) + } + return &echo.HTTPError{ + Code: http.StatusUnauthorized, + Message: "invalid or expired jwt", + Internal: err, + } + } + } +} + +// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header. +func jwtFromHeader(header string, authScheme string) jwtExtractor { + return func(c echo.Context) (string, error) { + auth := c.Request().Header.Get(header) + l := len(authScheme) + if len(auth) > l+1 && auth[:l] == authScheme { + return auth[l+1:], nil + } + return "", ErrJWTMissing + } +} + +// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string. +func jwtFromQuery(param string) jwtExtractor { + return func(c echo.Context) (string, error) { + token := c.QueryParam(param) + if token == "" { + return "", ErrJWTMissing + } + return token, nil + } +} + +// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie. +func jwtFromCookie(name string) jwtExtractor { + return func(c echo.Context) (string, error) { + cookie, err := c.Cookie(name) + if err != nil { + return "", ErrJWTMissing + } + return cookie.Value, nil + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/key_auth.go b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go new file mode 100644 index 00000000..fe01e23e --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go @@ -0,0 +1,150 @@ +package middleware + +import ( + "errors" + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // KeyAuthConfig defines the config for KeyAuth middleware. + KeyAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // KeyLookup is a string in the form of "<source>:<name>" that is used + // to extract key from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:<name>" + // - "query:<name>" + // - "form:<name>" + KeyLookup string `yaml:"key_lookup"` + + // AuthScheme to be used in the Authorization header. + // Optional. Default value "Bearer". + AuthScheme string + + // Validator is a function to validate key. + // Required. + Validator KeyAuthValidator + } + + // KeyAuthValidator defines a function to validate KeyAuth credentials. + KeyAuthValidator func(string, echo.Context) (bool, error) + + keyExtractor func(echo.Context) (string, error) +) + +var ( + // DefaultKeyAuthConfig is the default KeyAuth middleware config. + DefaultKeyAuthConfig = KeyAuthConfig{ + Skipper: DefaultSkipper, + KeyLookup: "header:" + echo.HeaderAuthorization, + AuthScheme: "Bearer", + } +) + +// KeyAuth returns an KeyAuth middleware. +// +// For valid key it calls the next handler. +// For invalid key, it sends "401 - Unauthorized" response. +// For missing key, it sends "400 - Bad Request" response. +func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc { + c := DefaultKeyAuthConfig + c.Validator = fn + return KeyAuthWithConfig(c) +} + +// KeyAuthWithConfig returns an KeyAuth middleware with config. +// See `KeyAuth()`. +func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultKeyAuthConfig.Skipper + } + // Defaults + if config.AuthScheme == "" { + config.AuthScheme = DefaultKeyAuthConfig.AuthScheme + } + if config.KeyLookup == "" { + config.KeyLookup = DefaultKeyAuthConfig.KeyLookup + } + if config.Validator == nil { + panic("echo: key-auth middleware requires a validator function") + } + + // Initialize + parts := strings.Split(config.KeyLookup, ":") + extractor := keyFromHeader(parts[1], config.AuthScheme) + switch parts[0] { + case "query": + extractor = keyFromQuery(parts[1]) + case "form": + extractor = keyFromForm(parts[1]) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + // Extract and verify key + key, err := extractor(c) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + valid, err := config.Validator(key, c) + if err != nil { + return err + } else if valid { + return next(c) + } + + return echo.ErrUnauthorized + } + } +} + +// keyFromHeader returns a `keyExtractor` that extracts key from the request header. +func keyFromHeader(header string, authScheme string) keyExtractor { + return func(c echo.Context) (string, error) { + auth := c.Request().Header.Get(header) + if auth == "" { + return "", errors.New("missing key in request header") + } + if header == echo.HeaderAuthorization { + l := len(authScheme) + if len(auth) > l+1 && auth[:l] == authScheme { + return auth[l+1:], nil + } + return "", errors.New("invalid key in the request header") + } + return auth, nil + } +} + +// keyFromQuery returns a `keyExtractor` that extracts key from the query string. +func keyFromQuery(param string) keyExtractor { + return func(c echo.Context) (string, error) { + key := c.QueryParam(param) + if key == "" { + return "", errors.New("missing key in the query string") + } + return key, nil + } +} + +// keyFromForm returns a `keyExtractor` that extracts key from the form. +func keyFromForm(param string) keyExtractor { + return func(c echo.Context) (string, error) { + key := c.FormValue(param) + if key == "" { + return "", errors.New("missing key in the form") + } + return key, nil + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/logger.go b/vendor/github.com/labstack/echo/v4/middleware/logger.go new file mode 100644 index 00000000..b2e48347 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/logger.go @@ -0,0 +1,217 @@ +package middleware + +import ( + "bytes" + "io" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/color" + "github.com/valyala/fasttemplate" +) + +type ( + // LoggerConfig defines the config for Logger middleware. + LoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Tags to constructed the logger format. + // + // - time_unix + // - time_unix_nano + // - time_rfc3339 + // - time_rfc3339_nano + // - time_custom + // - id (Request ID) + // - remote_ip + // - uri + // - host + // - method + // - path + // - protocol + // - referer + // - user_agent + // - status + // - error + // - latency (In nanoseconds) + // - latency_human (Human readable) + // - bytes_in (Bytes received) + // - bytes_out (Bytes sent) + // - header:<NAME> + // - query:<NAME> + // - form:<NAME> + // + // Example "${remote_ip} ${status}" + // + // Optional. Default value DefaultLoggerConfig.Format. + Format string `yaml:"format"` + + // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. + CustomTimeFormat string `yaml:"custom_time_format"` + + // Output is a writer where logs in JSON format are written. + // Optional. Default value os.Stdout. + Output io.Writer + + template *fasttemplate.Template + colorer *color.Color + pool *sync.Pool + } +) + +var ( + // DefaultLoggerConfig is the default Logger middleware config. + DefaultLoggerConfig = LoggerConfig{ + Skipper: DefaultSkipper, + Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + + `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + + `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + + `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", + CustomTimeFormat: "2006-01-02 15:04:05.00000", + Output: os.Stdout, + colorer: color.New(), + } +) + +// Logger returns a middleware that logs HTTP requests. +func Logger() echo.MiddlewareFunc { + return LoggerWithConfig(DefaultLoggerConfig) +} + +// LoggerWithConfig returns a Logger middleware with config. +// See: `Logger()`. +func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultLoggerConfig.Skipper + } + if config.Format == "" { + config.Format = DefaultLoggerConfig.Format + } + if config.Output == nil { + config.Output = DefaultLoggerConfig.Output + } + + config.template = fasttemplate.New(config.Format, "${", "}") + config.colorer = color.New() + config.colorer.SetOutput(config.Output) + config.pool = &sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 256)) + }, + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + start := time.Now() + if err = next(c); err != nil { + c.Error(err) + } + stop := time.Now() + buf := config.pool.Get().(*bytes.Buffer) + buf.Reset() + defer config.pool.Put(buf) + + if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { + switch tag { + case "time_unix": + return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10)) + case "time_unix_nano": + return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) + case "time_rfc3339": + return buf.WriteString(time.Now().Format(time.RFC3339)) + case "time_rfc3339_nano": + return buf.WriteString(time.Now().Format(time.RFC3339Nano)) + case "time_custom": + return buf.WriteString(time.Now().Format(config.CustomTimeFormat)) + case "id": + id := req.Header.Get(echo.HeaderXRequestID) + if id == "" { + id = res.Header().Get(echo.HeaderXRequestID) + } + return buf.WriteString(id) + case "remote_ip": + return buf.WriteString(c.RealIP()) + case "host": + return buf.WriteString(req.Host) + case "uri": + return buf.WriteString(req.RequestURI) + case "method": + return buf.WriteString(req.Method) + case "path": + p := req.URL.Path + if p == "" { + p = "/" + } + return buf.WriteString(p) + case "protocol": + return buf.WriteString(req.Proto) + case "referer": + return buf.WriteString(req.Referer()) + case "user_agent": + return buf.WriteString(req.UserAgent()) + case "status": + n := res.Status + s := config.colorer.Green(n) + switch { + case n >= 500: + s = config.colorer.Red(n) + case n >= 400: + s = config.colorer.Yellow(n) + case n >= 300: + s = config.colorer.Cyan(n) + } + return buf.WriteString(s) + case "error": + if err != nil { + return buf.WriteString(err.Error()) + } + case "latency": + l := stop.Sub(start) + return buf.WriteString(strconv.FormatInt(int64(l), 10)) + case "latency_human": + return buf.WriteString(stop.Sub(start).String()) + case "bytes_in": + cl := req.Header.Get(echo.HeaderContentLength) + if cl == "" { + cl = "0" + } + return buf.WriteString(cl) + case "bytes_out": + return buf.WriteString(strconv.FormatInt(res.Size, 10)) + default: + switch { + case strings.HasPrefix(tag, "header:"): + return buf.Write([]byte(c.Request().Header.Get(tag[7:]))) + case strings.HasPrefix(tag, "query:"): + return buf.Write([]byte(c.QueryParam(tag[6:]))) + case strings.HasPrefix(tag, "form:"): + return buf.Write([]byte(c.FormValue(tag[5:]))) + case strings.HasPrefix(tag, "cookie:"): + cookie, err := c.Cookie(tag[7:]) + if err == nil { + return buf.Write([]byte(cookie.Value)) + } + } + } + return 0, nil + }); err != nil { + return + } + + _, err = config.Output.Write(buf.Bytes()) + return + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/method_override.go b/vendor/github.com/labstack/echo/v4/middleware/method_override.go new file mode 100644 index 00000000..92b14d2e --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/method_override.go @@ -0,0 +1,92 @@ +package middleware + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +type ( + // MethodOverrideConfig defines the config for MethodOverride middleware. + MethodOverrideConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Getter is a function that gets overridden method from the request. + // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). + Getter MethodOverrideGetter + } + + // MethodOverrideGetter is a function that gets overridden method from the request + MethodOverrideGetter func(echo.Context) string +) + +var ( + // DefaultMethodOverrideConfig is the default MethodOverride middleware config. + DefaultMethodOverrideConfig = MethodOverrideConfig{ + Skipper: DefaultSkipper, + Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), + } +) + +// MethodOverride returns a MethodOverride middleware. +// MethodOverride middleware checks for the overridden method from the request and +// uses it instead of the original method. +// +// For security reasons, only `POST` method can be overridden. +func MethodOverride() echo.MiddlewareFunc { + return MethodOverrideWithConfig(DefaultMethodOverrideConfig) +} + +// MethodOverrideWithConfig returns a MethodOverride middleware with config. +// See: `MethodOverride()`. +func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultMethodOverrideConfig.Skipper + } + if config.Getter == nil { + config.Getter = DefaultMethodOverrideConfig.Getter + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + if req.Method == http.MethodPost { + m := config.Getter(c) + if m != "" { + req.Method = m + } + } + return next(c) + } + } +} + +// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from +// the request header. +func MethodFromHeader(header string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.Request().Header.Get(header) + } +} + +// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the +// form parameter. +func MethodFromForm(param string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.FormValue(param) + } +} + +// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from +// the query parameter. +func MethodFromQuery(param string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.QueryParam(param) + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go new file mode 100644 index 00000000..d0b7153c --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "regexp" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // Skipper defines a function to skip middleware. Returning true skips processing + // the middleware. + Skipper func(echo.Context) bool + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc func(echo.Context) +) + +func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer { + groups := pattern.FindAllStringSubmatch(input, -1) + if groups == nil { + return nil + } + values := groups[0][1:] + replace := make([]string, 2*len(values)) + for i, v := range values { + j := 2 * i + replace[j] = "$" + strconv.Itoa(i+1) + replace[j+1] = v + } + return strings.NewReplacer(replace...) +} + +// DefaultSkipper returns false which processes the middleware. +func DefaultSkipper(echo.Context) bool { + return false +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go new file mode 100644 index 00000000..9d67f445 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go @@ -0,0 +1,258 @@ +package middleware + +import ( + "fmt" + "io" + "math/rand" + "net" + "net/http" + "net/url" + "regexp" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/labstack/echo/v4" +) + +// TODO: Handle TLS proxy + +type ( + // ProxyConfig defines the config for Proxy middleware. + ProxyConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Balancer defines a load balancing technique. + // Required. + Balancer ProxyBalancer + + // Rewrite defines URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Examples: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + Rewrite map[string]string + + // Context key to store selected ProxyTarget into context. + // Optional. Default value "target". + ContextKey string + + // To customize the transport to remote. + // Examples: If custom TLS certificates are required. + Transport http.RoundTripper + + rewriteRegex map[*regexp.Regexp]string + } + + // ProxyTarget defines the upstream target. + ProxyTarget struct { + Name string + URL *url.URL + Meta echo.Map + } + + // ProxyBalancer defines an interface to implement a load balancing technique. + ProxyBalancer interface { + AddTarget(*ProxyTarget) bool + RemoveTarget(string) bool + Next(echo.Context) *ProxyTarget + } + + commonBalancer struct { + targets []*ProxyTarget + mutex sync.RWMutex + } + + // RandomBalancer implements a random load balancing technique. + randomBalancer struct { + *commonBalancer + random *rand.Rand + } + + // RoundRobinBalancer implements a round-robin load balancing technique. + roundRobinBalancer struct { + *commonBalancer + i uint32 + } +) + +var ( + // DefaultProxyConfig is the default Proxy middleware config. + DefaultProxyConfig = ProxyConfig{ + Skipper: DefaultSkipper, + ContextKey: "target", + } +) + +func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + in, _, err := c.Response().Hijack() + if err != nil { + c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err)) + return + } + defer in.Close() + + out, err := net.Dial("tcp", t.URL.Host) + if err != nil { + he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)) + c.Error(he) + return + } + defer out.Close() + + // Write header + err = r.Write(out) + if err != nil { + he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)) + c.Error(he) + return + } + + errCh := make(chan error, 2) + cp := func(dst io.Writer, src io.Reader) { + _, err = io.Copy(dst, src) + errCh <- err + } + + go cp(out, in) + go cp(in, out) + err = <-errCh + if err != nil && err != io.EOF { + c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err) + } + }) +} + +// NewRandomBalancer returns a random proxy balancer. +func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer { + b := &randomBalancer{commonBalancer: new(commonBalancer)} + b.targets = targets + return b +} + +// NewRoundRobinBalancer returns a round-robin proxy balancer. +func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer { + b := &roundRobinBalancer{commonBalancer: new(commonBalancer)} + b.targets = targets + return b +} + +// AddTarget adds an upstream target to the list. +func (b *commonBalancer) AddTarget(target *ProxyTarget) bool { + for _, t := range b.targets { + if t.Name == target.Name { + return false + } + } + b.mutex.Lock() + defer b.mutex.Unlock() + b.targets = append(b.targets, target) + return true +} + +// RemoveTarget removes an upstream target from the list. +func (b *commonBalancer) RemoveTarget(name string) bool { + b.mutex.Lock() + defer b.mutex.Unlock() + for i, t := range b.targets { + if t.Name == name { + b.targets = append(b.targets[:i], b.targets[i+1:]...) + return true + } + } + return false +} + +// Next randomly returns an upstream target. +func (b *randomBalancer) Next(c echo.Context) *ProxyTarget { + if b.random == nil { + b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + } + b.mutex.RLock() + defer b.mutex.RUnlock() + return b.targets[b.random.Intn(len(b.targets))] +} + +// Next returns an upstream target using round-robin technique. +func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget { + b.i = b.i % uint32(len(b.targets)) + t := b.targets[b.i] + atomic.AddUint32(&b.i, 1) + return t +} + +// Proxy returns a Proxy middleware. +// +// Proxy middleware forwards the request to upstream server using a configured load balancing technique. +func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc { + c := DefaultProxyConfig + c.Balancer = balancer + return ProxyWithConfig(c) +} + +// ProxyWithConfig returns a Proxy middleware with config. +// See: `Proxy()` +func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultLoggerConfig.Skipper + } + if config.Balancer == nil { + panic("echo: proxy middleware requires balancer") + } + config.rewriteRegex = map[*regexp.Regexp]string{} + + // Initialize + for k, v := range config.Rewrite { + k = strings.Replace(k, "*", "(\\S*)", -1) + config.rewriteRegex[regexp.MustCompile(k)] = v + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + tgt := config.Balancer.Next(c) + c.Set(config.ContextKey, tgt) + + // Rewrite + for k, v := range config.rewriteRegex { + replacer := captureTokens(k, req.URL.Path) + if replacer != nil { + req.URL.Path = replacer.Replace(v) + } + } + + // Fix header + if req.Header.Get(echo.HeaderXRealIP) == "" { + req.Header.Set(echo.HeaderXRealIP, c.RealIP()) + } + if req.Header.Get(echo.HeaderXForwardedProto) == "" { + req.Header.Set(echo.HeaderXForwardedProto, c.Scheme()) + } + if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy. + req.Header.Set(echo.HeaderXForwardedFor, c.RealIP()) + } + + // Proxy + switch { + case c.IsWebSocket(): + proxyRaw(tgt, c).ServeHTTP(res, req) + case req.Header.Get(echo.HeaderAccept) == "text/event-stream": + default: + proxyHTTP(tgt, c, config).ServeHTTP(res, req) + } + + return + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go new file mode 100644 index 00000000..7784f9c6 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go @@ -0,0 +1,25 @@ +// +build go1.11 + +package middleware + +import ( + "fmt" + "net/http" + "net/http/httputil" + + "github.com/labstack/echo/v4" +) + +func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler { + proxy := httputil.NewSingleHostReverseProxy(tgt.URL) + proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) { + desc := tgt.URL.String() + if tgt.Name != "" { + desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) + } + c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err) + c.Error(echo.NewHTTPError(http.StatusServiceUnavailable)) + } + proxy.Transport = config.Transport + return proxy +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go new file mode 100644 index 00000000..9a78929f --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go @@ -0,0 +1,14 @@ +// +build !go1.11 + +package middleware + +import ( + "net/http" + "net/http/httputil" + + "github.com/labstack/echo/v4" +) + +func proxyHTTP(t *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler { + return httputil.NewSingleHostReverseProxy(t.URL) +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/recover.go b/vendor/github.com/labstack/echo/v4/middleware/recover.go new file mode 100644 index 00000000..e87aaf32 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/recover.go @@ -0,0 +1,81 @@ +package middleware + +import ( + "fmt" + "runtime" + + "github.com/labstack/echo/v4" +) + +type ( + // RecoverConfig defines the config for Recover middleware. + RecoverConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Size of the stack to be printed. + // Optional. Default value 4KB. + StackSize int `yaml:"stack_size"` + + // DisableStackAll disables formatting stack traces of all other goroutines + // into buffer after the trace for the current goroutine. + // Optional. Default value false. + DisableStackAll bool `yaml:"disable_stack_all"` + + // DisablePrintStack disables printing stack trace. + // Optional. Default value as false. + DisablePrintStack bool `yaml:"disable_print_stack"` + } +) + +var ( + // DefaultRecoverConfig is the default Recover middleware config. + DefaultRecoverConfig = RecoverConfig{ + Skipper: DefaultSkipper, + StackSize: 4 << 10, // 4 KB + DisableStackAll: false, + DisablePrintStack: false, + } +) + +// Recover returns a middleware which recovers from panics anywhere in the chain +// and handles the control to the centralized HTTPErrorHandler. +func Recover() echo.MiddlewareFunc { + return RecoverWithConfig(DefaultRecoverConfig) +} + +// RecoverWithConfig returns a Recover middleware with config. +// See: `Recover()`. +func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultRecoverConfig.Skipper + } + if config.StackSize == 0 { + config.StackSize = DefaultRecoverConfig.StackSize + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + defer func() { + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + stack := make([]byte, config.StackSize) + length := runtime.Stack(stack, !config.DisableStackAll) + if !config.DisablePrintStack { + c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length]) + } + c.Error(err) + } + }() + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/redirect.go b/vendor/github.com/labstack/echo/v4/middleware/redirect.go new file mode 100644 index 00000000..30a2e403 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/redirect.go @@ -0,0 +1,153 @@ +package middleware + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +// RedirectConfig defines the config for Redirect middleware. +type RedirectConfig struct { + // Skipper defines a function to skip middleware. + Skipper + + // Status code to be used when redirecting the request. + // Optional. Default value http.StatusMovedPermanently. + Code int `yaml:"code"` +} + +// redirectLogic represents a function that given a scheme, host and uri +// can both: 1) determine if redirect is needed (will set ok accordingly) and +// 2) return the appropriate redirect url. +type redirectLogic func(scheme, host, uri string) (ok bool, url string) + +const www = "www" + +// DefaultRedirectConfig is the default Redirect middleware config. +var DefaultRedirectConfig = RedirectConfig{ + Skipper: DefaultSkipper, + Code: http.StatusMovedPermanently, +} + +// HTTPSRedirect redirects http requests to https. +// For example, http://labstack.com will be redirect to https://labstack.com. +// +// Usage `Echo#Pre(HTTPSRedirect())` +func HTTPSRedirect() echo.MiddlewareFunc { + return HTTPSRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSRedirect()`. +func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https"; ok { + url = "https://" + host + uri + } + return + }) +} + +// HTTPSWWWRedirect redirects http requests to https www. +// For example, http://labstack.com will be redirect to https://www.labstack.com. +// +// Usage `Echo#Pre(HTTPSWWWRedirect())` +func HTTPSWWWRedirect() echo.MiddlewareFunc { + return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSWWWRedirect()`. +func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https" && host[:3] != www; ok { + url = "https://www." + host + uri + } + return + }) +} + +// HTTPSNonWWWRedirect redirects http requests to https non www. +// For example, http://www.labstack.com will be redirect to https://labstack.com. +// +// Usage `Echo#Pre(HTTPSNonWWWRedirect())` +func HTTPSNonWWWRedirect() echo.MiddlewareFunc { + return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSNonWWWRedirect()`. +func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https"; ok { + if host[:3] == www { + host = host[4:] + } + url = "https://" + host + uri + } + return + }) +} + +// WWWRedirect redirects non www requests to www. +// For example, http://labstack.com will be redirect to http://www.labstack.com. +// +// Usage `Echo#Pre(WWWRedirect())` +func WWWRedirect() echo.MiddlewareFunc { + return WWWRedirectWithConfig(DefaultRedirectConfig) +} + +// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `WWWRedirect()`. +func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = host[:3] != www; ok { + url = scheme + "://www." + host + uri + } + return + }) +} + +// NonWWWRedirect redirects www requests to non www. +// For example, http://www.labstack.com will be redirect to http://labstack.com. +// +// Usage `Echo#Pre(NonWWWRedirect())` +func NonWWWRedirect() echo.MiddlewareFunc { + return NonWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `NonWWWRedirect()`. +func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = host[:3] == www; ok { + url = scheme + "://" + host[4:] + uri + } + return + }) +} + +func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc { + if config.Skipper == nil { + config.Skipper = DefaultTrailingSlashConfig.Skipper + } + if config.Code == 0 { + config.Code = DefaultRedirectConfig.Code + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req, scheme := c.Request(), c.Scheme() + host := req.Host + if ok, url := cb(scheme, host, req.RequestURI); ok { + return c.Redirect(config.Code, url) + } + + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_id.go b/vendor/github.com/labstack/echo/v4/middleware/request_id.go new file mode 100644 index 00000000..21f801f3 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/request_id.go @@ -0,0 +1,64 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/random" +) + +type ( + // RequestIDConfig defines the config for RequestID middleware. + RequestIDConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Generator defines a function to generate an ID. + // Optional. Default value random.String(32). + Generator func() string + } +) + +var ( + // DefaultRequestIDConfig is the default RequestID middleware config. + DefaultRequestIDConfig = RequestIDConfig{ + Skipper: DefaultSkipper, + Generator: generator, + } +) + +// RequestID returns a X-Request-ID middleware. +func RequestID() echo.MiddlewareFunc { + return RequestIDWithConfig(DefaultRequestIDConfig) +} + +// RequestIDWithConfig returns a X-Request-ID middleware with config. +func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultRequestIDConfig.Skipper + } + if config.Generator == nil { + config.Generator = generator + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + rid := req.Header.Get(echo.HeaderXRequestID) + if rid == "" { + rid = config.Generator() + } + res.Header().Set(echo.HeaderXRequestID, rid) + + return next(c) + } + } +} + +func generator() string { + return random.String(32) +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/rewrite.go b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go new file mode 100644 index 00000000..a64e10bb --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go @@ -0,0 +1,84 @@ +package middleware + +import ( + "regexp" + "strings" + + "github.com/labstack/echo/v4" +) + +type ( + // RewriteConfig defines the config for Rewrite middleware. + RewriteConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Rules defines the URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Example: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + // Required. + Rules map[string]string `yaml:"rules"` + + rulesRegex map[*regexp.Regexp]string + } +) + +var ( + // DefaultRewriteConfig is the default Rewrite middleware config. + DefaultRewriteConfig = RewriteConfig{ + Skipper: DefaultSkipper, + } +) + +// Rewrite returns a Rewrite middleware. +// +// Rewrite middleware rewrites the URL path based on the provided rules. +func Rewrite(rules map[string]string) echo.MiddlewareFunc { + c := DefaultRewriteConfig + c.Rules = rules + return RewriteWithConfig(c) +} + +// RewriteWithConfig returns a Rewrite middleware with config. +// See: `Rewrite()`. +func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc { + // Defaults + if config.Rules == nil { + panic("echo: rewrite middleware requires url path rewrite rules") + } + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + config.rulesRegex = map[*regexp.Regexp]string{} + + // Initialize + for k, v := range config.Rules { + k = strings.Replace(k, "*", "(.*)", -1) + k = k + "$" + config.rulesRegex[regexp.MustCompile(k)] = v + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + + // Rewrite + for k, v := range config.rulesRegex { + replacer := captureTokens(k, req.URL.Path) + if replacer != nil { + req.URL.Path = replacer.Replace(v) + break + } + } + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/secure.go b/vendor/github.com/labstack/echo/v4/middleware/secure.go new file mode 100644 index 00000000..8839c879 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/secure.go @@ -0,0 +1,116 @@ +package middleware + +import ( + "fmt" + + "github.com/labstack/echo/v4" +) + +type ( + // SecureConfig defines the config for Secure middleware. + SecureConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // XSSProtection provides protection against cross-site scripting attack (XSS) + // by setting the `X-XSS-Protection` header. + // Optional. Default value "1; mode=block". + XSSProtection string `yaml:"xss_protection"` + + // ContentTypeNosniff provides protection against overriding Content-Type + // header by setting the `X-Content-Type-Options` header. + // Optional. Default value "nosniff". + ContentTypeNosniff string `yaml:"content_type_nosniff"` + + // XFrameOptions can be used to indicate whether or not a browser should + // be allowed to render a page in a <frame>, <iframe> or <object> . + // Sites can use this to avoid clickjacking attacks, by ensuring that their + // content is not embedded into other sites.provides protection against + // clickjacking. + // Optional. Default value "SAMEORIGIN". + // Possible values: + // - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself. + // - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so. + // - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin. + XFrameOptions string `yaml:"x_frame_options"` + + // HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how + // long (in seconds) browsers should remember that this site is only to + // be accessed using HTTPS. This reduces your exposure to some SSL-stripping + // man-in-the-middle (MITM) attacks. + // Optional. Default value 0. + HSTSMaxAge int `yaml:"hsts_max_age"` + + // HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security` + // header, excluding all subdomains from security policy. It has no effect + // unless HSTSMaxAge is set to a non-zero value. + // Optional. Default value false. + HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"` + + // ContentSecurityPolicy sets the `Content-Security-Policy` header providing + // security against cross-site scripting (XSS), clickjacking and other code + // injection attacks resulting from execution of malicious content in the + // trusted web page context. + // Optional. Default value "". + ContentSecurityPolicy string `yaml:"content_security_policy"` + } +) + +var ( + // DefaultSecureConfig is the default Secure middleware config. + DefaultSecureConfig = SecureConfig{ + Skipper: DefaultSkipper, + XSSProtection: "1; mode=block", + ContentTypeNosniff: "nosniff", + XFrameOptions: "SAMEORIGIN", + } +) + +// Secure returns a Secure middleware. +// Secure middleware provides protection against cross-site scripting (XSS) attack, +// content type sniffing, clickjacking, insecure connection and other code injection +// attacks. +func Secure() echo.MiddlewareFunc { + return SecureWithConfig(DefaultSecureConfig) +} + +// SecureWithConfig returns a Secure middleware with config. +// See: `Secure()`. +func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultSecureConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + + if config.XSSProtection != "" { + res.Header().Set(echo.HeaderXXSSProtection, config.XSSProtection) + } + if config.ContentTypeNosniff != "" { + res.Header().Set(echo.HeaderXContentTypeOptions, config.ContentTypeNosniff) + } + if config.XFrameOptions != "" { + res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions) + } + if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 { + subdomains := "" + if !config.HSTSExcludeSubdomains { + subdomains = "; includeSubdomains" + } + res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains)) + } + if config.ContentSecurityPolicy != "" { + res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy) + } + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/slash.go b/vendor/github.com/labstack/echo/v4/middleware/slash.go new file mode 100644 index 00000000..61d6e30b --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/slash.go @@ -0,0 +1,119 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" +) + +type ( + // TrailingSlashConfig defines the config for TrailingSlash middleware. + TrailingSlashConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Status code to be used when redirecting the request. + // Optional, but when provided the request is redirected using this code. + RedirectCode int `yaml:"redirect_code"` + } +) + +var ( + // DefaultTrailingSlashConfig is the default TrailingSlash middleware config. + DefaultTrailingSlashConfig = TrailingSlashConfig{ + Skipper: DefaultSkipper, + } +) + +// AddTrailingSlash returns a root level (before router) middleware which adds a +// trailing slash to the request `URL#Path`. +// +// Usage `Echo#Pre(AddTrailingSlash())` +func AddTrailingSlash() echo.MiddlewareFunc { + return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig) +} + +// AddTrailingSlashWithConfig returns a AddTrailingSlash middleware with config. +// See `AddTrailingSlash()`. +func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultTrailingSlashConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + url := req.URL + path := url.Path + qs := c.QueryString() + if path != "/" && path[len(path)-1] != '/' { + path += "/" + uri := path + if qs != "" { + uri += "?" + qs + } + + // Redirect + if config.RedirectCode != 0 { + return c.Redirect(config.RedirectCode, uri) + } + + // Forward + req.RequestURI = uri + url.Path = path + } + return next(c) + } + } +} + +// RemoveTrailingSlash returns a root level (before router) middleware which removes +// a trailing slash from the request URI. +// +// Usage `Echo#Pre(RemoveTrailingSlash())` +func RemoveTrailingSlash() echo.MiddlewareFunc { + return RemoveTrailingSlashWithConfig(TrailingSlashConfig{}) +} + +// RemoveTrailingSlashWithConfig returns a RemoveTrailingSlash middleware with config. +// See `RemoveTrailingSlash()`. +func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultTrailingSlashConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + url := req.URL + path := url.Path + qs := c.QueryString() + l := len(path) - 1 + if l >= 0 && path != "/" && path[l] == '/' { + path = path[:l] + uri := path + if qs != "" { + uri += "?" + qs + } + + // Redirect + if config.RedirectCode != 0 { + return c.Redirect(config.RedirectCode, uri) + } + + // Forward + req.RequestURI = uri + url.Path = path + } + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/static.go b/vendor/github.com/labstack/echo/v4/middleware/static.go new file mode 100644 index 00000000..bc2087a7 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/static.go @@ -0,0 +1,229 @@ +package middleware + +import ( + "fmt" + "html/template" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/bytes" +) + +type ( + // StaticConfig defines the config for Static middleware. + StaticConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Root directory from where the static content is served. + // Required. + Root string `yaml:"root"` + + // Index file for serving a directory. + // Optional. Default value "index.html". + Index string `yaml:"index"` + + // Enable HTML5 mode by forwarding all not-found requests to root so that + // SPA (single-page application) can handle the routing. + // Optional. Default value false. + HTML5 bool `yaml:"html5"` + + // Enable directory browsing. + // Optional. Default value false. + Browse bool `yaml:"browse"` + } +) + +const html = ` +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>{{ .Name }}</title> + <style> + body { + font-family: Menlo, Consolas, monospace; + padding: 48px; + } + header { + padding: 4px 16px; + font-size: 24px; + } + ul { + list-style-type: none; + margin: 0; + padding: 20px 0 0 0; + display: flex; + flex-wrap: wrap; + } + li { + width: 300px; + padding: 16px; + } + li a { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-decoration: none; + transition: opacity 0.25s; + } + li span { + color: #707070; + font-size: 12px; + } + li a:hover { + opacity: 0.50; + } + .dir { + color: #E91E63; + } + .file { + color: #673AB7; + } + </style> +</head> +<body> + <header> + {{ .Name }} + </header> + <ul> + {{ range .Files }} + <li> + {{ if .Dir }} + {{ $name := print .Name "/" }} + <a class="dir" href="{{ $name }}">{{ $name }}</a> + {{ else }} + <a class="file" href="{{ .Name }}">{{ .Name }}</a> + <span>{{ .Size }}</span> + {{ end }} + </li> + {{ end }} + </ul> +</body> +</html> +` + +var ( + // DefaultStaticConfig is the default Static middleware config. + DefaultStaticConfig = StaticConfig{ + Skipper: DefaultSkipper, + Index: "index.html", + } +) + +// Static returns a Static middleware to serves static content from the provided +// root directory. +func Static(root string) echo.MiddlewareFunc { + c := DefaultStaticConfig + c.Root = root + return StaticWithConfig(c) +} + +// StaticWithConfig returns a Static middleware with config. +// See `Static()`. +func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { + // Defaults + if config.Root == "" { + config.Root = "." // For security we want to restrict to CWD. + } + if config.Skipper == nil { + config.Skipper = DefaultStaticConfig.Skipper + } + if config.Index == "" { + config.Index = DefaultStaticConfig.Index + } + + // Index template + t, err := template.New("index").Parse(html) + if err != nil { + panic(fmt.Sprintf("echo: %v", err)) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + p := c.Request().URL.Path + if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`. + p = c.Param("*") + } + p, err = url.PathUnescape(p) + if err != nil { + return + } + name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security + + fi, err := os.Stat(name) + if err != nil { + if os.IsNotExist(err) { + if err = next(c); err != nil { + if he, ok := err.(*echo.HTTPError); ok { + if config.HTML5 && he.Code == http.StatusNotFound { + return c.File(filepath.Join(config.Root, config.Index)) + } + } + return + } + } + return + } + + if fi.IsDir() { + index := filepath.Join(name, config.Index) + fi, err = os.Stat(index) + + if err != nil { + if config.Browse { + return listDir(t, name, c.Response()) + } + if os.IsNotExist(err) { + return next(c) + } + return + } + + return c.File(index) + } + + return c.File(name) + } + } +} + +func listDir(t *template.Template, name string, res *echo.Response) (err error) { + file, err := os.Open(name) + if err != nil { + return + } + files, err := file.Readdir(-1) + if err != nil { + return + } + + // Create directory index + res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + data := struct { + Name string + Files []interface{} + }{ + Name: name, + } + for _, f := range files { + data.Files = append(data.Files, struct { + Name string + Dir bool + Size string + }{f.Name(), f.IsDir(), bytes.Format(f.Size())}) + } + return t.Execute(res, data) +} |