Add v16.7.2

This commit is contained in:
Jan Stabenow
2022-05-13 19:26:45 +02:00
parent 8e70517dff
commit 9c0b535199
2368 changed files with 687657 additions and 1 deletions

25
vendor/github.com/labstack/echo/v4/.editorconfig generated vendored Normal file
View File

@@ -0,0 +1,25 @@
# EditorConfig coding styles definitions. For more information about the
# properties used in this file, please see the EditorConfig documentation:
# http://editorconfig.org/
# indicate this is the root of the project
root = true
[*]
charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
[*.go]
indent_style = tab

20
vendor/github.com/labstack/echo/v4/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1,20 @@
# Automatically normalize line endings for all text-based files
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
* text=auto
# For the following file types, normalize line endings to LF on checking and
# prevent conversion to CRLF when they are checked out (this is required in
# order to prevent newline related issues)
.* text eol=lf
*.go text eol=lf
*.yml text eol=lf
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.json text eol=lf
LICENSE text eol=lf
# Exclude `website` and `cookbook` from GitHub's language statistics
# https://github.com/github/linguist#using-gitattributes
cookbook/* linguist-documentation
website/* linguist-documentation

8
vendor/github.com/labstack/echo/v4/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
coverage.txt
_test
vendor
.idea
*.iml
*.out
.vscode

21
vendor/github.com/labstack/echo/v4/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,21 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.14.x
- 1.15.x
- tip
env:
- GO111MODULE=on
install:
- go get -v golang.org/x/lint/golint
script:
- golint -set_exit_status ./...
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)
matrix:
allow_failures:
- go: tip

234
vendor/github.com/labstack/echo/v4/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,234 @@
# Changelog
## v4.7.0 - 2022-03-01
**Enhancements**
* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
**Fixes**
* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
**General**
* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
## v4.6.3 - 2022-01-10
**Fixes**
* Fixed Echo version number in greeting message which was not incremented to `4.6.2` [#2066](https://github.com/labstack/echo/issues/2066)
## v4.6.2 - 2022-01-08
**Fixes**
* Fixed route containing escaped colon should be matchable but is not matched to request path [#2047](https://github.com/labstack/echo/pull/2047)
* Fixed a problem that returned wrong content-encoding when the gzip compressed content was empty. [#1921](https://github.com/labstack/echo/pull/1921)
* Update (test) dependencies [#2021](https://github.com/labstack/echo/pull/2021)
**Enhancements**
* Add support for configurable target header for the request_id middleware [#2040](https://github.com/labstack/echo/pull/2040)
* Change decompress middleware to use stream decompression instead of buffering [#2018](https://github.com/labstack/echo/pull/2018)
* Documentation updates
## v4.6.1 - 2021-09-26
**Enhancements**
* Add start time to request logger middleware values [#1991](https://github.com/labstack/echo/pull/1991)
## v4.6.0 - 2021-09-20
Introduced a new [request logger](https://github.com/labstack/echo/blob/master/middleware/request_logger.go) middleware
to help with cases when you want to use some other logging library in your application.
**Fixes**
* fix timeout middleware warning: superfluous response.WriteHeader [#1905](https://github.com/labstack/echo/issues/1905)
**Enhancements**
* Add Cookie to KeyAuth middleware's KeyLookup [#1929](https://github.com/labstack/echo/pull/1929)
* JWT middleware should ignore case of auth scheme in request header [#1951](https://github.com/labstack/echo/pull/1951)
* Refactor default error handler to return first if response is already committed [#1956](https://github.com/labstack/echo/pull/1956)
* Added request logger middleware which helps to use custom logger library for logging requests. [#1980](https://github.com/labstack/echo/pull/1980)
* Allow escaping of colon in route path so Google Cloud API "custom methods" could be implemented [#1988](https://github.com/labstack/echo/pull/1988)
## v4.5.0 - 2021-08-01
**Important notes**
A **BREAKING CHANGE** is introduced for JWT middleware users.
The JWT library used for the JWT middleware had to be changed from [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) to
[github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) due former library being unmaintained and affected by security
issues.
The [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) project is a drop-in replacement, but supports only the latest 2 Go versions.
So for JWT middleware users Go 1.15+ is required. For detailed information please read [#1940](https://github.com/labstack/echo/discussions/)
To change the library imports in all .go files in your project replace all occurrences of `dgrijalva/jwt-go` with `golang-jwt/jwt`.
For Linux CLI you can use:
```bash
find -type f -name "*.go" -exec sed -i "s/dgrijalva\/jwt-go/golang-jwt\/jwt/g" {} \;
go mod tidy
```
**Fixes**
* Change JWT library to `github.com/golang-jwt/jwt` [#1946](https://github.com/labstack/echo/pull/1946)
## v4.4.0 - 2021-07-12
**Fixes**
* Split HeaderXForwardedFor header only by comma [#1878](https://github.com/labstack/echo/pull/1878)
* Fix Timeout middleware Context propagation [#1910](https://github.com/labstack/echo/pull/1910)
**Enhancements**
* Bind data using headers as source [#1866](https://github.com/labstack/echo/pull/1866)
* Adds JWTConfig.ParseTokenFunc to JWT middleware to allow different libraries implementing JWT parsing. [#1887](https://github.com/labstack/echo/pull/1887)
* Adding tests for Echo#Host [#1895](https://github.com/labstack/echo/pull/1895)
* Adds RequestIDHandler function to RequestID middleware [#1898](https://github.com/labstack/echo/pull/1898)
* Allow for custom JSON encoding implementations [#1880](https://github.com/labstack/echo/pull/1880)
## v4.3.0 - 2021-05-08
**Important notes**
* Route matching has improvements for following cases:
1. Correctly match routes with parameter part as last part of route (with trailing backslash)
2. Considering handlers when resolving routes and search for matching http method handler
* Echo minimal Go version is now 1.13.
**Fixes**
* When url ends with slash first param route is the match [#1804](https://github.com/labstack/echo/pull/1812)
* Router should check if node is suitable as matching route by path+method and if not then continue search in tree [#1808](https://github.com/labstack/echo/issues/1808)
* Fix timeout middleware not writing response correctly when handler panics [#1864](https://github.com/labstack/echo/pull/1864)
* Fix binder not working with embedded pointer structs [#1861](https://github.com/labstack/echo/pull/1861)
* Add Go 1.16 to CI and drop 1.12 specific code [#1850](https://github.com/labstack/echo/pull/1850)
**Enhancements**
* Make KeyFunc public in JWT middleware [#1756](https://github.com/labstack/echo/pull/1756)
* Add support for optional filesystem to the static middleware [#1797](https://github.com/labstack/echo/pull/1797)
* Add a custom error handler to key-auth middleware [#1847](https://github.com/labstack/echo/pull/1847)
* Allow JWT token to be looked up from multiple sources [#1845](https://github.com/labstack/echo/pull/1845)
## v4.2.2 - 2021-04-07
**Fixes**
* Allow proxy middleware to use query part in rewrite (#1802)
* Fix timeout middleware not sending status code when handler returns an error (#1805)
* Fix Bind() when target is array/slice and path/query params complains bind target not being struct (#1835)
* Fix panic in redirect middleware on short host name (#1813)
* Fix timeout middleware docs (#1836)
## v4.2.1 - 2021-03-08
**Important notes**
Due to a datarace the config parameters for the newly added timeout middleware required a change.
See the [docs](https://echo.labstack.com/middleware/timeout).
A performance regression has been fixed, even bringing better performance than before for some routing scenarios.
**Fixes**
* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas)
* Avoid context canceled errors (#1789, clwluvw)
* Improve router to use on stack backtracking (#1791, aldas, stffabi)
* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas)
* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas)
* Apply go fmt (#1788, Le0tk0k)
* Uses strings.Equalfold (#1790, rkilingr)
* Improve code quality (#1792, withshubh)
This release was made possible by our **contributors**:
aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh
## v4.2.0 - 2021-02-11
**Important notes**
The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by
enforcing `explicit tagging` for processing parameters. This **may break** your code if you
expect combined handling of query/path/form params.
Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request)
The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules.
Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details.
**Security**
* Fix directory traversal vulnerability for Windows (#1718, little-cui)
* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye)
**Enhancements**
* Add Echo#ListenerNetwork as configuration (#1667, pafuent)
* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari)
* Echo server startup to allow data race free access to listener address
* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas)
* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas)
* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas)
* Router: Performance improvements for missed routes (#1689, pafuent)
* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb)
* Middleware: Support real regex rules for rewrite and proxy middleware (#1767)
* Middleware: New rate limiting middleware (#1724, iambenkay)
* Middleware: New timeout middleware implementation for go1.13+ (#1743, )
* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew)
* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay)
* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid)
* Middleware: Support form fields in JWT middleware (#1704, rkfg)
* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent)
* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009)
* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni)
* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head)
**Fixes**
* Fix handling of special trailing slash case for partial prefix (#1741, stffabi)
* Fix handling of static routes with trailing slash (#1747)
* Fix Static files route not working (#1671, pwli0755, lammel)
* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow)
* Fix Echo#Reverse for Any type routes (#1695, pafuent)
* Fix Router#Find panic with infinite loop (#1661, pafuent)
* Fix Router#Find panic fails on Param paths (#1659, pafuent)
* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel)
* Fix incorrect CORS headers (#1669, ulasakdeniz)
* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009)
* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009)
* Remove unless defer (#1656, imxyb)
**General**
* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent)
* Add GitHub action to compare benchmarks (#1702, pafuent)
* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas)
* Add support for Go 1.15 in CI (#1683, asahasrabuddhe)
* Add test for request id to remain unchanged if provided (#1719, iambenkay)
* Refactor echo instance listener access and startup to speed up testing (#1735, aldas)
* Refactor and improve various tests for binding and routing
* Run test workflow only for relevant changes (#1637, #1636, pofl)
* Update .travis.yml (#1662, santosh653)
* Update README.md with an recents framework benchmark (#1679, pafuent)
This release was made possible by **over 100 commits** from more than **20 contributors**:
asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb,
juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari,
rkfg, santosh653, segfiner, stffabi, ulasakdeniz

21
vendor/github.com/labstack/echo/v4/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 LabStack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
vendor/github.com/labstack/echo/v4/Makefile generated vendored Normal file
View File

@@ -0,0 +1,34 @@
PKG := "github.com/labstack/echo"
PKG_LIST := $(shell go list ${PKG}/...)
tag:
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
@git tag|grep -v ^v
.DEFAULT_GOAL := check
check: lint vet race ## Check project
init:
@go get -u golang.org/x/lint/golint
lint: ## Lint the files
@golint -set_exit_status ${PKG_LIST}
vet: ## Vet the files
@go vet ${PKG_LIST}
test: ## Run tests
@go test -short ${PKG_LIST}
race: ## Run tests with data race detector
@go test -race ${PKG_LIST}
benchmark: ## Run benchmarks
@go test -run="-" -bench=".*" ${PKG_LIST}
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.15"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

138
vendor/github.com/labstack/echo/v4/README.md generated vendored Normal file
View File

@@ -0,0 +1,138 @@
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Supported Go versions
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required:
- 1.9.7+
- 1.10.3+
- 1.14+
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward.
For older versions, please use the latest v3 tag.
## Feature Overview
- Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs
- Group APIs
- Extensible middleware framework
- Define middleware at root, group or route level
- Data binding for JSON, XML and form payload
- Handy functions to send variety of HTTP responses
- Centralized HTTP error handling
- Template rendering with any template engine
- Define your format for the logger
- Highly customizable
- Automatic TLS via Lets Encrypt
- HTTP/2 support
## Benchmarks
Date: 2020/11/11<br>
Source: https://github.com/vishr/web-framework-benchmark<br>
Lower is better!
<img src="https://i.imgur.com/qwPNQbl.png">
<img src="https://i.imgur.com/s8yKQjx.png">
The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
## [Guide](https://echo.labstack.com/guide)
### Installation
```sh
// go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v4
```
### Example
```go
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
)
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
// Handler
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
```
# Third-party middlewares
| Repository | Description |
|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares |
| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo.
Please send a PR to add your own library here.
## Help
- [Forum](https://github.com/labstack/echo/discussions)
## Contribute
**Use issues for everything**
- For a small change, just send a PR.
- For bigger changes open an issue for discussion before sending a PR.
- PR should have:
- Test case
- Documentation
- Example (If it makes sense)
- You can also contribute by:
- Reporting issues
- Suggesting new features or enhancements
- Improve/fix documentation
## Credits
- [Vishal Rana](https://github.com/vishr) (Author)
- [Nitin Rana](https://github.com/nr17) (Consultant)
- [Roland Lammel](https://github.com/lammel) (Maintainer)
- [Martti T.](https://github.com/aldas) (Maintainer)
- [Pablo Andres Fuente](https://github.com/pafuent) (Maintainer)
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
## License
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)

340
vendor/github.com/labstack/echo/v4/bind.go generated vendored Normal file
View File

@@ -0,0 +1,340 @@
package echo
import (
"encoding"
"encoding/xml"
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
)
type (
// Binder is the interface that wraps the Bind method.
Binder interface {
Bind(i interface{}, c Context) error
}
// DefaultBinder is the default implementation of the Binder interface.
DefaultBinder struct{}
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
// Types that don't implement this, but do implement encoding.TextUnmarshaler
// will use that interface instead.
BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error
}
)
// BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
names := c.ParamNames()
values := c.ParamValues()
params := map[string][]string{}
for i, name := range names {
params[name] = []string{values[i]}
}
if err := b.bindData(i, params, "param"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return nil
}
// BindQueryParams binds query params to bindable object
func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
if err := b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return nil
}
// BindBody binds request body contents to bindable object
// NB: then binding forms take note that this implementation uses standard library form parsing
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
req := c.Request()
if req.ContentLength == 0 {
return
}
ctype := req.Header.Get(HeaderContentType)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON):
if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil {
switch err.(type) {
case *HTTPError:
return err
default:
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
}
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
} else if se, ok := err.(*xml.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
params, err := c.FormParams()
if err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
if err = b.bindData(i, params, "form"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
default:
return ErrUnsupportedMediaType
}
return nil
}
// BindHeaders binds HTTP headers to a bindable object
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
if err := b.bindData(i, c.Request().Header, "header"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return nil
}
// Bind implements the `Binder#Bind` function.
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil {
return err
}
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err = b.BindQueryParams(c, i); err != nil {
return err
}
}
return b.BindBody(c, i)
}
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
if destination == nil || len(data) == 0 {
return nil
}
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()
// Map
if typ.Kind() == reflect.Map {
for k, v := range data {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
}
return nil
}
// !struct
if typ.Kind() != reflect.Struct {
if tag == "param" || tag == "query" || tag == "header" {
// incompatible type, data is probably to be found in the body
return nil
}
return errors.New("binding element must be a struct")
}
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
if typeField.Anonymous {
if structField.Kind() == reflect.Ptr {
structField = structField.Elem()
}
}
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
// structs that implement BindUnmarshaler are binded only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
}
// does not have explicit tag and is not an ordinary struct - so move to next field
continue
}
inputValue, exists := data[inputFieldName]
if !exists {
// Go json.Unmarshal supports case insensitive binding. However the
// url params are bound case sensitive which is inconsistent. To
// fix this we must check all of the map values in a
// case-insensitive search.
for k, v := range data {
if strings.EqualFold(k, inputFieldName) {
inputValue = v
exists = true
break
}
}
}
if !exists {
continue
}
// Call this first, in case we're dealing with an alias to an array type
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
if err != nil {
return err
}
continue
}
numElems := len(inputValue)
if structFieldKind == reflect.Slice && numElems > 0 {
sliceOf := structField.Type().Elem().Kind()
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
val.Field(i).Set(slice)
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
}
}
return nil
}
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
// But also call it here, in case we're dealing with an array of BindUnmarshalers
if ok, err := unmarshalField(valueKind, val, structField); ok {
return err
}
switch valueKind {
case reflect.Ptr:
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
case reflect.Int:
return setIntField(val, 0, structField)
case reflect.Int8:
return setIntField(val, 8, structField)
case reflect.Int16:
return setIntField(val, 16, structField)
case reflect.Int32:
return setIntField(val, 32, structField)
case reflect.Int64:
return setIntField(val, 64, structField)
case reflect.Uint:
return setUintField(val, 0, structField)
case reflect.Uint8:
return setUintField(val, 8, structField)
case reflect.Uint16:
return setUintField(val, 16, structField)
case reflect.Uint32:
return setUintField(val, 32, structField)
case reflect.Uint64:
return setUintField(val, 64, structField)
case reflect.Bool:
return setBoolField(val, structField)
case reflect.Float32:
return setFloatField(val, 32, structField)
case reflect.Float64:
return setFloatField(val, 64, structField)
case reflect.String:
structField.SetString(val)
default:
return errors.New("unknown type")
}
return nil
}
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
switch valueKind {
case reflect.Ptr:
return unmarshalFieldPtr(val, field)
default:
return unmarshalFieldNonPtr(val, field)
}
}
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
fieldIValue := field.Addr().Interface()
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
return true, unmarshaler.UnmarshalParam(value)
}
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
return true, unmarshaler.UnmarshalText([]byte(value))
}
return false, nil
}
func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
if field.IsNil() {
// Initialize the pointer to a nil value
field.Set(reflect.New(field.Type().Elem()))
}
return unmarshalFieldNonPtr(value, field.Elem())
}
func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
intVal, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.SetInt(intVal)
}
return err
}
func setUintField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
uintVal, err := strconv.ParseUint(value, 10, bitSize)
if err == nil {
field.SetUint(uintVal)
}
return err
}
func setBoolField(value string, field reflect.Value) error {
if value == "" {
value = "false"
}
boolVal, err := strconv.ParseBool(value)
if err == nil {
field.SetBool(boolVal)
}
return err
}
func setFloatField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0.0"
}
floatVal, err := strconv.ParseFloat(value, bitSize)
if err == nil {
field.SetFloat(floatVal)
}
return err
}

1230
vendor/github.com/labstack/echo/v4/binder.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

11
vendor/github.com/labstack/echo/v4/codecov.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
coverage:
status:
project:
default:
threshold: 1%
patch:
default:
threshold: 1%
comment:
require_changes: true

638
vendor/github.com/labstack/echo/v4/context.go generated vendored Normal file
View File

@@ -0,0 +1,638 @@
package echo
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/url"
"strings"
"sync"
)
type (
// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
Context interface {
// Request returns `*http.Request`.
Request() *http.Request
// SetRequest sets `*http.Request`.
SetRequest(r *http.Request)
// SetResponse sets `*Response`.
SetResponse(r *Response)
// Response returns `*Response`.
Response() *Response
// IsTLS returns true if HTTP connection is TLS otherwise false.
IsTLS() bool
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
IsWebSocket() bool
// Scheme returns the HTTP protocol scheme, `http` or `https`.
Scheme() string
// RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header.
// The behavior can be configured using `Echo#IPExtractor`.
RealIP() string
// Path returns the registered path for the handler.
Path() string
// SetPath sets the registered path for the handler.
SetPath(p string)
// Param returns path parameter by name.
Param(name string) string
// ParamNames returns path parameter names.
ParamNames() []string
// SetParamNames sets path parameter names.
SetParamNames(names ...string)
// ParamValues returns path parameter values.
ParamValues() []string
// SetParamValues sets path parameter values.
SetParamValues(values ...string)
// QueryParam returns the query param for the provided name.
QueryParam(name string) string
// QueryParams returns the query parameters as `url.Values`.
QueryParams() url.Values
// QueryString returns the URL query string.
QueryString() string
// FormValue returns the form field value for the provided name.
FormValue(name string) string
// FormParams returns the form parameters as `url.Values`.
FormParams() (url.Values, error)
// FormFile returns the multipart form file for the provided name.
FormFile(name string) (*multipart.FileHeader, error)
// MultipartForm returns the multipart form.
MultipartForm() (*multipart.Form, error)
// Cookie returns the named cookie provided in the request.
Cookie(name string) (*http.Cookie, error)
// SetCookie adds a `Set-Cookie` header in HTTP response.
SetCookie(cookie *http.Cookie)
// Cookies returns the HTTP cookies sent with the request.
Cookies() []*http.Cookie
// Get retrieves data from the context.
Get(key string) interface{}
// Set saves data in the context.
Set(key string, val interface{})
// Bind binds the request body into provided type `i`. The default binder
// does it based on Content-Type header.
Bind(i interface{}) error
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
// Validator must be registered using `Echo#Validator`.
Validate(i interface{}) error
// Render renders a template with data and sends a text/html response with status
// code. Renderer must be registered using `Echo.Renderer`.
Render(code int, name string, data interface{}) error
// HTML sends an HTTP response with status code.
HTML(code int, html string) error
// HTMLBlob sends an HTTP blob response with status code.
HTMLBlob(code int, b []byte) error
// String sends a string response with status code.
String(code int, s string) error
// JSON sends a JSON response with status code.
JSON(code int, i interface{}) error
// JSONPretty sends a pretty-print JSON with status code.
JSONPretty(code int, i interface{}, indent string) error
// JSONBlob sends a JSON blob response with status code.
JSONBlob(code int, b []byte) error
// JSONP sends a JSONP response with status code. It uses `callback` to construct
// the JSONP payload.
JSONP(code int, callback string, i interface{}) error
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
// to construct the JSONP payload.
JSONPBlob(code int, callback string, b []byte) error
// XML sends an XML response with status code.
XML(code int, i interface{}) error
// XMLPretty sends a pretty-print XML with status code.
XMLPretty(code int, i interface{}, indent string) error
// XMLBlob sends an XML blob response with status code.
XMLBlob(code int, b []byte) error
// Blob sends a blob response with status code and content type.
Blob(code int, contentType string, b []byte) error
// Stream sends a streaming response with status code and content type.
Stream(code int, contentType string, r io.Reader) error
// File sends a response with the content of the file.
File(file string) error
// Attachment sends a response as attachment, prompting client to save the
// file.
Attachment(file string, name string) error
// Inline sends a response as inline, opening the file in the browser.
Inline(file string, name string) error
// NoContent sends a response with no body and a status code.
NoContent(code int) error
// Redirect redirects the request to a provided URL with status code.
Redirect(code int, url string) error
// Error invokes the registered HTTP error handler. Generally used by middleware.
Error(err error)
// Handler returns the matched handler by router.
Handler() HandlerFunc
// SetHandler sets the matched handler by router.
SetHandler(h HandlerFunc)
// Logger returns the `Logger` instance.
Logger() Logger
// Set the logger
SetLogger(l Logger)
// Echo returns the `Echo` instance.
Echo() *Echo
// Reset resets the context after request completes. It must be called along
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
// See `Echo#ServeHTTP()`
Reset(r *http.Request, w http.ResponseWriter)
}
context struct {
request *http.Request
response *Response
path string
pnames []string
pvalues []string
query url.Values
handler HandlerFunc
store Map
echo *Echo
logger Logger
lock sync.RWMutex
}
)
const (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
// Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests.
// It is added to context only when Router does not find matching method handler for request.
ContextKeyHeaderAllow = "echo_header_allow"
)
const (
defaultMemory = 32 << 20 // 32 MB
indexPage = "index.html"
defaultIndent = " "
)
func (c *context) writeContentType(value string) {
header := c.Response().Header()
if header.Get(HeaderContentType) == "" {
header.Set(HeaderContentType, value)
}
}
func (c *context) Request() *http.Request {
return c.request
}
func (c *context) SetRequest(r *http.Request) {
c.request = r
}
func (c *context) Response() *Response {
return c.response
}
func (c *context) SetResponse(r *Response) {
c.response = r
}
func (c *context) IsTLS() bool {
return c.request.TLS != nil
}
func (c *context) IsWebSocket() bool {
upgrade := c.request.Header.Get(HeaderUpgrade)
return strings.EqualFold(upgrade, "websocket")
}
func (c *context) Scheme() string {
// Can't use `r.Request.URL.Scheme`
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
if c.IsTLS() {
return "https"
}
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
return scheme
}
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
return scheme
}
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
return "https"
}
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
return scheme
}
return "http"
}
func (c *context) RealIP() string {
if c.echo != nil && c.echo.IPExtractor != nil {
return c.echo.IPExtractor(c.request)
}
// Fall back to legacy behavior
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
i := strings.IndexAny(ip, ",")
if i > 0 {
return strings.TrimSpace(ip[:i])
}
return ip
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
return ip
}
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
return ra
}
func (c *context) Path() string {
return c.path
}
func (c *context) SetPath(p string) {
c.path = p
}
func (c *context) Param(name string) string {
for i, n := range c.pnames {
if i < len(c.pvalues) {
if n == name {
return c.pvalues[i]
}
}
}
return ""
}
func (c *context) ParamNames() []string {
return c.pnames
}
func (c *context) SetParamNames(names ...string) {
c.pnames = names
l := len(names)
if *c.echo.maxParam < l {
*c.echo.maxParam = l
}
if len(c.pvalues) < l {
// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
// probably those values will be overriden in a Context#SetParamValues
newPvalues := make([]string, l)
copy(newPvalues, c.pvalues)
c.pvalues = newPvalues
}
}
func (c *context) ParamValues() []string {
return c.pvalues[:len(c.pnames)]
}
func (c *context) SetParamValues(values ...string) {
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
// It will brake the Router#Find code
limit := len(values)
if limit > *c.echo.maxParam {
limit = *c.echo.maxParam
}
for i := 0; i < limit; i++ {
c.pvalues[i] = values[i]
}
}
func (c *context) QueryParam(name string) string {
if c.query == nil {
c.query = c.request.URL.Query()
}
return c.query.Get(name)
}
func (c *context) QueryParams() url.Values {
if c.query == nil {
c.query = c.request.URL.Query()
}
return c.query
}
func (c *context) QueryString() string {
return c.request.URL.RawQuery
}
func (c *context) FormValue(name string) string {
return c.request.FormValue(name)
}
func (c *context) FormParams() (url.Values, error) {
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
return nil, err
}
} else {
if err := c.request.ParseForm(); err != nil {
return nil, err
}
}
return c.request.Form, nil
}
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
f, fh, err := c.request.FormFile(name)
if err != nil {
return nil, err
}
f.Close()
return fh, nil
}
func (c *context) MultipartForm() (*multipart.Form, error) {
err := c.request.ParseMultipartForm(defaultMemory)
return c.request.MultipartForm, err
}
func (c *context) Cookie(name string) (*http.Cookie, error) {
return c.request.Cookie(name)
}
func (c *context) SetCookie(cookie *http.Cookie) {
http.SetCookie(c.Response(), cookie)
}
func (c *context) Cookies() []*http.Cookie {
return c.request.Cookies()
}
func (c *context) Get(key string) interface{} {
c.lock.RLock()
defer c.lock.RUnlock()
return c.store[key]
}
func (c *context) Set(key string, val interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if c.store == nil {
c.store = make(Map)
}
c.store[key] = val
}
func (c *context) Bind(i interface{}) error {
return c.echo.Binder.Bind(i, c)
}
func (c *context) Validate(i interface{}) error {
if c.echo.Validator == nil {
return ErrValidatorNotRegistered
}
return c.echo.Validator.Validate(i)
}
func (c *context) Render(code int, name string, data interface{}) (err error) {
if c.echo.Renderer == nil {
return ErrRendererNotRegistered
}
buf := new(bytes.Buffer)
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
return
}
return c.HTMLBlob(code, buf.Bytes())
}
func (c *context) HTML(code int, html string) (err error) {
return c.HTMLBlob(code, []byte(html))
}
func (c *context) HTMLBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
}
func (c *context) String(code int, s string) (err error) {
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
}
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
return
}
if err = c.echo.JSONSerializer.Serialize(c, i, indent); err != nil {
return
}
if _, err = c.response.Write([]byte(");")); err != nil {
return
}
return
}
func (c *context) json(code int, i interface{}, indent string) error {
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
c.response.Status = code
return c.echo.JSONSerializer.Serialize(c, i, indent)
}
func (c *context) JSON(code int, i interface{}) (err error) {
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
return c.json(code, i, indent)
}
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
return c.json(code, i, indent)
}
func (c *context) JSONBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
}
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
return c.jsonPBlob(code, callback, i)
}
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
return
}
if _, err = c.response.Write(b); err != nil {
return
}
_, err = c.response.Write([]byte(");"))
return
}
func (c *context) xml(code int, i interface{}, indent string) (err error) {
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
c.response.WriteHeader(code)
enc := xml.NewEncoder(c.response)
if indent != "" {
enc.Indent("", indent)
}
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
return
}
return enc.Encode(i)
}
func (c *context) XML(code int, i interface{}) (err error) {
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
return c.xml(code, i, indent)
}
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
return c.xml(code, i, indent)
}
func (c *context) XMLBlob(code int, b []byte) (err error) {
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
return
}
_, err = c.response.Write(b)
return
}
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
c.writeContentType(contentType)
c.response.WriteHeader(code)
_, err = c.response.Write(b)
return
}
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
c.writeContentType(contentType)
c.response.WriteHeader(code)
_, err = io.Copy(c.response, r)
return
}
func (c *context) Attachment(file, name string) error {
return c.contentDisposition(file, name, "attachment")
}
func (c *context) Inline(file, name string) error {
return c.contentDisposition(file, name, "inline")
}
func (c *context) contentDisposition(file, name, dispositionType string) error {
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
return c.File(file)
}
func (c *context) NoContent(code int) error {
c.response.WriteHeader(code)
return nil
}
func (c *context) Redirect(code int, url string) error {
if code < 300 || code > 308 {
return ErrInvalidRedirectCode
}
c.response.Header().Set(HeaderLocation, url)
c.response.WriteHeader(code)
return nil
}
func (c *context) Error(err error) {
c.echo.HTTPErrorHandler(err, c)
}
func (c *context) Echo() *Echo {
return c.echo
}
func (c *context) Handler() HandlerFunc {
return c.handler
}
func (c *context) SetHandler(h HandlerFunc) {
c.handler = h
}
func (c *context) Logger() Logger {
res := c.logger
if res != nil {
return res
}
return c.echo.Logger
}
func (c *context) SetLogger(l Logger) {
c.logger = l
}
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.request = r
c.response.reset(w)
c.query = nil
c.handler = NotFoundHandler
c.store = nil
c.path = ""
c.pnames = nil
c.logger = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
for i := 0; i < *c.echo.maxParam; i++ {
c.pvalues[i] = ""
}
}

33
vendor/github.com/labstack/echo/v4/context_fs.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
//go:build !go1.16
// +build !go1.16
package echo
import (
"net/http"
"os"
"path/filepath"
)
func (c *context) File(file string) (err error) {
f, err := os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.Join(file, indexPage)
f, err = os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return
}
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
return
}

View File

@@ -0,0 +1,52 @@
//go:build go1.16
// +build go1.16
package echo
import (
"errors"
"io"
"io/fs"
"net/http"
"path/filepath"
)
func (c *context) File(file string) error {
return fsFile(c, file, c.echo.Filesystem)
}
// FileFS serves file from given file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (c *context) FileFS(file string, filesystem fs.FS) error {
return fsFile(c, file, filesystem)
}
func fsFile(c Context, file string, filesystem fs.FS) error {
f, err := filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
f, err = filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return err
}
}
ff, ok := f.(io.ReadSeeker)
if !ok {
return errors.New("file does not implement io.ReadSeeker")
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
return nil
}

966
vendor/github.com/labstack/echo/v4/echo.go generated vendored Normal file
View File

@@ -0,0 +1,966 @@
/*
Package echo implements high performance, minimalist Go web framework.
Example:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// Handler
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
Learn more at https://echo.labstack.com
*/
package echo
import (
"bytes"
stdContext "context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
stdLog "log"
"net"
"net/http"
"reflect"
"runtime"
"sync"
"time"
"github.com/labstack/gommon/color"
"github.com/labstack/gommon/log"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
type (
// Echo is the top-level framework instance.
Echo struct {
filesystem
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
startupMutex sync.RWMutex
StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
router *Router
routers map[string]*Router
pool sync.Pool
Server *http.Server
TLSServer *http.Server
Listener net.Listener
TLSListener net.Listener
AutoTLSManager autocert.Manager
DisableHTTP2 bool
Debug bool
HideBanner bool
HidePort bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
JSONSerializer JSONSerializer
Validator Validator
Renderer Renderer
Logger Logger
IPExtractor IPExtractor
ListenerNetwork string
}
// Route contains a handler and information for matching against requests.
Route struct {
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 `json:"-"`
Message interface{} `json:"message"`
Internal error `json:"-"` // Stores the error returned by an external dependency
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(next HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
// Validator is the interface that wraps the Validate function.
Validator interface {
Validate(i interface{}) error
}
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
JSONSerializer interface {
Serialize(c Context, i interface{}, indent string) error
Deserialize(c Context, i interface{}) error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
// Map defines a generic map of type `map[string]interface{}`.
Map map[string]interface{}
// Common struct for Echo & Group.
common struct{}
)
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
const (
CONNECT = http.MethodConnect
DELETE = http.MethodDelete
GET = http.MethodGet
HEAD = http.MethodHead
OPTIONS = http.MethodOptions
PATCH = http.MethodPatch
POST = http.MethodPost
// PROPFIND = "PROPFIND"
PUT = http.MethodPut
TRACE = http.MethodTrace
)
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
MIMEApplicationXML = "application/xml"
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
MIMETextXML = "text/xml"
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack"
MIMETextHTML = "text/html"
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain = "text/plain"
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
MIMEMultipartForm = "multipart/form-data"
MIMEOctetStream = "application/octet-stream"
)
const (
charsetUTF8 = "charset=UTF-8"
// PROPFIND Method can be used on collection and property resources.
PROPFIND = "PROPFIND"
// REPORT Method can be used to get information about a resource, see rfc 3253
REPORT = "REPORT"
)
// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
// HeaderAllow is the name of the "Allow" header field used to list the set of methods
// advertised as supported by the target resource. Returning an Allow header is mandatory
// for status 405 (method not found) and useful for the OPTIONS method in responses.
// See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
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"
HeaderRetryAfter = "Retry-After"
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"
HeaderXCorrelationID = "X-Correlation-Id"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"
HeaderCacheControl = "Cache-Control"
HeaderConnection = "Connection"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderXCSRFToken = "X-CSRF-Token"
HeaderReferrerPolicy = "Referrer-Policy"
)
const (
// Version of Echo
Version = "4.7.0"
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{
http.MethodConnect,
http.MethodDelete,
http.MethodGet,
http.MethodHead,
http.MethodOptions,
http.MethodPatch,
http.MethodPost,
PROPFIND,
http.MethodPut,
http.MethodTrace,
REPORT,
}
)
// Errors
var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
ErrNotFound = NewHTTPError(http.StatusNotFound)
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
ErrForbidden = NewHTTPError(http.StatusForbidden)
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests)
ErrBadRequest = NewHTTPError(http.StatusBadRequest)
ErrBadGateway = NewHTTPError(http.StatusBadGateway)
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError)
ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout)
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable)
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")
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte")
ErrInvalidListenerNetwork = errors.New("invalid listener network")
)
// Error handlers
var (
NotFoundHandler = func(c Context) error {
return ErrNotFound
}
MethodNotAllowedHandler = func(c Context) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
if ok && routerAllowMethods != "" {
c.Response().Header().Set(HeaderAllow, routerAllowMethods)
}
return ErrMethodNotAllowed
}
)
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{
filesystem: createFilesystem(),
Server: new(http.Server),
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
Logger: log.New("echo"),
colorer: color.New(),
maxParam: new(int),
ListenerNetwork: "tcp",
}
e.Server.Handler = e
e.TLSServer.Handler = e
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{}
e.JSONSerializer = &DefaultJSONSerializer{}
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)
}
e.router = NewRouter(e)
e.routers = map[string]*Router{}
return
}
// NewContext returns a Context instance.
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
return &context{
request: r,
response: NewResponse(w, e),
store: make(Map),
echo: e,
pvalues: make([]string, *e.maxParam),
handler: NotFoundHandler,
}
}
// Router returns the default router.
func (e *Echo) Router() *Router {
return e.router
}
// Routers returns the map of host => router.
func (e *Echo) Routers() map[string]*Router {
return e.routers
}
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code.
//
// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from
// handler. Then the error that global error handler received will be ignored because we have already "commited" the
// response and status code header has been sent to the client.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if c.Response().Committed {
return
}
he, ok := err.(*HTTPError)
if ok {
if he.Internal != nil {
if herr, ok := he.Internal.(*HTTPError); ok {
he = herr
}
}
} else {
he = &HTTPError{
Code: http.StatusInternalServerError,
Message: http.StatusText(http.StatusInternalServerError),
}
}
// Issue #1426
code := he.Code
message := he.Message
if m, ok := he.Message.(string); ok {
if e.Debug {
message = Map{"message": m, "error": err.Error()}
} else {
message = Map{"message": m}
}
}
// Send response
if c.Request().Method == http.MethodHead { // Issue #608
err = c.NoContent(he.Code)
} else {
err = c.JSON(code, message)
}
if err != nil {
e.Logger.Error(err)
}
}
// Pre adds middleware to the chain which is run before router.
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
e.premiddleware = append(e.premiddleware, middleware...)
}
// Use adds middleware to the chain which is run after router.
func (e *Echo) Use(middleware ...MiddlewareFunc) {
e.middleware = append(e.middleware, middleware...)
}
// 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) *Route {
return e.Add(http.MethodConnect, 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) *Route {
return e.Add(http.MethodDelete, 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) *Route {
return e.Add(http.MethodGet, 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) *Route {
return e.Add(http.MethodHead, 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) *Route {
return e.Add(http.MethodOptions, 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) *Route {
return e.Add(http.MethodPatch, 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) *Route {
return e.Add(http.MethodPost, 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) *Route {
return e.Add(http.MethodPut, 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) *Route {
return e.Add(http.MethodTrace, 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) []*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) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = e.Add(m, path, handler, middleware...)
}
return routes
}
func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
m ...MiddlewareFunc) *Route {
return get(path, func(c Context) error {
return c.File(file)
}, m...)
}
// 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.file(path, file, e.GET, m...)
}
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
name := handlerName(handler)
router := e.findRouter(host)
router.Add(method, path, func(c Context) error {
h := applyMiddleware(handler, middleware...)
return h(c)
})
r := &Route{
Method: method,
Path: path,
Name: name,
}
e.router.routes[method+path] = r
return r
}
// 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 {
return e.add("", method, path, handler, middleware...)
}
// Host creates a new router group for the provided host and optional host-level middleware.
func (e *Echo) Host(name string, m ...MiddlewareFunc) (g *Group) {
e.routers[name] = NewRouter(e)
g = &Group{host: name, echo: e}
g.Use(m...)
return
}
// Group creates a new router group with prefix and optional group-level middleware.
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
g = &Group{prefix: prefix, echo: e}
g.Use(m...)
return
}
// 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
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
}
// Routes returns the registered routes.
func (e *Echo) Routes() []*Route {
routes := make([]*Route, 0, len(e.router.routes))
for _, v := range e.router.routes {
routes = append(routes, v)
}
return routes
}
// AcquireContext returns an empty `Context` instance from the pool.
// You must return the context by calling `ReleaseContext()`.
func (e *Echo) AcquireContext() Context {
return e.pool.Get().(Context)
}
// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (e *Echo) ReleaseContext(c Context) {
e.pool.Put(c)
}
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
var h func(Context) error
if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h = c.Handler()
h = applyMiddleware(h, e.middleware...)
} else {
h = func(c Context) error {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h := c.Handler()
h = applyMiddleware(h, e.middleware...)
return h(c)
}
h = applyMiddleware(h, e.premiddleware...)
}
// Execute chain
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
// Release context
e.pool.Put(c)
}
// Start starts an HTTP server.
func (e *Echo) Start(address string) error {
e.startupMutex.Lock()
e.Server.Addr = address
if err := e.configureServer(e.Server); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return e.Server.Serve(e.Listener)
}
// StartTLS starts an HTTPS server.
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
e.startupMutex.Lock()
var cert []byte
if cert, err = filepathOrContent(certFile); err != nil {
e.startupMutex.Unlock()
return
}
var key []byte
if key, err = filepathOrContent(keyFile); err != nil {
e.startupMutex.Unlock()
return
}
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
e.startupMutex.Unlock()
return
}
e.configureTLS(address)
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
switch v := fileOrContent.(type) {
case string:
return ioutil.ReadFile(v)
case []byte:
return v, nil
default:
return nil, ErrInvalidCertOrKeyType
}
}
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
func (e *Echo) StartAutoTLS(address string) error {
e.startupMutex.Lock()
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
e.configureTLS(address)
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
func (e *Echo) configureTLS(address string) {
s := e.TLSServer
s.Addr = address
if !e.DisableHTTP2 {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
}
}
// StartServer starts a custom http server.
func (e *Echo) StartServer(s *http.Server) (err error) {
e.startupMutex.Lock()
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
if s.TLSConfig != nil {
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
e.startupMutex.Unlock()
return s.Serve(e.Listener)
}
func (e *Echo) configureServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
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 {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
return nil
}
if e.TLSListener == nil {
l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
e.TLSListener = tls.NewListener(l, s.TLSConfig)
}
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return nil
}
// ListenerAddr returns net.Addr for Listener
func (e *Echo) ListenerAddr() net.Addr {
e.startupMutex.RLock()
defer e.startupMutex.RUnlock()
if e.Listener == nil {
return nil
}
return e.Listener.Addr()
}
// TLSListenerAddr returns net.Addr for TLSListener
func (e *Echo) TLSListenerAddr() net.Addr {
e.startupMutex.RLock()
defer e.startupMutex.RUnlock()
if e.TLSListener == nil {
return nil
}
return e.TLSListener.Addr()
}
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
e.startupMutex.Lock()
// Setup
s := e.Server
s.Addr = address
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
s.Handler = h2c.NewHandler(e, h2s)
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 e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
e.startupMutex.Unlock()
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
e.startupMutex.Unlock()
return s.Serve(e.Listener)
}
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
e.startupMutex.Lock()
defer e.startupMutex.Unlock()
if err := e.TLSServer.Close(); err != nil {
return err
}
return e.Server.Close()
}
// Shutdown stops the server gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
e.startupMutex.Lock()
defer e.startupMutex.Unlock()
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)}
if len(message) > 0 {
he.Message = message[0]
}
return he
}
// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
if he.Internal == nil {
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
}
return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal)
}
// SetInternal sets error to HTTPError.Internal
func (he *HTTPError) SetInternal(err error) *HTTPError {
he.Internal = err
return he
}
// Unwrap satisfies the Go 1.13 error wrapper interface.
func (he *HTTPError) Unwrap() error {
return he.Internal
}
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
func WrapHandler(h http.Handler) HandlerFunc {
return func(c Context) error {
h.ServeHTTP(c.Response(), c.Request())
return nil
}
}
// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
return func(next HandlerFunc) HandlerFunc {
return func(c Context) (err error) {
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.SetRequest(r)
c.SetResponse(NewResponse(w, c.Echo()))
err = next(c)
})).ServeHTTP(c.Response(), c.Request())
return
}
}
}
// GetPath returns RawPath, if it's empty returns Path from URL
// Difference between RawPath and Path is:
// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
// * RawPath is an optional field which only gets set if the default encoding is different from Path.
func GetPath(r *http.Request) string {
path := r.URL.RawPath
if path == "" {
path = r.URL.Path
}
return path
}
func (e *Echo) findRouter(host string) *Router {
if len(e.routers) > 0 {
if r, ok := e.routers[host]; ok {
return r
}
}
return e.router
}
func handlerName(h HandlerFunc) string {
t := reflect.ValueOf(h).Type()
if t.Kind() == reflect.Func {
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}
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
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
if c, err = ln.AcceptTCP(); err != nil {
return
} else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil {
return
}
// Ignore error from setting the KeepAlivePeriod as some systems, such as
// OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP
_ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute)
return
}
func newListener(address, network string) (*tcpKeepAliveListener, error) {
if network != "tcp" && network != "tcp4" && network != "tcp6" {
return nil, ErrInvalidListenerNetwork
}
l, err := net.Listen(network, address)
if err != nil {
return nil, err
}
return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
}
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h
}

62
vendor/github.com/labstack/echo/v4/echo_fs.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
//go:build !go1.16
// +build !go1.16
package echo
import (
"net/http"
"net/url"
"os"
"path/filepath"
)
type filesystem struct {
}
func createFilesystem() filesystem {
return filesystem{}
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
return e.static(prefix, root, e.GET)
}
func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
h := func(c Context) error {
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
// The access path does not exist
return NotFoundHandler(c)
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, p+"/")
}
return c.File(name)
}
// Handle added routes based on trailing slash:
// /prefix => exact route "/prefix" + any route "/prefix/*"
// /prefix/ => only any route "/prefix/*"
if prefix != "" {
if prefix[len(prefix)-1] == '/' {
// Only add any route for intentional trailing slash
return get(prefix+"*", h)
}
get(prefix, h)
}
return get(prefix+"/*", h)
}

145
vendor/github.com/labstack/echo/v4/echo_fs_go1.16.go generated vendored Normal file
View File

@@ -0,0 +1,145 @@
//go:build go1.16
// +build go1.16
package echo
import (
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
type filesystem struct {
// Filesystem is file system used by Static and File handlers to access files.
// Defaults to os.DirFS(".")
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
Filesystem fs.FS
}
func createFilesystem() filesystem {
return filesystem{
Filesystem: newDefaultFS(),
}
}
// Static registers a new route with path prefix to serve static files from the provided root directory.
func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
subFs := MustSubFS(e.Filesystem, fsRoot)
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(subFs, false),
)
}
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(filesystem, false),
)
}
// StaticDirectoryHandler creates handler function to serve files from provided file system
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
return func(c Context) error {
p := c.Param("*")
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
tmpPath, err := url.PathUnescape(p)
if err != nil {
return fmt.Errorf("failed to unescape path variable: %w", err)
}
p = tmpPath
}
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
fi, err := fs.Stat(fileSystem, name)
if err != nil {
return ErrNotFound
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, p+"/")
}
return fsFile(c, name, fileSystem)
}
}
// FileFS registers a new route with path to serve file from the provided file system.
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
return e.GET(path, StaticFileHandler(file, filesystem), m...)
}
// StaticFileHandler creates handler function to serve file from provided file system
func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
return func(c Context) error {
return fsFile(c, file, filesystem)
}
}
// defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images`
// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break
// all old applications that rely on being able to traverse up from current executable run path.
// NB: private because you really should use fs.FS implementation instances
type defaultFS struct {
prefix string
fs fs.FS
}
func newDefaultFS() *defaultFS {
dir, _ := os.Getwd()
return &defaultFS{
prefix: dir,
fs: os.DirFS(dir),
}
}
func (fs defaultFS) Open(name string) (fs.File, error) {
return fs.fs.Open(name)
}
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
if dFS, ok := currentFs.(*defaultFS); ok {
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS to
// allow cases when root is given as `../somepath` which is not valid for fs.FS
root = filepath.Join(dFS.prefix, root)
return &defaultFS{
prefix: root,
fs: os.DirFS(root),
}, nil
}
return fs.Sub(currentFs, root)
}
// MustSubFS creates sub FS from current filesystem or panic on failure.
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
//
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
// create sub fs which uses necessary prefix for directory path.
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
subFs, err := subFS(currentFs, fsRoot)
if err != nil {
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
}
return subFs
}

24
vendor/github.com/labstack/echo/v4/go.mod generated vendored Normal file
View File

@@ -0,0 +1,24 @@
module github.com/labstack/echo/v4
go 1.17
require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/labstack/gommon v0.3.1
github.com/stretchr/testify v1.7.0
github.com/valyala/fasttemplate v1.2.1
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

45
vendor/github.com/labstack/echo/v4/go.sum generated vendored Normal file
View File

@@ -0,0 +1,45 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

119
vendor/github.com/labstack/echo/v4/group.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package echo
import (
"net/http"
)
type (
// Group is a set of sub-routes for a specified route. It can be used for inner
// routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
Group struct {
common
host string
prefix string
middleware []MiddlewareFunc
echo *Echo
}
)
// Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) {
g.middleware = append(g.middleware, middleware...)
if len(g.middleware) == 0 {
return
}
// Allow all requests to reach the group as they might get dropped if router
// doesn't find a match, making none of the group middleware process.
g.Any("", NotFoundHandler)
g.Any("/*", NotFoundHandler)
}
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodConnect, path, h, m...)
}
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodDelete, path, h, m...)
}
// GET implements `Echo#GET()` for sub-routes within the Group.
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodGet, path, h, m...)
}
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodHead, path, h, m...)
}
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodOptions, path, h, m...)
}
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPatch, path, h, m...)
}
// POST implements `Echo#POST()` for sub-routes within the Group.
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPost, path, h, m...)
}
// PUT implements `Echo#PUT()` for sub-routes within the Group.
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPut, path, h, m...)
}
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodTrace, path, h, m...)
}
// Any implements `Echo#Any()` for sub-routes within the Group.
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
}
return routes
}
// Match implements `Echo#Match()` for sub-routes within the Group.
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
}
return routes
}
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
m = append(m, g.middleware...)
m = append(m, middleware...)
sg = g.echo.Group(g.prefix+prefix, m...)
sg.host = g.host
return
}
// File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) {
g.file(path, file, g.GET)
}
// Add implements `Echo#Add()` for sub-routes within the Group.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// Combine into a new slice to avoid accidentally passing the same slice for
// multiple routes, which would lead to later add() calls overwriting the
// middleware from earlier calls.
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
m = append(m, g.middleware...)
m = append(m, middleware...)
return g.echo.add(g.host, method, g.prefix+path, handler, m...)
}

9
vendor/github.com/labstack/echo/v4/group_fs.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
//go:build !go1.16
// +build !go1.16
package echo
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(prefix, root string) {
g.static(prefix, root, g.GET)
}

33
vendor/github.com/labstack/echo/v4/group_fs_go1.16.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
//go:build go1.16
// +build go1.16
package echo
import (
"io/fs"
"net/http"
)
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(pathPrefix, fsRoot string) {
subFs := MustSubFS(g.echo.Filesystem, fsRoot)
g.StaticFS(pathPrefix, subFs)
}
// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) {
g.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(filesystem, false),
)
}
// FileFS implements `Echo#FileFS()` for sub-routes within the Group.
func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
return g.GET(path, StaticFileHandler(file, filesystem), m...)
}

263
vendor/github.com/labstack/echo/v4/ip.go generated vendored Normal file
View File

@@ -0,0 +1,263 @@
package echo
import (
"net"
"net/http"
"strings"
)
/**
By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 )
Source: https://echo.labstack.com/guide/ip-address/
IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.
Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.
However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
Otherwise, you might give someone a chance of deceiving you. **A security risk!**
To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
This guides show you why and how.
> Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.
Let's start from two questions to know the right direction:
1. Do you put any HTTP (L7) proxy in front of the application?
- It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway).
2. If yes, what HTTP header do your proxies use to pass client IP to the application?
## Case 1. With no proxy
If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.
Any HTTP header is untrustable because the clients have full control what headers to be set.
In this case, use `echo.ExtractIPDirect()`.
```go
e.IPExtractor = echo.ExtractIPDirect()
```
## Case 2. With proxies using `X-Forwarded-For` header
[`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header
to relay clients' IP addresses.
At each hop on the proxies, they append the request IP address at the end of the header.
Following example diagram illustrates this behavior.
```text
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ "Origin" │───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
│ (IP: a) │ │ (IP: b) │ │ (IP: c) │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Case 1.
XFF: "" "a" "a, b"
~~~~~~
Case 2.
XFF: "x" "x, a" "x, a, b"
~~~~~~~~~
↑ What your app will see
```
In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
```go
e.IPExtractor = echo.ExtractIPFromXFFHeader()
```
By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
[RFC4193](https://tools.ietf.org/html/rfc4193)).
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
E.g.:
```go
e.IPExtractor = echo.ExtractIPFromXFFHeader(
TrustLinkLocal(false),
TrustIPRanges(lbIPRange),
)
```
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
## Case 3. With proxies using `X-Real-IP` header
`X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF.
If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`.
```go
e.IPExtractor = echo.ExtractIPFromRealIPHeader()
```
Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
[RFC4193](https://tools.ietf.org/html/rfc4193)).
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
> **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**.
> Otherwise there is a chance of fraud, as it is what clients can control.
## About default behavior
In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer.
As you might already notice, after reading this article, this is not good.
Sole reason this is default is just backward compatibility.
## Private IP ranges
See: https://en.wikipedia.org/wiki/Private_network
Private IPv4 address ranges (RFC 1918):
* 10.0.0.0 10.255.255.255 (24-bit block)
* 172.16.0.0 172.31.255.255 (20-bit block)
* 192.168.0.0 192.168.255.255 (16-bit block)
Private IPv6 address ranges:
* fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA)
*/
type ipChecker struct {
trustLoopback bool
trustLinkLocal bool
trustPrivateNet bool
trustExtraRanges []*net.IPNet
}
// TrustOption is config for which IP address to trust
type TrustOption func(*ipChecker)
// TrustLoopback configures if you trust loopback address (default: true).
func TrustLoopback(v bool) TrustOption {
return func(c *ipChecker) {
c.trustLoopback = v
}
}
// TrustLinkLocal configures if you trust link-local address (default: true).
func TrustLinkLocal(v bool) TrustOption {
return func(c *ipChecker) {
c.trustLinkLocal = v
}
}
// TrustPrivateNet configures if you trust private network address (default: true).
func TrustPrivateNet(v bool) TrustOption {
return func(c *ipChecker) {
c.trustPrivateNet = v
}
}
// TrustIPRange add trustable IP ranges using CIDR notation.
func TrustIPRange(ipRange *net.IPNet) TrustOption {
return func(c *ipChecker) {
c.trustExtraRanges = append(c.trustExtraRanges, ipRange)
}
}
func newIPChecker(configs []TrustOption) *ipChecker {
checker := &ipChecker{trustLoopback: true, trustLinkLocal: true, trustPrivateNet: true}
for _, configure := range configs {
configure(checker)
}
return checker
}
// Go1.16+ added `ip.IsPrivate()` but until that use this implementation
func isPrivateIPRange(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 ||
ip4[0] == 172 && ip4[1]&0xf0 == 16 ||
ip4[0] == 192 && ip4[1] == 168
}
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}
func (c *ipChecker) trust(ip net.IP) bool {
if c.trustLoopback && ip.IsLoopback() {
return true
}
if c.trustLinkLocal && ip.IsLinkLocalUnicast() {
return true
}
if c.trustPrivateNet && isPrivateIPRange(ip) {
return true
}
for _, trustedRange := range c.trustExtraRanges {
if trustedRange.Contains(ip) {
return true
}
}
return false
}
// IPExtractor is a function to extract IP addr from http.Request.
// Set appropriate one to Echo#IPExtractor.
// See https://echo.labstack.com/guide/ip-address for more details.
type IPExtractor func(*http.Request) string
// ExtractIPDirect extracts IP address using actual IP address.
// Use this if your server faces to internet directory (i.e.: uses no proxy).
func ExtractIPDirect() IPExtractor {
return extractIP
}
func extractIP(req *http.Request) string {
ra, _, _ := net.SplitHostPort(req.RemoteAddr)
return ra
}
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
// Use this if you put proxy which uses this header.
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" {
if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
return realIP
}
}
return extractIP(req)
}
}
// ExtractIPFromXFFHeader extracts IP address using x-forwarded-for header.
// Use this if you put proxy which uses this header.
// This returns nearest untrustable IP. If all IPs are trustable, returns furthest one (i.e.: XFF[0]).
func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := extractIP(req)
xffs := req.Header[HeaderXForwardedFor]
if len(xffs) == 0 {
return directIP
}
ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP)
for i := len(ips) - 1; i >= 0; i-- {
ip := net.ParseIP(strings.TrimSpace(ips[i]))
if ip == nil {
// Unable to parse IP; cannot trust entire records
return directIP
}
if !checker.trust(ip) {
return ip.String()
}
}
// All of the IPs are trusted; return first element because it is furthest from server (best effort strategy).
return strings.TrimSpace(ips[0])
}
}

31
vendor/github.com/labstack/echo/v4/json.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package echo
import (
"encoding/json"
"fmt"
"net/http"
)
// DefaultJSONSerializer implements JSON encoding using encoding/json.
type DefaultJSONSerializer struct{}
// Serialize converts an interface into a json and writes it to the response.
// You can optionally use the indent parameter to produce pretty JSONs.
func (d DefaultJSONSerializer) Serialize(c Context, i interface{}, indent string) error {
enc := json.NewEncoder(c.Response())
if indent != "" {
enc.SetIndent("", indent)
}
return enc.Encode(i)
}
// Deserialize reads a JSON from a request body and converts it into an interface.
func (d DefaultJSONSerializer) Deserialize(c Context, i interface{}) error {
err := json.NewDecoder(c.Request().Body).Decode(i)
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
} else if se, ok := err.(*json.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
}
return err
}

41
vendor/github.com/labstack/echo/v4/log.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
package echo
import (
"io"
"github.com/labstack/gommon/log"
)
type (
// Logger defines the logging interface.
Logger interface {
Output() io.Writer
SetOutput(w io.Writer)
Prefix() string
SetPrefix(p string)
Level() log.Lvl
SetLevel(v log.Lvl)
SetHeader(h string)
Print(i ...interface{})
Printf(format string, args ...interface{})
Printj(j log.JSON)
Debug(i ...interface{})
Debugf(format string, args ...interface{})
Debugj(j log.JSON)
Info(i ...interface{})
Infof(format string, args ...interface{})
Infoj(j log.JSON)
Warn(i ...interface{})
Warnf(format string, args ...interface{})
Warnj(j log.JSON)
Error(i ...interface{})
Errorf(format string, args ...interface{})
Errorj(j log.JSON)
Fatal(i ...interface{})
Fatalj(j log.JSON)
Fatalf(format string, args ...interface{})
Panic(i ...interface{})
Panicj(j log.JSON)
Panicf(format string, args ...interface{})
}
)

View File

@@ -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.EqualFold(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
}
}
}

View File

@@ -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.
//
// BodyDump 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()
}

View File

@@ -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}
},
}
}

View File

@@ -0,0 +1,145 @@
package middleware
import (
"bufio"
"compress/gzip"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
"sync"
"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
wroteBody bool
}
)
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
}
pool := gzipCompressPool(config)
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
i := pool.Get()
w, ok := i.(*gzip.Writer)
if !ok {
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
}
rw := res.Writer
w.Reset(rw)
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
defer func() {
if !grw.wroteBody {
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()
pool.Put(w)
}()
res.Writer = grw
}
return next(c)
}
}
}
func (w *gzipResponseWriter) WriteHeader(code int) {
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))
}
w.wroteBody = true
return w.Writer.Write(b)
}
func (w *gzipResponseWriter) Flush() {
w.Writer.(*gzip.Writer).Flush()
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}
func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
if p, ok := w.ResponseWriter.(http.Pusher); ok {
return p.Push(target, opts)
}
return http.ErrNotSupported
}
func gzipCompressPool(config GzipConfig) sync.Pool {
return sync.Pool{
New: func() interface{} {
w, err := gzip.NewWriterLevel(ioutil.Discard, config.Level)
if err != nil {
return err
}
return w
},
}
}

234
vendor/github.com/labstack/echo/v4/middleware/cors.go generated vendored Normal file
View File

@@ -0,0 +1,234 @@
package middleware
import (
"net/http"
"regexp"
"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"`
// AllowOriginFunc is a custom function to validate the origin. It takes the
// origin as an argument and returns true if allowed or false otherwise. If
// an error is returned, it is returned by the handler. If this option is
// set, AllowOrigins is ignored.
// Optional.
AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"`
// AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods.
// If `allowMethods` is left empty will fill for preflight request `Access-Control-Allow-Methods` header value
// from `Allow` header that echo.Router set into context.
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.
// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
// See http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
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
}
hasCustomAllowMethods := true
if len(config.AllowMethods) == 0 {
hasCustomAllowMethods = false
config.AllowMethods = DefaultCORSConfig.AllowMethods
}
allowOriginPatterns := []string{}
for _, origin := range config.AllowOrigins {
pattern := regexp.QuoteMeta(origin)
pattern = strings.Replace(pattern, "\\*", ".*", -1)
pattern = strings.Replace(pattern, "\\?", ".", -1)
pattern = "^" + pattern + "$"
allowOriginPatterns = append(allowOriginPatterns, pattern)
}
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 := ""
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
// Preflight request is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method,
// Access-Control-Request-Headers, and the Origin header. See: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
// For simplicity we just consider method type and later `Origin` header.
preflight := req.Method == http.MethodOptions
// Although router adds special handler in case of OPTIONS method we avoid calling next for OPTIONS in this middleware
// as CORS requests do not have cookies / authentication headers by default, so we could get stuck in auth
// middlewares by calling next(c).
// But we still want to send `Allow` header as response in case of Non-CORS OPTIONS request as router default
// handler does.
routerAllowMethods := ""
if preflight {
tmpAllowMethods, ok := c.Get(echo.ContextKeyHeaderAllow).(string)
if ok && tmpAllowMethods != "" {
routerAllowMethods = tmpAllowMethods
c.Response().Header().Set(echo.HeaderAllow, routerAllowMethods)
}
}
// No Origin provided. This is (probably) not request from actual browser - proceed executing middleware chain
if origin == "" {
if !preflight {
return next(c)
}
return c.NoContent(http.StatusNoContent)
}
if config.AllowOriginFunc != nil {
allowed, err := config.AllowOriginFunc(origin)
if err != nil {
return err
}
if allowed {
allowOrigin = origin
}
} else {
// Check allowed origins
for _, o := range config.AllowOrigins {
if o == "*" && config.AllowCredentials {
allowOrigin = origin
break
}
if o == "*" || o == origin {
allowOrigin = o
break
}
if matchSubdomain(origin, o) {
allowOrigin = origin
break
}
}
checkPatterns := false
if allowOrigin == "" {
// to avoid regex cost by invalid (long) domains (253 is domain name max limit)
if len(origin) <= (253+3+5) && strings.Contains(origin, "://") {
checkPatterns = true
}
}
if checkPatterns {
for _, re := range allowOriginPatterns {
if match, _ := regexp.MatchString(re, origin); match {
allowOrigin = origin
break
}
}
}
}
// Origin not allowed
if allowOrigin == "" {
if !preflight {
return next(c)
}
return c.NoContent(http.StatusNoContent)
}
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
// Simple request
if !preflight {
if exposeHeaders != "" {
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
}
return next(c)
}
// Preflight request
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
if !hasCustomAllowMethods && routerAllowMethods != "" {
res.Header().Set(echo.HeaderAccessControlAllowMethods, routerAllowMethods)
} else {
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
}
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)
}
}
}

205
vendor/github.com/labstack/echo/v4/middleware/csrf.go generated vendored Normal file
View File

@@ -0,0 +1,205 @@
package middleware
import (
"crypto/subtle"
"net/http"
"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>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>" or "header:<name>:<cut-prefix>"
// - "query:<name>"
// - "form:<name>"
// Multiple sources example:
// - "header:X-CSRF-Token,query:csrf"
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"`
// Indicates SameSite mode of the CSRF cookie.
// Optional. Default value SameSiteDefaultMode.
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
}
)
// ErrCSRFInvalid is returned when CSRF check fails
var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
Skipper: DefaultSkipper,
TokenLength: 32,
TokenLookup: "header:" + echo.HeaderXCSRFToken,
ContextKey: "csrf",
CookieName: "_csrf",
CookieMaxAge: 86400,
CookieSameSite: http.SameSiteDefaultMode,
}
)
// 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
}
if config.CookieSameSite == http.SameSiteNoneMode {
config.CookieSecure = true
}
extractors, err := createExtractors(config.TokenLookup, "")
if err != nil {
panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
token := ""
if k, err := c.Cookie(config.CookieName); err != nil {
token = random.String(config.TokenLength) // Generate token
} else {
token = k.Value // Reuse token
}
switch c.Request().Method {
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
default:
// Validate token only for requests which are not defined as 'safe' by RFC7231
var lastExtractorErr error
var lastTokenErr error
outer:
for _, extractor := range extractors {
clientTokens, err := extractor(c)
if err != nil {
lastExtractorErr = err
continue
}
for _, clientToken := range clientTokens {
if validateCSRFToken(token, clientToken) {
lastTokenErr = nil
lastExtractorErr = nil
break outer
}
lastTokenErr = ErrCSRFInvalid
}
}
if lastTokenErr != nil {
return lastTokenErr
} else if lastExtractorErr != nil {
// ugly part to preserve backwards compatible errors. someone could rely on them
if lastExtractorErr == errQueryExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the query string")
} else if lastExtractorErr == errFormExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the form parameter")
} else if lastExtractorErr == errHeaderExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header")
} else {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, lastExtractorErr.Error())
}
return lastExtractorErr
}
}
// 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
}
if config.CookieSameSite != http.SameSiteDefaultMode {
cookie.SameSite = config.CookieSameSite
}
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)
}
}
}
func validateCSRFToken(token, clientToken string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
}

View File

@@ -0,0 +1,99 @@
package middleware
import (
"compress/gzip"
"io"
"net/http"
"sync"
"github.com/labstack/echo/v4"
)
type (
// DecompressConfig defines the config for Decompress middleware.
DecompressConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
GzipDecompressPool Decompressor
}
)
//GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
const GZIPEncoding string = "gzip"
// Decompressor is used to get the sync.Pool used by the middleware to get Gzip readers
type Decompressor interface {
gzipDecompressPool() sync.Pool
}
var (
//DefaultDecompressConfig defines the config for decompress middleware
DefaultDecompressConfig = DecompressConfig{
Skipper: DefaultSkipper,
GzipDecompressPool: &DefaultGzipDecompressPool{},
}
)
// DefaultGzipDecompressPool is the default implementation of Decompressor interface
type DefaultGzipDecompressPool struct {
}
func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
return sync.Pool{New: func() interface{} { return new(gzip.Reader) }}
}
//Decompress decompresses request body based if content encoding type is set to "gzip" with default config
func Decompress() echo.MiddlewareFunc {
return DecompressWithConfig(DefaultDecompressConfig)
}
//DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config
func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultGzipConfig.Skipper
}
if config.GzipDecompressPool == nil {
config.GzipDecompressPool = DefaultDecompressConfig.GzipDecompressPool
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
pool := config.GzipDecompressPool.gzipDecompressPool()
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
if c.Request().Header.Get(echo.HeaderContentEncoding) != GZIPEncoding {
return next(c)
}
i := pool.Get()
gr, ok := i.(*gzip.Reader)
if !ok || gr == nil {
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
}
defer pool.Put(gr)
b := c.Request().Body
defer b.Close()
if err := gr.Reset(b); err != nil {
if err == io.EOF { //ignore if body is empty
return next(c)
}
return err
}
// only Close gzip reader if it was set to a proper gzip source otherwise it will panic on close.
defer gr.Close()
c.Request().Body = gr
return next(c)
}
}
}

View File

@@ -0,0 +1,184 @@
package middleware
import (
"errors"
"fmt"
"github.com/labstack/echo/v4"
"net/textproto"
"strings"
)
const (
// extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion
// attack vector
extractorLimit = 20
)
var errHeaderExtractorValueMissing = errors.New("missing value in request header")
var errHeaderExtractorValueInvalid = errors.New("invalid value in request header")
var errQueryExtractorValueMissing = errors.New("missing value in the query string")
var errParamExtractorValueMissing = errors.New("missing value in path params")
var errCookieExtractorValueMissing = errors.New("missing value in cookies")
var errFormExtractorValueMissing = errors.New("missing value in the form")
// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context.
type ValuesExtractor func(c echo.Context) ([]string, error)
func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) {
if lookups == "" {
return nil, nil
}
sources := strings.Split(lookups, ",")
var extractors = make([]ValuesExtractor, 0)
for _, source := range sources {
parts := strings.Split(source, ":")
if len(parts) < 2 {
return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source)
}
switch parts[0] {
case "query":
extractors = append(extractors, valuesFromQuery(parts[1]))
case "param":
extractors = append(extractors, valuesFromParam(parts[1]))
case "cookie":
extractors = append(extractors, valuesFromCookie(parts[1]))
case "form":
extractors = append(extractors, valuesFromForm(parts[1]))
case "header":
prefix := ""
if len(parts) > 2 {
prefix = parts[2]
} else if authScheme != "" && parts[1] == echo.HeaderAuthorization {
// backwards compatibility for JWT and KeyAuth:
// * we only apply this fix to Authorization as header we use and uses prefixes like "Bearer <token-value>" etc
// * previously header extractor assumed that auth-scheme/prefix had a space as suffix we need to retain that
// behaviour for default values and Authorization header.
prefix = authScheme
if !strings.HasSuffix(prefix, " ") {
prefix += " "
}
}
extractors = append(extractors, valuesFromHeader(parts[1], prefix))
}
}
return extractors, nil
}
// valuesFromHeader returns a functions that extracts values from the request header.
// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static
// prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to remove is `<auth-scheme> `
// note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove
// is `Basic `. In case of JWT tokens `Authorization: Bearer <token>` prefix is `Bearer `.
// If prefix is left empty the whole value is returned.
func valuesFromHeader(header string, valuePrefix string) ValuesExtractor {
prefixLen := len(valuePrefix)
// standard library parses http.Request header keys in canonical form but we may provide something else so fix this
header = textproto.CanonicalMIMEHeaderKey(header)
return func(c echo.Context) ([]string, error) {
values := c.Request().Header.Values(header)
if len(values) == 0 {
return nil, errHeaderExtractorValueMissing
}
result := make([]string, 0)
for i, value := range values {
if prefixLen == 0 {
result = append(result, value)
if i >= extractorLimit-1 {
break
}
continue
}
if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) {
result = append(result, value[prefixLen:])
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
if prefixLen > 0 {
return nil, errHeaderExtractorValueInvalid
}
return nil, errHeaderExtractorValueMissing
}
return result, nil
}
}
// valuesFromQuery returns a function that extracts values from the query string.
func valuesFromQuery(param string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
result := c.QueryParams()[param]
if len(result) == 0 {
return nil, errQueryExtractorValueMissing
} else if len(result) > extractorLimit-1 {
result = result[:extractorLimit]
}
return result, nil
}
}
// valuesFromParam returns a function that extracts values from the url param string.
func valuesFromParam(param string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
result := make([]string, 0)
paramVales := c.ParamValues()
for i, p := range c.ParamNames() {
if param == p {
result = append(result, paramVales[i])
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
return nil, errParamExtractorValueMissing
}
return result, nil
}
}
// valuesFromCookie returns a function that extracts values from the named cookie.
func valuesFromCookie(name string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
cookies := c.Cookies()
if len(cookies) == 0 {
return nil, errCookieExtractorValueMissing
}
result := make([]string, 0)
for i, cookie := range cookies {
if name == cookie.Name {
result = append(result, cookie.Value)
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
return nil, errCookieExtractorValueMissing
}
return result, nil
}
}
// valuesFromForm returns a function that extracts values from the form field.
func valuesFromForm(name string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
if parseErr := c.Request().ParseForm(); parseErr != nil {
return nil, fmt.Errorf("valuesFromForm parse form failed: %w", parseErr)
}
values := c.Request().Form[name]
if len(values) == 0 {
return nil, errFormExtractorValueMissing
}
if len(values) > extractorLimit-1 {
values = values[:extractorLimit]
}
result := append([]string{}, values...)
return result, nil
}
}

300
vendor/github.com/labstack/echo/v4/middleware/jwt.go generated vendored Normal file
View File

@@ -0,0 +1,300 @@
//go:build go1.15
// +build go1.15
package middleware
import (
"errors"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"net/http"
"reflect"
)
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 before middleware chain continues with next
// middleware or handler.
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
// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
ErrorHandlerWithContext JWTErrorHandlerWithContext
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to
// ignore the error (by returning `nil`).
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
// In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context
// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
ContinueOnIgnoredError bool
// Signing key to validate token.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither user-defined KeyFunc nor SigningKeys is provided.
SigningKey interface{}
// Map of signing keys to validate token with kid field usage.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither user-defined KeyFunc nor SigningKey is provided.
SigningKeys map[string]interface{}
// Signing method used to check the token's signing algorithm.
// 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. Used by default ParseTokenFunc implementation.
// Not used if custom ParseTokenFunc is set.
// Optional. Default value jwt.MapClaims
Claims jwt.Claims
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
// If prefix is left empty the whole value is returned.
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
// - "form:<name>"
// Multiple sources example:
// - "header:Authorization,cookie:myowncookie"
TokenLookup string
// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
// This is one of the two options to provide a token extractor.
// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
// You can also provide both if you want.
TokenLookupFuncs []ValuesExtractor
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
// KeyFunc defines a user-defined function that supplies the public key for a token validation.
// The function shall take care of verifying the signing algorithm and selecting the proper key.
// A user-defined KeyFunc can be useful if tokens are issued by an external party.
// Used by default ParseTokenFunc implementation.
//
// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither SigningKeys nor SigningKey is provided.
// Not used if custom ParseTokenFunc is set.
// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
KeyFunc jwt.Keyfunc
// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
// parsing fails or parsed token is invalid.
// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
ParseTokenFunc func(auth string, c echo.Context) (interface{}, error)
}
// JWTSuccessHandler defines a function which is executed for a valid token.
JWTSuccessHandler func(c echo.Context)
// JWTErrorHandler defines a function which is executed for an invalid token.
JWTErrorHandler func(err error) error
// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
JWTErrorHandlerWithContext func(err error, c echo.Context) error
)
// Algorithms
const (
AlgorithmHS256 = "HS256"
)
// Errors
var (
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
)
var (
// DefaultJWTConfig is the default JWT auth middleware config.
DefaultJWTConfig = JWTConfig{
Skipper: DefaultSkipper,
SigningMethod: AlgorithmHS256,
ContextKey: "user",
TokenLookup: "header:" + echo.HeaderAuthorization,
TokenLookupFuncs: nil,
AuthScheme: "Bearer",
Claims: jwt.MapClaims{},
KeyFunc: nil,
}
)
// 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 && len(config.SigningKeys) == 0 && config.KeyFunc == nil && config.ParseTokenFunc == 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 == "" && len(config.TokenLookupFuncs) == 0 {
config.TokenLookup = DefaultJWTConfig.TokenLookup
}
if config.AuthScheme == "" {
config.AuthScheme = DefaultJWTConfig.AuthScheme
}
if config.KeyFunc == nil {
config.KeyFunc = config.defaultKeyFunc
}
if config.ParseTokenFunc == nil {
config.ParseTokenFunc = config.defaultParseToken
}
extractors, err := createExtractors(config.TokenLookup, config.AuthScheme)
if err != nil {
panic(err)
}
if len(config.TokenLookupFuncs) > 0 {
extractors = append(config.TokenLookupFuncs, extractors...)
}
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)
}
var lastExtractorErr error
var lastTokenErr error
for _, extractor := range extractors {
auths, err := extractor(c)
if err != nil {
lastExtractorErr = ErrJWTMissing // backwards compatibility: all extraction errors are same (unlike KeyAuth)
continue
}
for _, auth := range auths {
token, err := config.ParseTokenFunc(auth, c)
if err != nil {
lastTokenErr = err
continue
}
// Store user information from token into context.
c.Set(config.ContextKey, token)
if config.SuccessHandler != nil {
config.SuccessHandler(c)
}
return next(c)
}
}
// we are here only when we did not successfully extract or parse any of the tokens
err := lastTokenErr
if err == nil { // prioritize token errors over extracting errors
err = lastExtractorErr
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
if config.ErrorHandlerWithContext != nil {
tmpErr := config.ErrorHandlerWithContext(err, c)
if config.ContinueOnIgnoredError && tmpErr == nil {
return next(c)
}
return tmpErr
}
// backwards compatible errors codes
if lastTokenErr != nil {
return &echo.HTTPError{
Code: ErrJWTInvalid.Code,
Message: ErrJWTInvalid.Message,
Internal: err,
}
}
return err // this is lastExtractorErr value
}
}
}
func (config *JWTConfig) defaultParseToken(auth string, c echo.Context) (interface{}, error) {
token := new(jwt.Token)
var err error
// 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 {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return token, nil
}
// defaultKeyFunc returns a signing key of the given token.
func (config *JWTConfig) defaultKeyFunc(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"])
}
if len(config.SigningKeys) > 0 {
if kid, ok := t.Header["kid"].(string); ok {
if key, ok := config.SigningKeys[kid]; ok {
return key, nil
}
}
return nil, fmt.Errorf("unexpected jwt key id=%v", t.Header["kid"])
}
return config.SigningKey, nil
}

View File

@@ -0,0 +1,180 @@
package middleware
import (
"errors"
"github.com/labstack/echo/v4"
"net/http"
)
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>" or "<source>:<name>,<source>:<name>" that is used
// to extract key from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
// - "query:<name>"
// - "form:<name>"
// - "cookie:<name>"
// Multiple sources example:
// - "header:Authorization,header:X-Api-Key"
KeyLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
// Validator is a function to validate key.
// Required.
Validator KeyAuthValidator
// ErrorHandler defines a function which is executed for an invalid key.
// It may be used to define a custom error.
ErrorHandler KeyAuthErrorHandler
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
// ignore the error (by returning `nil`).
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
// In that case you can use ErrorHandler to set a default public key auth value in the request context
// and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
ContinueOnIgnoredError bool
}
// KeyAuthValidator defines a function to validate KeyAuth credentials.
KeyAuthValidator func(auth string, c echo.Context) (bool, error)
// KeyAuthErrorHandler defines a function which is executed for an invalid key.
KeyAuthErrorHandler func(err error, c echo.Context) error
)
var (
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
DefaultKeyAuthConfig = KeyAuthConfig{
Skipper: DefaultSkipper,
KeyLookup: "header:" + echo.HeaderAuthorization,
AuthScheme: "Bearer",
}
)
// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
type ErrKeyAuthMissing struct {
Err error
}
// Error returns errors text
func (e *ErrKeyAuthMissing) Error() string {
return e.Err.Error()
}
// Unwrap unwraps error
func (e *ErrKeyAuthMissing) Unwrap() error {
return e.Err
}
// 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")
}
extractors, err := createExtractors(config.KeyLookup, config.AuthScheme)
if err != nil {
panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
var lastExtractorErr error
var lastValidatorErr error
for _, extractor := range extractors {
keys, err := extractor(c)
if err != nil {
lastExtractorErr = err
continue
}
for _, key := range keys {
valid, err := config.Validator(key, c)
if err != nil {
lastValidatorErr = err
continue
}
if valid {
return next(c)
}
lastValidatorErr = errors.New("invalid key")
}
}
// we are here only when we did not successfully extract and validate any of keys
err := lastValidatorErr
if err == nil { // prioritize validator errors over extracting errors
// ugly part to preserve backwards compatible errors. someone could rely on them
if lastExtractorErr == errQueryExtractorValueMissing {
err = errors.New("missing key in the query string")
} else if lastExtractorErr == errCookieExtractorValueMissing {
err = errors.New("missing key in cookies")
} else if lastExtractorErr == errFormExtractorValueMissing {
err = errors.New("missing key in the form")
} else if lastExtractorErr == errHeaderExtractorValueMissing {
err = errors.New("missing key in request header")
} else if lastExtractorErr == errHeaderExtractorValueInvalid {
err = errors.New("invalid key in the request header")
} else {
err = lastExtractorErr
}
err = &ErrKeyAuthMissing{Err: err}
}
if config.ErrorHandler != nil {
tmpErr := config.ErrorHandler(err, c)
if config.ContinueOnIgnoredError && tmpErr == nil {
return next(c)
}
return tmpErr
}
if lastValidatorErr != nil { // prioritize validator errors over extracting errors
return &echo.HTTPError{
Code: http.StatusUnauthorized,
Message: "Unauthorized",
Internal: lastValidatorErr,
}
}
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
}

223
vendor/github.com/labstack/echo/v4/middleware/logger.go generated vendored Normal file
View File

@@ -0,0 +1,223 @@
package middleware
import (
"bytes"
"encoding/json"
"io"
"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 construct 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",
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 {
// Error may contain invalid JSON e.g. `"`
b, _ := json.Marshal(err.Error())
b = b[1 : len(b)-1]
return buf.Write(b)
}
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
}
if config.Output == nil {
_, err = c.Logger().Output().Write(buf.Bytes())
return
}
_, err = config.Output.Write(buf.Bytes())
return
}
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,89 @@
package middleware
import (
"net/http"
"regexp"
"strconv"
"strings"
"github.com/labstack/echo/v4"
)
type (
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
Skipper func(c echo.Context) bool
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc func(c 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...)
}
func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string {
// Initialize
rulesRegex := map[*regexp.Regexp]string{}
for k, v := range rewrite {
k = regexp.QuoteMeta(k)
k = strings.Replace(k, `\*`, "(.*?)", -1)
if strings.HasPrefix(k, `\^`) {
k = strings.Replace(k, `\^`, "^", -1)
}
k = k + "$"
rulesRegex[regexp.MustCompile(k)] = v
}
return rulesRegex
}
func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error {
if len(rewriteRegex) == 0 {
return nil
}
// Depending how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
// We only want to use path part for rewriting and therefore trim prefix if it exists
rawURI := req.RequestURI
if rawURI != "" && rawURI[0] != '/' {
prefix := ""
if req.URL.Scheme != "" {
prefix = req.URL.Scheme + "://"
}
if req.URL.Host != "" {
prefix += req.URL.Host // host or host:port
}
if prefix != "" {
rawURI = strings.TrimPrefix(rawURI, prefix)
}
}
for k, v := range rewriteRegex {
if replacer := captureTokens(k, rawURI); replacer != nil {
url, err := req.URL.Parse(replacer.Replace(v))
if err != nil {
return err
}
req.URL = url
return nil // rewrite only once
}
}
return nil
}
// DefaultSkipper returns false which processes the middleware.
func DefaultSkipper(echo.Context) bool {
return false
}

303
vendor/github.com/labstack/echo/v4/middleware/proxy.go generated vendored Normal file
View File

@@ -0,0 +1,303 @@
package middleware
import (
"context"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
"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
// RegexRewrite defines rewrite rules using regexp.Rexexp with captures
// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
// Example:
// "^/old/[0.9]+/": "/new",
// "^/api/.+?/(.*)": "/v2/$1",
RegexRewrite map[*regexp.Regexp]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
// ModifyResponse defines function to modify response from ProxyTarget.
ModifyResponse func(*http.Response) error
}
// 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.Set("_error", fmt.Sprintf("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 {
c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)))
return
}
defer out.Close()
// Write header
err = r.Write(out)
if err != nil {
c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)))
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.Set("_error", fmt.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 = DefaultProxyConfig.Skipper
}
if config.Balancer == nil {
panic("echo: proxy middleware requires balancer")
}
if config.Rewrite != nil {
if config.RegexRewrite == nil {
config.RegexRewrite = make(map[*regexp.Regexp]string)
}
for k, v := range rewriteRulesRegex(config.Rewrite) {
config.RegexRewrite[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)
if err := rewriteURL(config.RegexRewrite, req); err != nil {
return err
}
// Fix header
// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.
// However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor.
if req.Header.Get(echo.HeaderXRealIP) == "" || c.Echo().IPExtractor != nil {
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)
}
if e, ok := c.Get("_error").(error); ok {
err = e
}
return
}
}
}
// StatusCodeContextCanceled is a custom HTTP status code for situations
// where a client unexpectedly closed the connection to the server.
// As there is no standard error code for "client closed connection", but
// various well-known HTTP clients and server implement this HTTP code we use
// 499 too instead of the more problematic 5xx, which does not allow to detect this situation
const StatusCodeContextCanceled = 499
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())
}
// If the client canceled the request (usually by closing the connection), we can report a
// client error (4xx) instead of a server error (5xx) to correctly identify the situation.
// The Go standard library (at of late 2020) wraps the exported, standard
// context.Canceled error with unexported garbage value requiring a substring check, see
// https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430
if err == context.Canceled || strings.Contains(err.Error(), "operation was canceled") {
httpError := echo.NewHTTPError(StatusCodeContextCanceled, fmt.Sprintf("client closed connection: %v", err))
httpError.Internal = err
c.Set("_error", httpError)
} else {
httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err))
httpError.Internal = err
c.Set("_error", httpError)
}
}
proxy.Transport = config.Transport
proxy.ModifyResponse = config.ModifyResponse
return proxy
}

View File

@@ -0,0 +1,270 @@
package middleware
import (
"net/http"
"sync"
"time"
"github.com/labstack/echo/v4"
"golang.org/x/time/rate"
)
type (
// RateLimiterStore is the interface to be implemented by custom stores.
RateLimiterStore interface {
// Stores for the rate limiter have to implement the Allow method
Allow(identifier string) (bool, error)
}
)
type (
// RateLimiterConfig defines the configuration for the rate limiter
RateLimiterConfig struct {
Skipper Skipper
BeforeFunc BeforeFunc
// IdentifierExtractor uses echo.Context to extract the identifier for a visitor
IdentifierExtractor Extractor
// Store defines a store for the rate limiter
Store RateLimiterStore
// ErrorHandler provides a handler to be called when IdentifierExtractor returns an error
ErrorHandler func(context echo.Context, err error) error
// DenyHandler provides a handler to be called when RateLimiter denies access
DenyHandler func(context echo.Context, identifier string, err error) error
}
// Extractor is used to extract data from echo.Context
Extractor func(context echo.Context) (string, error)
)
// errors
var (
// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
// ErrExtractorError denotes an error raised when extractor function is unsuccessful
ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
)
// DefaultRateLimiterConfig defines default values for RateLimiterConfig
var DefaultRateLimiterConfig = RateLimiterConfig{
Skipper: DefaultSkipper,
IdentifierExtractor: func(ctx echo.Context) (string, error) {
id := ctx.RealIP()
return id, nil
},
ErrorHandler: func(context echo.Context, err error) error {
return &echo.HTTPError{
Code: ErrExtractorError.Code,
Message: ErrExtractorError.Message,
Internal: err,
}
},
DenyHandler: func(context echo.Context, identifier string, err error) error {
return &echo.HTTPError{
Code: ErrRateLimitExceeded.Code,
Message: ErrRateLimitExceeded.Message,
Internal: err,
}
},
}
/*
RateLimiter returns a rate limiting middleware
e := echo.New()
limiterStore := middleware.NewRateLimiterMemoryStore(20)
e.GET("/rate-limited", func(c echo.Context) error {
return c.String(http.StatusOK, "test")
}, RateLimiter(limiterStore))
*/
func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {
config := DefaultRateLimiterConfig
config.Store = store
return RateLimiterWithConfig(config)
}
/*
RateLimiterWithConfig returns a rate limiting middleware
e := echo.New()
config := middleware.RateLimiterConfig{
Skipper: DefaultSkipper,
Store: middleware.NewRateLimiterMemoryStore(
middleware.RateLimiterMemoryStoreConfig{Rate: 10, Burst: 30, ExpiresIn: 3 * time.Minute}
)
IdentifierExtractor: func(ctx echo.Context) (string, error) {
id := ctx.RealIP()
return id, nil
},
ErrorHandler: func(context echo.Context, err error) error {
return context.JSON(http.StatusTooManyRequests, nil)
},
DenyHandler: func(context echo.Context, identifier string) error {
return context.JSON(http.StatusForbidden, nil)
},
}
e.GET("/rate-limited", func(c echo.Context) error {
return c.String(http.StatusOK, "test")
}, middleware.RateLimiterWithConfig(config))
*/
func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
if config.Skipper == nil {
config.Skipper = DefaultRateLimiterConfig.Skipper
}
if config.IdentifierExtractor == nil {
config.IdentifierExtractor = DefaultRateLimiterConfig.IdentifierExtractor
}
if config.ErrorHandler == nil {
config.ErrorHandler = DefaultRateLimiterConfig.ErrorHandler
}
if config.DenyHandler == nil {
config.DenyHandler = DefaultRateLimiterConfig.DenyHandler
}
if config.Store == nil {
panic("Store configuration must be provided")
}
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)
}
identifier, err := config.IdentifierExtractor(c)
if err != nil {
c.Error(config.ErrorHandler(c, err))
return nil
}
if allow, err := config.Store.Allow(identifier); !allow {
c.Error(config.DenyHandler(c, identifier, err))
return nil
}
return next(c)
}
}
}
type (
// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
RateLimiterMemoryStore struct {
visitors map[string]*Visitor
mutex sync.Mutex
rate rate.Limit //for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
burst int
expiresIn time.Duration
lastCleanup time.Time
}
// Visitor signifies a unique user's limiter details
Visitor struct {
*rate.Limiter
lastSeen time.Time
}
)
/*
NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
the provided rate (as req/s). The provided rate less than 1 will be treated as zero.
for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst and ExpiresIn will be set to default values.
Example (with 20 requests/sec):
limiterStore := middleware.NewRateLimiterMemoryStore(20)
*/
func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore) {
return NewRateLimiterMemoryStoreWithConfig(RateLimiterMemoryStoreConfig{
Rate: rate,
})
}
/*
NewRateLimiterMemoryStoreWithConfig returns an instance of RateLimiterMemoryStore
with the provided configuration. Rate must be provided. Burst will be set to the value of
the configured rate if not provided or set to 0.
The build-in memory store is usually capable for modest loads. For higher loads other
store implementations should be considered.
Characteristics:
* Concurrency above 100 parallel requests may causes measurable lock contention
* A high number of different IP addresses (above 16000) may be impacted by the internally used Go map
* A high number of requests from a single IP address may cause lock contention
Example:
limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig(
middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minute},
)
*/
func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore) {
store = &RateLimiterMemoryStore{}
store.rate = config.Rate
store.burst = config.Burst
store.expiresIn = config.ExpiresIn
if config.ExpiresIn == 0 {
store.expiresIn = DefaultRateLimiterMemoryStoreConfig.ExpiresIn
}
if config.Burst == 0 {
store.burst = int(config.Rate)
}
store.visitors = make(map[string]*Visitor)
store.lastCleanup = now()
return
}
// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore
type RateLimiterMemoryStoreConfig struct {
Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst int // Burst additionally allows a number of requests to pass when rate limit is reached
ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up
}
// DefaultRateLimiterMemoryStoreConfig provides default configuration values for RateLimiterMemoryStore
var DefaultRateLimiterMemoryStoreConfig = RateLimiterMemoryStoreConfig{
ExpiresIn: 3 * time.Minute,
}
// Allow implements RateLimiterStore.Allow
func (store *RateLimiterMemoryStore) Allow(identifier string) (bool, error) {
store.mutex.Lock()
limiter, exists := store.visitors[identifier]
if !exists {
limiter = new(Visitor)
limiter.Limiter = rate.NewLimiter(store.rate, store.burst)
store.visitors[identifier] = limiter
}
limiter.lastSeen = now()
if now().Sub(store.lastCleanup) > store.expiresIn {
store.cleanupStaleVisitors()
}
store.mutex.Unlock()
return limiter.AllowN(now(), 1), nil
}
/*
cleanupStaleVisitors helps manage the size of the visitors map by removing stale records
of users who haven't visited again after the configured expiry time has elapsed
*/
func (store *RateLimiterMemoryStore) cleanupStaleVisitors() {
for id, visitor := range store.visitors {
if now().Sub(visitor.lastSeen) > store.expiresIn {
delete(store.visitors, id)
}
}
store.lastCleanup = now()
}
/*
actual time method which is mocked in test file
*/
var now = time.Now

View File

@@ -0,0 +1,118 @@
package middleware
import (
"fmt"
"runtime"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
)
type (
// LogErrorFunc defines a function for custom logging in the middleware.
LogErrorFunc func(c echo.Context, err error, stack []byte) error
// 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"`
// LogLevel is log level to printing stack trace.
// Optional. Default value 0 (Print).
LogLevel log.Lvl
// LogErrorFunc defines a function for custom logging in the middleware.
// If it's set you don't need to provide LogLevel for config.
LogErrorFunc LogErrorFunc
}
)
var (
// DefaultRecoverConfig is the default Recover middleware config.
DefaultRecoverConfig = RecoverConfig{
Skipper: DefaultSkipper,
StackSize: 4 << 10, // 4 KB
DisableStackAll: false,
DisablePrintStack: false,
LogLevel: 0,
LogErrorFunc: nil,
}
)
// 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)
}
var stack []byte
var length int
if !config.DisablePrintStack {
stack = make([]byte, config.StackSize)
length = runtime.Stack(stack, !config.DisableStackAll)
stack = stack[:length]
}
if config.LogErrorFunc != nil {
err = config.LogErrorFunc(c, err, stack)
} else if !config.DisablePrintStack {
msg := fmt.Sprintf("[PANIC RECOVER] %v %s\n", err, stack[:length])
switch config.LogLevel {
case log.DEBUG:
c.Logger().Debug(msg)
case log.INFO:
c.Logger().Info(msg)
case log.WARN:
c.Logger().Warn(msg)
case log.ERROR:
c.Logger().Error(msg)
case log.OFF:
// None.
default:
c.Logger().Print(msg)
}
}
c.Error(err)
}
}()
return next(c)
}
}
}

View File

@@ -0,0 +1,152 @@
package middleware
import (
"net/http"
"strings"
"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) (bool, string) {
if scheme != "https" {
return true, "https://" + host + uri
}
return false, ""
})
}
// 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) (bool, string) {
if scheme != "https" && !strings.HasPrefix(host, www) {
return true, "https://www." + host + uri
}
return false, ""
})
}
// 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 scheme != "https" {
host = strings.TrimPrefix(host, www)
return true, "https://" + host + uri
}
return false, ""
})
}
// 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) (bool, string) {
if !strings.HasPrefix(host, www) {
return true, scheme + "://www." + host + uri
}
return false, ""
})
}
// 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) (bool, string) {
if strings.HasPrefix(host, www) {
return true, scheme + "://" + host[4:] + uri
}
return false, ""
})
}
func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
if config.Skipper == nil {
config.Skipper = DefaultRedirectConfig.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)
}
}
}

View File

@@ -0,0 +1,77 @@
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
// RequestIDHandler defines a function which is executed for a request id.
RequestIDHandler func(echo.Context, string)
// TargetHeader defines what header to look for to populate the id
TargetHeader string
}
)
var (
// DefaultRequestIDConfig is the default RequestID middleware config.
DefaultRequestIDConfig = RequestIDConfig{
Skipper: DefaultSkipper,
Generator: generator,
TargetHeader: echo.HeaderXRequestID,
}
)
// 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
}
if config.TargetHeader == "" {
config.TargetHeader = echo.HeaderXRequestID
}
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(config.TargetHeader)
if rid == "" {
rid = config.Generator()
}
res.Header().Set(config.TargetHeader, rid)
if config.RequestIDHandler != nil {
config.RequestIDHandler(c, rid)
}
return next(c)
}
}
}
func generator() string {
return random.String(32)
}

View File

@@ -0,0 +1,314 @@
package middleware
import (
"errors"
"github.com/labstack/echo/v4"
"net/http"
"time"
)
// Example for `fmt.Printf`
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogStatus: true,
// LogURI: true,
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// fmt.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status)
// return nil
// },
// }))
//
// Example for Zerolog (https://github.com/rs/zerolog)
// logger := zerolog.New(os.Stdout)
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true,
// LogStatus: true,
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// logger.Info().
// Str("URI", v.URI).
// Int("status", v.Status).
// Msg("request")
//
// return nil
// },
// }))
//
// Example for Zap (https://github.com/uber-go/zap)
// logger, _ := zap.NewProduction()
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true,
// LogStatus: true,
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// logger.Info("request",
// zap.String("URI", v.URI),
// zap.Int("status", v.Status),
// )
//
// return nil
// },
// }))
//
// Example for Logrus (https://github.com/sirupsen/logrus)
// log := logrus.New()
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true,
// LogStatus: true,
// LogValuesFunc: func(c echo.Context, values middleware.RequestLoggerValues) error {
// log.WithFields(logrus.Fields{
// "URI": values.URI,
// "status": values.Status,
// }).Info("request")
//
// return nil
// },
// }))
// RequestLoggerConfig is configuration for Request Logger middleware.
type RequestLoggerConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// BeforeNextFunc defines a function that is called before next middleware or handler is called in chain.
BeforeNextFunc func(c echo.Context)
// LogValuesFunc defines a function that is called with values extracted by logger from request/response.
// Mandatory.
LogValuesFunc func(c echo.Context, v RequestLoggerValues) error
// LogLatency instructs logger to record duration it took to execute rest of the handler chain (next(c) call).
LogLatency bool
// LogProtocol instructs logger to extract request protocol (i.e. `HTTP/1.1` or `HTTP/2`)
LogProtocol bool
// LogRemoteIP instructs logger to extract request remote IP. See `echo.Context.RealIP()` for implementation details.
LogRemoteIP bool
// LogHost instructs logger to extract request host value (i.e. `example.com`)
LogHost bool
// LogMethod instructs logger to extract request method value (i.e. `GET` etc)
LogMethod bool
// LogURI instructs logger to extract request URI (i.e. `/list?lang=en&page=1`)
LogURI bool
// LogURIPath instructs logger to extract request URI path part (i.e. `/list`)
LogURIPath bool
// LogRoutePath instructs logger to extract route path part to which request was matched to (i.e. `/user/:id`)
LogRoutePath bool
// LogRequestID instructs logger to extract request ID from request `X-Request-ID` header or response if request did not have value.
LogRequestID bool
// LogReferer instructs logger to extract request referer values.
LogReferer bool
// LogUserAgent instructs logger to extract request user agent values.
LogUserAgent bool
// LogStatus instructs logger to extract response status code. If handler chain returns an echo.HTTPError,
// the status code is extracted from the echo.HTTPError returned
LogStatus bool
// LogError instructs logger to extract error returned from executed handler chain.
LogError bool
// LogContentLength instructs logger to extract content length header value. Note: this value could be different from
// actual request body size as it could be spoofed etc.
LogContentLength bool
// LogResponseSize instructs logger to extract response content length value. Note: when used with Gzip middleware
// this value may not be always correct.
LogResponseSize bool
// LogHeaders instructs logger to extract given list of headers from request. Note: request can contain more than
// one header with same value so slice of values is been logger for each given header.
//
// Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header
// names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding".
LogHeaders []string
// LogQueryParams instructs logger to extract given list of query parameters from request URI. Note: request can
// contain more than one query parameter with same name so slice of values is been logger for each given query param name.
LogQueryParams []string
// LogFormValues instructs logger to extract given list of form values from request body+URI. Note: request can
// contain more than one form value with same name so slice of values is been logger for each given form value name.
LogFormValues []string
timeNow func() time.Time
}
// RequestLoggerValues contains extracted values from logger.
type RequestLoggerValues struct {
// StartTime is time recorded before next middleware/handler is executed.
StartTime time.Time
// Latency is duration it took to execute rest of the handler chain (next(c) call).
Latency time.Duration
// Protocol is request protocol (i.e. `HTTP/1.1` or `HTTP/2`)
Protocol string
// RemoteIP is request remote IP. See `echo.Context.RealIP()` for implementation details.
RemoteIP string
// Host is request host value (i.e. `example.com`)
Host string
// Method is request method value (i.e. `GET` etc)
Method string
// URI is request URI (i.e. `/list?lang=en&page=1`)
URI string
// URIPath is request URI path part (i.e. `/list`)
URIPath string
// RoutePath is route path part to which request was matched to (i.e. `/user/:id`)
RoutePath string
// RequestID is request ID from request `X-Request-ID` header or response if request did not have value.
RequestID string
// Referer is request referer values.
Referer string
// UserAgent is request user agent values.
UserAgent string
// Status is response status code. Then handler returns an echo.HTTPError then code from there.
Status int
// Error is error returned from executed handler chain.
Error error
// ContentLength is content length header value. Note: this value could be different from actual request body size
// as it could be spoofed etc.
ContentLength string
// ResponseSize is response content length value. Note: when used with Gzip middleware this value may not be always correct.
ResponseSize int64
// Headers are list of headers from request. Note: request can contain more than one header with same value so slice
// of values is been logger for each given header.
// Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header
// names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding".
Headers map[string][]string
// QueryParams are list of query parameters from request URI. Note: request can contain more than one query parameter
// with same name so slice of values is been logger for each given query param name.
QueryParams map[string][]string
// FormValues are list of form values from request body+URI. Note: request can contain more than one form value with
// same name so slice of values is been logger for each given form value name.
FormValues map[string][]string
}
// RequestLoggerWithConfig returns a RequestLogger middleware with config.
func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc {
mw, err := config.ToMiddleware()
if err != nil {
panic(err)
}
return mw
}
// ToMiddleware converts RequestLoggerConfig into middleware or returns an error for invalid configuration.
func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
if config.Skipper == nil {
config.Skipper = DefaultSkipper
}
now = time.Now
if config.timeNow != nil {
now = config.timeNow
}
if config.LogValuesFunc == nil {
return nil, errors.New("missing LogValuesFunc callback function for request logger middleware")
}
logHeaders := len(config.LogHeaders) > 0
headers := append([]string(nil), config.LogHeaders...)
for i, v := range headers {
headers[i] = http.CanonicalHeaderKey(v)
}
logQueryParams := len(config.LogQueryParams) > 0
logFormValues := len(config.LogFormValues) > 0
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()
start := now()
if config.BeforeNextFunc != nil {
config.BeforeNextFunc(c)
}
err := next(c)
v := RequestLoggerValues{
StartTime: start,
}
if config.LogLatency {
v.Latency = now().Sub(start)
}
if config.LogProtocol {
v.Protocol = req.Proto
}
if config.LogRemoteIP {
v.RemoteIP = c.RealIP()
}
if config.LogHost {
v.Host = req.Host
}
if config.LogMethod {
v.Method = req.Method
}
if config.LogURI {
v.URI = req.RequestURI
}
if config.LogURIPath {
p := req.URL.Path
if p == "" {
p = "/"
}
v.URIPath = p
}
if config.LogRoutePath {
v.RoutePath = c.Path()
}
if config.LogRequestID {
id := req.Header.Get(echo.HeaderXRequestID)
if id == "" {
id = res.Header().Get(echo.HeaderXRequestID)
}
v.RequestID = id
}
if config.LogReferer {
v.Referer = req.Referer()
}
if config.LogUserAgent {
v.UserAgent = req.UserAgent()
}
if config.LogStatus {
v.Status = res.Status
if err != nil {
if httpErr, ok := err.(*echo.HTTPError); ok {
v.Status = httpErr.Code
}
}
}
if config.LogError && err != nil {
v.Error = err
}
if config.LogContentLength {
v.ContentLength = req.Header.Get(echo.HeaderContentLength)
}
if config.LogResponseSize {
v.ResponseSize = res.Size
}
if logHeaders {
v.Headers = map[string][]string{}
for _, header := range headers {
if values, ok := req.Header[header]; ok {
v.Headers[header] = values
}
}
}
if logQueryParams {
queryParams := c.QueryParams()
v.QueryParams = map[string][]string{}
for _, param := range config.LogQueryParams {
if values, ok := queryParams[param]; ok {
v.QueryParams[param] = values
}
}
}
if logFormValues {
v.FormValues = map[string][]string{}
for _, formValue := range config.LogFormValues {
if values, ok := req.Form[formValue]; ok {
v.FormValues[formValue] = values
}
}
}
if errOnLog := config.LogValuesFunc(c, v); errOnLog != nil {
return errOnLog
}
return err
}
}, nil
}

View File

@@ -0,0 +1,81 @@
package middleware
import (
"regexp"
"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"`
// RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
// Example:
// "^/old/[0.9]+/": "/new",
// "^/api/.+?/(.*)": "/v2/$1",
RegexRules map[*regexp.Regexp]string `yaml:"regex_rules"`
}
)
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 && config.RegexRules == nil {
panic("echo: rewrite middleware requires url path rewrite rules or regex rules")
}
if config.Skipper == nil {
config.Skipper = DefaultBodyDumpConfig.Skipper
}
if config.RegexRules == nil {
config.RegexRules = make(map[*regexp.Regexp]string)
}
for k, v := range rewriteRulesRegex(config.Rules) {
config.RegexRules[k] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
if err := rewriteURL(config.RegexRules, c.Request()); err != nil {
return err
}
return next(c)
}
}
}

145
vendor/github.com/labstack/echo/v4/middleware/secure.go generated vendored Normal file
View File

@@ -0,0 +1,145 @@
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"`
// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
// of the `Content-Security-Policy` header. This allows iterative updates of the
// content security policy by only reporting the violations that would
// have occurred instead of blocking the resource.
// Optional. Default value false.
CSPReportOnly bool `yaml:"csp_report_only"`
// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
// header, which enables the domain to be included in the HSTS preload list
// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
// Optional. Default value false.
HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
// ReferrerPolicy sets the `Referrer-Policy` header providing security against
// leaking potentially sensitive request paths to third parties.
// Optional. Default value "".
ReferrerPolicy string `yaml:"referrer_policy"`
}
)
var (
// DefaultSecureConfig is the default Secure middleware config.
DefaultSecureConfig = SecureConfig{
Skipper: DefaultSkipper,
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSPreloadEnabled: false,
}
)
// 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"
}
if config.HSTSPreloadEnabled {
subdomains = fmt.Sprintf("%s; preload", subdomains)
}
res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
}
if config.ContentSecurityPolicy != "" {
if config.CSPReportOnly {
res.Header().Set(echo.HeaderContentSecurityPolicyReportOnly, config.ContentSecurityPolicy)
} else {
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
}
}
if config.ReferrerPolicy != "" {
res.Header().Set(echo.HeaderReferrerPolicy, config.ReferrerPolicy)
}
return next(c)
}
}
}

130
vendor/github.com/labstack/echo/v4/middleware/slash.go generated vendored Normal file
View File

@@ -0,0 +1,130 @@
package middleware
import (
"strings"
"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 !strings.HasSuffix(path, "/") {
path += "/"
uri := path
if qs != "" {
uri += "?" + qs
}
// Redirect
if config.RedirectCode != 0 {
return c.Redirect(config.RedirectCode, sanitizeURI(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 && strings.HasSuffix(path, "/") {
path = path[:l]
uri := path
if qs != "" {
uri += "?" + qs
}
// Redirect
if config.RedirectCode != 0 {
return c.Redirect(config.RedirectCode, sanitizeURI(uri))
}
// Forward
req.RequestURI = uri
url.Path = path
}
return next(c)
}
}
}
func sanitizeURI(uri string) string {
// double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
// we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
uri = "/" + strings.TrimLeft(uri, `/\`)
}
return uri
}

276
vendor/github.com/labstack/echo/v4/middleware/static.go generated vendored Normal file
View File

@@ -0,0 +1,276 @@
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"`
// Enable ignoring of the base of the URL path.
// Example: when assigning a static middleware to a non root path group,
// the filesystem path is not doubled
// Optional. Default value false.
IgnoreBase bool `yaml:"ignoreBase"`
// Filesystem provides access to the static content.
// Optional. Defaults to http.Dir(config.Root)
Filesystem http.FileSystem `yaml:"-"`
}
)
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
}
if config.Filesystem == nil {
config.Filesystem = http.Dir(config.Root)
config.Root = "."
}
// 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, filepath.Clean("/"+p)) // "/"+ for security
if config.IgnoreBase {
routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
baseURLPath := path.Base(p)
if baseURLPath == routePath {
i := strings.LastIndex(name, routePath)
name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
}
}
file, err := openFile(config.Filesystem, name)
if err != nil {
if !os.IsNotExist(err) {
return err
}
if err = next(c); err == nil {
return err
}
he, ok := err.(*echo.HTTPError)
if !(ok && config.HTML5 && he.Code == http.StatusNotFound) {
return err
}
file, err = openFile(config.Filesystem, filepath.Join(config.Root, config.Index))
if err != nil {
return err
}
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
if info.IsDir() {
index, err := openFile(config.Filesystem, filepath.Join(name, config.Index))
if err != nil {
if config.Browse {
return listDir(t, name, file, c.Response())
}
if os.IsNotExist(err) {
return next(c)
}
}
defer index.Close()
info, err = index.Stat()
if err != nil {
return err
}
return serveFile(c, index, info)
}
return serveFile(c, file, info)
}
}
}
func openFile(fs http.FileSystem, name string) (http.File, error) {
pathWithSlashes := filepath.ToSlash(name)
return fs.Open(pathWithSlashes)
}
func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
return nil
}
func listDir(t *template.Template, name string, dir http.File, res *echo.Response) (err error) {
files, err := dir.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)
}

View File

@@ -0,0 +1,182 @@
package middleware
import (
"context"
"net/http"
"time"
"github.com/labstack/echo/v4"
)
// ---------------------------------------------------------------------------------------------------------------
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
// WARNING: Timeout middleware causes more problems than it solves.
// WARNING: This middleware should be first middleware as it messes with request Writer and could cause data race if
// it is in other position
//
// Depending on out requirements you could be better of setting timeout to context and
// check its deadline from handler.
//
// For example: create middleware to set timeout to context
// func RequestTimeout(timeout time.Duration) echo.MiddlewareFunc {
// return func(next echo.HandlerFunc) echo.HandlerFunc {
// return func(c echo.Context) error {
// timeoutCtx, cancel := context.WithTimeout(c.Request().Context(), timeout)
// c.SetRequest(c.Request().WithContext(timeoutCtx))
// defer cancel()
// return next(c)
// }
// }
//}
//
// Create handler that checks for context deadline and runs actual task in separate coroutine
// Note: separate coroutine may not be even if you do not want to process continue executing and
// just want to stop long-running handler to stop and you are using "context aware" methods (ala db queries with ctx)
// e.GET("/", func(c echo.Context) error {
//
// doneCh := make(chan error)
// go func(ctx context.Context) {
// doneCh <- myPossiblyLongRunningBackgroundTaskWithCtx(ctx)
// }(c.Request().Context())
//
// select { // wait for task to finish or context to timeout/cancelled
// case err := <-doneCh:
// if err != nil {
// return err
// }
// return c.String(http.StatusOK, "OK")
// case <-c.Request().Context().Done():
// if c.Request().Context().Err() == context.DeadlineExceeded {
// return c.String(http.StatusServiceUnavailable, "timeout")
// }
// return c.Request().Context().Err()
// }
//
// })
//
type (
// TimeoutConfig defines the config for Timeout middleware.
TimeoutConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
// It can be used to define a custom timeout error message
ErrorMessage string
// OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
// request timeouted and we already had sent the error code (503) and message response to the client.
// NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
// will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
OnTimeoutRouteErrorHandler func(err error, c echo.Context)
// Timeout configures a timeout for the middleware, defaults to 0 for no timeout
// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
// difference over 500microseconds (0.5millisecond) response seems to be reliable
Timeout time.Duration
}
)
var (
// DefaultTimeoutConfig is the default Timeout middleware config.
DefaultTimeoutConfig = TimeoutConfig{
Skipper: DefaultSkipper,
Timeout: 0,
ErrorMessage: "",
}
)
// Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
// call runs for longer than its time limit. NB: timeout does not stop handler execution.
func Timeout() echo.MiddlewareFunc {
return TimeoutWithConfig(DefaultTimeoutConfig)
}
// TimeoutWithConfig returns a Timeout middleware with config.
// See: `Timeout()`.
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultTimeoutConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) || config.Timeout == 0 {
return next(c)
}
handlerWrapper := echoHandlerFuncWrapper{
ctx: c,
handler: next,
errChan: make(chan error, 1),
errHandler: config.OnTimeoutRouteErrorHandler,
}
handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage)
handler.ServeHTTP(c.Response().Writer, c.Request())
select {
case err := <-handlerWrapper.errChan:
return err
default:
return nil
}
}
}
}
type echoHandlerFuncWrapper struct {
ctx echo.Context
handler echo.HandlerFunc
errHandler func(err error, c echo.Context)
errChan chan error
}
func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// replace echo.Context Request with the one provided by TimeoutHandler to let later middlewares/handler on the chain
// handle properly it's cancellation
t.ctx.SetRequest(r)
// replace writer with TimeoutHandler custom one. This will guarantee that
// `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
originalWriter := t.ctx.Response().Writer
t.ctx.Response().Writer = rw
// in case of panic we restore original writer and call panic again
// so it could be handled with global middleware Recover()
defer func() {
if err := recover(); err != nil {
t.ctx.Response().Writer = originalWriter
panic(err)
}
}()
err := t.handler(t.ctx)
if ctxErr := r.Context().Err(); ctxErr == context.DeadlineExceeded {
if err != nil && t.errHandler != nil {
t.errHandler(err, t.ctx)
}
return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers
}
// we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
// and should not anymore send additional headers/data
// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
if err != nil {
// Error must be written into Writer created in `http.TimeoutHandler` so to get Response into `commited` state.
// So call global error handler to write error to the client. This is needed or `http.TimeoutHandler` will send
// status code by itself and after that our tries to write status code will not work anymore and/or create errors in
// log about `superfluous response.WriteHeader call from`
t.ctx.Error(err)
// we pass error from handler to middlewares up in handler chain to act on it if needed. But this means that
// global error handler is probably be called twice as `t.ctx.Error` already does that.
// NB: later call of the global error handler or middlewares will not take any effect, as echo.Response will be
// already marked as `committed` because we called global error handler above.
t.ctx.Response().Writer = originalWriter // make sure we restore before we signal original coroutine about the error
t.errChan <- err
return
}
t.ctx.Response().Writer = originalWriter
}

54
vendor/github.com/labstack/echo/v4/middleware/util.go generated vendored Normal file
View File

@@ -0,0 +1,54 @@
package middleware
import (
"strings"
)
func matchScheme(domain, pattern string) bool {
didx := strings.Index(domain, ":")
pidx := strings.Index(pattern, ":")
return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
}
// matchSubdomain compares authority with wildcard
func matchSubdomain(domain, pattern string) bool {
if !matchScheme(domain, pattern) {
return false
}
didx := strings.Index(domain, "://")
pidx := strings.Index(pattern, "://")
if didx == -1 || pidx == -1 {
return false
}
domAuth := domain[didx+3:]
// to avoid long loop by invalid long domain
if len(domAuth) > 253 {
return false
}
patAuth := pattern[pidx+3:]
domComp := strings.Split(domAuth, ".")
patComp := strings.Split(patAuth, ".")
for i := len(domComp)/2 - 1; i >= 0; i-- {
opp := len(domComp) - 1 - i
domComp[i], domComp[opp] = domComp[opp], domComp[i]
}
for i := len(patComp)/2 - 1; i >= 0; i-- {
opp := len(patComp) - 1 - i
patComp[i], patComp[opp] = patComp[opp], patComp[i]
}
for i, v := range domComp {
if len(patComp) <= i {
return false
}
p := patComp[i]
if p == "*" {
return true
}
if p != v {
return false
}
}
return false
}

104
vendor/github.com/labstack/echo/v4/response.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
package echo
import (
"bufio"
"net"
"net/http"
)
type (
// Response wraps an http.ResponseWriter and implements its interface to be used
// by an HTTP handler to construct an HTTP response.
// See: https://golang.org/pkg/net/http/#ResponseWriter
Response struct {
echo *Echo
beforeFuncs []func()
afterFuncs []func()
Writer http.ResponseWriter
Status int
Size int64
Committed bool
}
)
// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
return &Response{Writer: w, echo: e}
}
// Header returns the header map for the writer that will be sent by
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
// no effect unless the modified headers were declared as trailers by setting
// the "Trailer" header before the call to WriteHeader (see example)
// To suppress implicit response headers, set their value to nil.
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
func (r *Response) Header() http.Header {
return r.Writer.Header()
}
// Before registers a function which is called just before the response is written.
func (r *Response) Before(fn func()) {
r.beforeFuncs = append(r.beforeFuncs, fn)
}
// After registers a function which is called just after the response is written.
// If the `Content-Length` is unknown, none of the after function is executed.
func (r *Response) After(fn func()) {
r.afterFuncs = append(r.afterFuncs, fn)
}
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
// not called explicitly, the first call to Write will trigger an implicit
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
// used to send error codes.
func (r *Response) WriteHeader(code int) {
if r.Committed {
r.echo.Logger.Warn("response already committed")
return
}
r.Status = code
for _, fn := range r.beforeFuncs {
fn()
}
r.Writer.WriteHeader(r.Status)
r.Committed = true
}
// Write writes the data to the connection as part of an HTTP reply.
func (r *Response) Write(b []byte) (n int, err error) {
if !r.Committed {
if r.Status == 0 {
r.Status = http.StatusOK
}
r.WriteHeader(r.Status)
}
n, err = r.Writer.Write(b)
r.Size += int64(n)
for _, fn := range r.afterFuncs {
fn()
}
return
}
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() {
r.Writer.(http.Flusher).Flush()
}
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack()
}
func (r *Response) reset(w http.ResponseWriter) {
r.beforeFuncs = nil
r.afterFuncs = nil
r.Writer = w
r.Size = 0
r.Status = http.StatusOK
r.Committed = false
}

627
vendor/github.com/labstack/echo/v4/router.go generated vendored Normal file
View File

@@ -0,0 +1,627 @@
package echo
import (
"bytes"
"net/http"
)
type (
// Router is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing.
Router struct {
tree *node
routes map[string]*Route
echo *Echo
}
node struct {
kind kind
label byte
prefix string
parent *node
staticChildren children
ppath string
pnames []string
methodHandler *methodHandler
paramChild *node
anyChild *node
// isLeaf indicates that node does not have child routes
isLeaf bool
// isHandler indicates that node has at least one handler registered to it
isHandler bool
}
kind uint8
children []*node
methodHandler struct {
connect HandlerFunc
delete HandlerFunc
get HandlerFunc
head HandlerFunc
options HandlerFunc
patch HandlerFunc
post HandlerFunc
propfind HandlerFunc
put HandlerFunc
trace HandlerFunc
report HandlerFunc
allowHeader string
}
)
const (
staticKind kind = iota
paramKind
anyKind
paramLabel = byte(':')
anyLabel = byte('*')
)
func (m *methodHandler) isHandler() bool {
return m.connect != nil ||
m.delete != nil ||
m.get != nil ||
m.head != nil ||
m.options != nil ||
m.patch != nil ||
m.post != nil ||
m.propfind != nil ||
m.put != nil ||
m.trace != nil ||
m.report != nil
}
func (m *methodHandler) updateAllowHeader() {
buf := new(bytes.Buffer)
buf.WriteString(http.MethodOptions)
if m.connect != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodConnect)
}
if m.delete != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodDelete)
}
if m.get != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodGet)
}
if m.head != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodHead)
}
if m.patch != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodPatch)
}
if m.post != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodPost)
}
if m.propfind != nil {
buf.WriteString(", PROPFIND")
}
if m.put != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodPut)
}
if m.trace != nil {
buf.WriteString(", ")
buf.WriteString(http.MethodTrace)
}
if m.report != nil {
buf.WriteString(", REPORT")
}
m.allowHeader = buf.String()
}
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router {
return &Router{
tree: &node{
methodHandler: new(methodHandler),
},
routes: map[string]*Route{},
echo: e,
}
}
// Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path
if path == "" {
path = "/"
}
if path[0] != '/' {
path = "/" + path
}
pnames := []string{} // Param names
ppath := path // Pristine path
if h == nil && r.echo.Logger != nil {
// FIXME: in future we should return error
r.echo.Logger.Errorf("Adding route without handler function: %v:%v", method, path)
}
for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
if path[i] == ':' {
if i > 0 && path[i-1] == '\\' {
path = path[:i-1] + path[i:]
i--
lcpIndex--
continue
}
j := i + 1
r.insert(method, path[:i], nil, staticKind, "", nil)
for ; i < lcpIndex && path[i] != '/'; i++ {
}
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
i, lcpIndex = j, len(path)
if i == lcpIndex {
// path node is last fragment of route path. ie. `/users/:id`
r.insert(method, path[:i], h, paramKind, ppath, pnames)
} else {
r.insert(method, path[:i], nil, paramKind, "", nil)
}
} else if path[i] == '*' {
r.insert(method, path[:i], nil, staticKind, "", nil)
pnames = append(pnames, "*")
r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
}
}
r.insert(method, path, h, staticKind, ppath, pnames)
}
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
// Adjust max param
paramLen := len(pnames)
if *r.echo.maxParam < paramLen {
*r.echo.maxParam = paramLen
}
currentNode := r.tree // Current node as root
if currentNode == nil {
panic("echo: invalid method")
}
search := path
for {
searchLen := len(search)
prefixLen := len(currentNode.prefix)
lcpLen := 0
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
}
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
if lcpLen == 0 {
// At root node
currentNode.label = search[0]
currentNode.prefix = search
if h != nil {
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
}
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else if lcpLen < prefixLen {
// Split node
n := newNode(
currentNode.kind,
currentNode.prefix[lcpLen:],
currentNode,
currentNode.staticChildren,
currentNode.methodHandler,
currentNode.ppath,
currentNode.pnames,
currentNode.paramChild,
currentNode.anyChild,
)
// Update parent path for all children to new node
for _, child := range currentNode.staticChildren {
child.parent = n
}
if currentNode.paramChild != nil {
currentNode.paramChild.parent = n
}
if currentNode.anyChild != nil {
currentNode.anyChild.parent = n
}
// Reset parent node
currentNode.kind = staticKind
currentNode.label = currentNode.prefix[0]
currentNode.prefix = currentNode.prefix[:lcpLen]
currentNode.staticChildren = nil
currentNode.methodHandler = new(methodHandler)
currentNode.ppath = ""
currentNode.pnames = nil
currentNode.paramChild = nil
currentNode.anyChild = nil
currentNode.isLeaf = false
currentNode.isHandler = false
// Only Static children could reach here
currentNode.addStaticChild(n)
if lcpLen == searchLen {
// At parent node
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
} else {
// Create child node
n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
// Only Static children could reach here
currentNode.addStaticChild(n)
}
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else if lcpLen < searchLen {
search = search[lcpLen:]
c := currentNode.findChildWithLabel(search[0])
if c != nil {
// Go deeper
currentNode = c
continue
}
// Create child node
n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
switch t {
case staticKind:
currentNode.addStaticChild(n)
case paramKind:
currentNode.paramChild = n
case anyKind:
currentNode.anyChild = n
}
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else {
// Node already exists
if h != nil {
currentNode.addHandler(method, h)
currentNode.ppath = ppath
if len(currentNode.pnames) == 0 { // Issue #729
currentNode.pnames = pnames
}
}
}
return
}
}
func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
return &node{
kind: t,
label: pre[0],
prefix: pre,
parent: p,
staticChildren: sc,
ppath: ppath,
pnames: pnames,
methodHandler: mh,
paramChild: paramChildren,
anyChild: anyChildren,
isLeaf: sc == nil && paramChildren == nil && anyChildren == nil,
isHandler: mh.isHandler(),
}
}
func (n *node) addStaticChild(c *node) {
n.staticChildren = append(n.staticChildren, c)
}
func (n *node) findStaticChild(l byte) *node {
for _, c := range n.staticChildren {
if c.label == l {
return c
}
}
return nil
}
func (n *node) findChildWithLabel(l byte) *node {
for _, c := range n.staticChildren {
if c.label == l {
return c
}
}
if l == paramLabel {
return n.paramChild
}
if l == anyLabel {
return n.anyChild
}
return nil
}
func (n *node) addHandler(method string, h HandlerFunc) {
switch method {
case http.MethodConnect:
n.methodHandler.connect = h
case http.MethodDelete:
n.methodHandler.delete = h
case http.MethodGet:
n.methodHandler.get = h
case http.MethodHead:
n.methodHandler.head = h
case http.MethodOptions:
n.methodHandler.options = h
case http.MethodPatch:
n.methodHandler.patch = h
case http.MethodPost:
n.methodHandler.post = h
case PROPFIND:
n.methodHandler.propfind = h
case http.MethodPut:
n.methodHandler.put = h
case http.MethodTrace:
n.methodHandler.trace = h
case REPORT:
n.methodHandler.report = h
}
n.methodHandler.updateAllowHeader()
if h != nil {
n.isHandler = true
} else {
n.isHandler = n.methodHandler.isHandler()
}
}
func (n *node) findHandler(method string) HandlerFunc {
switch method {
case http.MethodConnect:
return n.methodHandler.connect
case http.MethodDelete:
return n.methodHandler.delete
case http.MethodGet:
return n.methodHandler.get
case http.MethodHead:
return n.methodHandler.head
case http.MethodOptions:
return n.methodHandler.options
case http.MethodPatch:
return n.methodHandler.patch
case http.MethodPost:
return n.methodHandler.post
case PROPFIND:
return n.methodHandler.propfind
case http.MethodPut:
return n.methodHandler.put
case http.MethodTrace:
return n.methodHandler.trace
case REPORT:
return n.methodHandler.report
default:
return nil
}
}
func optionsMethodHandler(allowMethods string) func(c Context) error {
return func(c Context) error {
// Note: we are not handling most of the CORS headers here. CORS is handled by CORS middleware
// 'OPTIONS' method RFC: https://httpwg.org/specs/rfc7231.html#OPTIONS
// 'Allow' header RFC: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
c.Response().Header().Add(HeaderAllow, allowMethods)
return c.NoContent(http.StatusNoContent)
}
}
// Find lookup a handler registered for method and path. It also parses URL for path
// parameters and load them into context.
//
// For performance:
//
// - Get context from `Echo#AcquireContext()`
// - Reset it `Context#Reset()`
// - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context)
ctx.path = path
currentNode := r.tree // Current node as root
var (
previousBestMatchNode *node
matchedHandler HandlerFunc
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
// and search value gets shorter and shorter.
search = path
searchIndex = 0
paramIndex int // Param counter
paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
)
// Backtracking is needed when a dead end (leaf node) is reached in the router tree.
// To backtrack the current node will be changed to the parent node and the next kind for the
// router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
// For example if there is no static node match we should check parent next sibling by kind (param).
// Backtracking itself does not check if there is a next sibling, this is done by the router logic.
backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
previous := currentNode
currentNode = previous.parent
valid = currentNode != nil
// Next node type by priority
if previous.kind == anyKind {
nextNodeKind = staticKind
} else {
nextNodeKind = previous.kind + 1
}
if fromKind == staticKind {
// when backtracking is done from static kind block we did not change search so nothing to restore
return
}
// restore search to value it was before we move to current node we are backtracking from.
if previous.kind == staticKind {
searchIndex -= len(previous.prefix)
} else {
paramIndex--
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
searchIndex -= len(paramValues[paramIndex])
paramValues[paramIndex] = ""
}
search = path[searchIndex:]
return
}
// Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
// Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
// Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
// Search order/priority is: static > param > any.
//
// Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
// and hoping to (goto statement) next block by priority to check if it is the match.
for {
prefixLen := 0 // Prefix length
lcpLen := 0 // LCP (longest common prefix) length
if currentNode.kind == staticKind {
searchLen := len(search)
prefixLen = len(currentNode.prefix)
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
}
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
}
if lcpLen != prefixLen {
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(staticKind)
if !ok {
return // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
//} else if nk == anyKind {
// goto Any
} else {
// Not found (this should never be possible for static node we are looking currently)
break
}
}
// The full prefix has matched, remove the prefix from the remaining search
search = search[lcpLen:]
searchIndex = searchIndex + lcpLen
// Finish routing if no remaining search and we are on a node with handler and matching method type
if search == "" && currentNode.isHandler {
// check if current node has handler registered for http method we are looking for. we store currentNode as
// best matching in case we do no find no more routes matching this path+method
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
if h := currentNode.findHandler(method); h != nil {
matchedHandler = h
break
}
}
// Static node
if search != "" {
if child := currentNode.findStaticChild(search[0]); child != nil {
currentNode = child
continue
}
}
Param:
// Param node
if child := currentNode.paramChild; search != "" && child != nil {
currentNode = child
i := 0
l := len(search)
if currentNode.isLeaf {
// when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
i = l
} else {
for ; i < l && search[i] != '/'; i++ {
}
}
paramValues[paramIndex] = search[:i]
paramIndex++
search = search[i:]
searchIndex = searchIndex + i
continue
}
Any:
// Any node
if child := currentNode.anyChild; child != nil {
// If any node is found, use remaining path for paramValues
currentNode = child
paramValues[len(currentNode.pnames)-1] = search
// update indexes/search in case we need to backtrack when no handler match is found
paramIndex++
searchIndex += +len(search)
search = ""
// check if current node has handler registered for http method we are looking for. we store currentNode as
// best matching in case we do no find no more routes matching this path+method
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
if h := currentNode.findHandler(method); h != nil {
matchedHandler = h
break
}
}
// Let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(anyKind)
if !ok {
break // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
} else if nk == anyKind {
goto Any
} else {
// Not found
break
}
}
if currentNode == nil && previousBestMatchNode == nil {
return // nothing matched at all
}
if matchedHandler != nil {
ctx.handler = matchedHandler
} else {
// use previous match as basis. although we have no matching handler we have path match.
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
currentNode = previousBestMatchNode
ctx.handler = NotFoundHandler
if currentNode.isHandler {
ctx.Set(ContextKeyHeaderAllow, currentNode.methodHandler.allowHeader)
ctx.handler = MethodNotAllowedHandler
if method == http.MethodOptions {
ctx.handler = optionsMethodHandler(currentNode.methodHandler.allowHeader)
}
}
}
ctx.path = currentNode.ppath
ctx.pnames = currentNode.pnames
}

22
vendor/github.com/labstack/gommon/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 labstack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

29
vendor/github.com/labstack/gommon/bytes/README.md generated vendored Normal file
View File

@@ -0,0 +1,29 @@
# Bytes
- Format bytes integer to human readable bytes string.
- Parse human readable bytes string to bytes integer.
## Installation
```go
go get github.com/labstack/gommon/bytes
```
## [Usage](https://github.com/labstack/gommon/blob/master/bytes/bytes_test.go)
### Format
```go
println(bytes.Format(13231323))
```
`12.62MB`
### Parse
```go
b, _ = Parse("2M")
println(b)
```
`2097152`

109
vendor/github.com/labstack/gommon/bytes/bytes.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
package bytes
import (
"fmt"
"regexp"
"strconv"
"strings"
)
type (
// Bytes struct
Bytes struct{}
)
const (
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
KB
MB
GB
TB
PB
EB
)
var (
pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]B?|B?)$`)
global = New()
)
// New creates a Bytes instance.
func New() *Bytes {
return &Bytes{}
}
// Format formats bytes integer to human readable string.
// For example, 31323 bytes will return 30.59KB.
func (*Bytes) Format(b int64) string {
multiple := ""
value := float64(b)
switch {
case b >= EB:
value /= EB
multiple = "EB"
case b >= PB:
value /= PB
multiple = "PB"
case b >= TB:
value /= TB
multiple = "TB"
case b >= GB:
value /= GB
multiple = "GB"
case b >= MB:
value /= MB
multiple = "MB"
case b >= KB:
value /= KB
multiple = "KB"
case b == 0:
return "0"
default:
return strconv.FormatInt(b, 10) + "B"
}
return fmt.Sprintf("%.2f%s", value, multiple)
}
// Parse parses human readable bytes string to bytes integer.
// For example, 6GB (6G is also valid) will return 6442450944.
func (*Bytes) Parse(value string) (i int64, err error) {
parts := pattern.FindStringSubmatch(value)
if len(parts) < 3 {
return 0, fmt.Errorf("error parsing value=%s", value)
}
bytesString := parts[1]
multiple := strings.ToUpper(parts[2])
bytes, err := strconv.ParseFloat(bytesString, 64)
if err != nil {
return
}
switch multiple {
default:
return int64(bytes), nil
case "K", "KB":
return int64(bytes * KB), nil
case "M", "MB":
return int64(bytes * MB), nil
case "G", "GB":
return int64(bytes * GB), nil
case "T", "TB":
return int64(bytes * TB), nil
case "P", "PB":
return int64(bytes * PB), nil
case "E", "EB":
return int64(bytes * EB), nil
}
}
// Format wraps global Bytes's Format function.
func Format(b int64) string {
return global.Format(b)
}
// Parse wraps global Bytes's Parse function.
func Parse(val string) (int64, error) {
return global.Parse(val)
}

86
vendor/github.com/labstack/gommon/color/README.md generated vendored Normal file
View File

@@ -0,0 +1,86 @@
# Color
Style terminal text.
## Installation
```sh
go get github.com/labstack/gommon/color
```
## Windows?
Try [cmder](http://bliker.github.io/cmder) or https://github.com/mattn/go-colorable
## [Usage](https://github.com/labstack/gommon/blob/master/color/color_test.go)
```sh
import github.com/labstack/gommon/color
```
### Colored text
```go
color.Println(color.Black("black"))
color.Println(color.Red("red"))
color.Println(color.Green("green"))
color.Println(color.Yellow("yellow"))
color.Println(color.Blue("blue"))
color.Println(color.Magenta("magenta"))
color.Println(color.Cyan("cyan"))
color.Println(color.White("white"))
color.Println(color.Grey("grey"))
```
![Colored Text](http://i.imgur.com/8RtY1QR.png)
### Colored background
```go
color.Println(color.BlackBg("black background", color.Wht))
color.Println(color.RedBg("red background"))
color.Println(color.GreenBg("green background"))
color.Println(color.YellowBg("yellow background"))
color.Println(color.BlueBg("blue background"))
color.Println(color.MagentaBg("magenta background"))
color.Println(color.CyanBg("cyan background"))
color.Println(color.WhiteBg("white background"))
```
![Colored Background](http://i.imgur.com/SrrS6lw.png)
### Emphasis
```go
color.Println(color.Bold("bold"))
color.Println(color.Dim("dim"))
color.Println(color.Italic("italic"))
color.Println(color.Underline("underline"))
color.Println(color.Inverse("inverse"))
color.Println(color.Hidden("hidden"))
color.Println(color.Strikeout("strikeout"))
```
![Emphasis](http://i.imgur.com/3RSJBbc.png)
### Mix and match
```go
color.Println(color.Green("bold green with white background", color.B, color.WhtBg))
color.Println(color.Red("underline red", color.U))
color.Println(color.Yellow("dim yellow", color.D))
color.Println(color.Cyan("inverse cyan", color.In))
color.Println(color.Blue("bold underline dim blue", color.B, color.U, color.D))
```
![Mix and match](http://i.imgur.com/jWGq9Ca.png)
### Enable/Disable the package
```go
color.Disable()
color.Enable()
```
### New instance
```go
c := New()
c.Green("green")
```

407
vendor/github.com/labstack/gommon/color/color.go generated vendored Normal file
View File

@@ -0,0 +1,407 @@
package color
import (
"bytes"
"fmt"
"io"
"os"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
type (
inner func(interface{}, []string, *Color) string
)
// Color styles
const (
// Blk Black text style
Blk = "30"
// Rd red text style
Rd = "31"
// Grn green text style
Grn = "32"
// Yel yellow text style
Yel = "33"
// Blu blue text style
Blu = "34"
// Mgn magenta text style
Mgn = "35"
// Cyn cyan text style
Cyn = "36"
// Wht white text style
Wht = "37"
// Gry grey text style
Gry = "90"
// BlkBg black background style
BlkBg = "40"
// RdBg red background style
RdBg = "41"
// GrnBg green background style
GrnBg = "42"
// YelBg yellow background style
YelBg = "43"
// BluBg blue background style
BluBg = "44"
// MgnBg magenta background style
MgnBg = "45"
// CynBg cyan background style
CynBg = "46"
// WhtBg white background style
WhtBg = "47"
// R reset emphasis style
R = "0"
// B bold emphasis style
B = "1"
// D dim emphasis style
D = "2"
// I italic emphasis style
I = "3"
// U underline emphasis style
U = "4"
// In inverse emphasis style
In = "7"
// H hidden emphasis style
H = "8"
// S strikeout emphasis style
S = "9"
)
var (
black = outer(Blk)
red = outer(Rd)
green = outer(Grn)
yellow = outer(Yel)
blue = outer(Blu)
magenta = outer(Mgn)
cyan = outer(Cyn)
white = outer(Wht)
grey = outer(Gry)
blackBg = outer(BlkBg)
redBg = outer(RdBg)
greenBg = outer(GrnBg)
yellowBg = outer(YelBg)
blueBg = outer(BluBg)
magentaBg = outer(MgnBg)
cyanBg = outer(CynBg)
whiteBg = outer(WhtBg)
reset = outer(R)
bold = outer(B)
dim = outer(D)
italic = outer(I)
underline = outer(U)
inverse = outer(In)
hidden = outer(H)
strikeout = outer(S)
global = New()
)
func outer(n string) inner {
return func(msg interface{}, styles []string, c *Color) string {
// TODO: Drop fmt to boost performance?
if c.disabled {
return fmt.Sprintf("%v", msg)
}
b := new(bytes.Buffer)
b.WriteString("\x1b[")
b.WriteString(n)
for _, s := range styles {
b.WriteString(";")
b.WriteString(s)
}
b.WriteString("m")
return fmt.Sprintf("%s%v\x1b[0m", b.String(), msg)
}
}
type (
Color struct {
output io.Writer
disabled bool
}
)
// New creates a Color instance.
func New() (c *Color) {
c = new(Color)
c.SetOutput(colorable.NewColorableStdout())
return
}
// Output returns the output.
func (c *Color) Output() io.Writer {
return c.output
}
// SetOutput sets the output.
func (c *Color) SetOutput(w io.Writer) {
c.output = w
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
c.disabled = true
}
}
// Disable disables the colors and styles.
func (c *Color) Disable() {
c.disabled = true
}
// Enable enables the colors and styles.
func (c *Color) Enable() {
c.disabled = false
}
// Print is analogous to `fmt.Print` with termial detection.
func (c *Color) Print(args ...interface{}) {
fmt.Fprint(c.output, args...)
}
// Println is analogous to `fmt.Println` with termial detection.
func (c *Color) Println(args ...interface{}) {
fmt.Fprintln(c.output, args...)
}
// Printf is analogous to `fmt.Printf` with termial detection.
func (c *Color) Printf(format string, args ...interface{}) {
fmt.Fprintf(c.output, format, args...)
}
func (c *Color) Black(msg interface{}, styles ...string) string {
return black(msg, styles, c)
}
func (c *Color) Red(msg interface{}, styles ...string) string {
return red(msg, styles, c)
}
func (c *Color) Green(msg interface{}, styles ...string) string {
return green(msg, styles, c)
}
func (c *Color) Yellow(msg interface{}, styles ...string) string {
return yellow(msg, styles, c)
}
func (c *Color) Blue(msg interface{}, styles ...string) string {
return blue(msg, styles, c)
}
func (c *Color) Magenta(msg interface{}, styles ...string) string {
return magenta(msg, styles, c)
}
func (c *Color) Cyan(msg interface{}, styles ...string) string {
return cyan(msg, styles, c)
}
func (c *Color) White(msg interface{}, styles ...string) string {
return white(msg, styles, c)
}
func (c *Color) Grey(msg interface{}, styles ...string) string {
return grey(msg, styles, c)
}
func (c *Color) BlackBg(msg interface{}, styles ...string) string {
return blackBg(msg, styles, c)
}
func (c *Color) RedBg(msg interface{}, styles ...string) string {
return redBg(msg, styles, c)
}
func (c *Color) GreenBg(msg interface{}, styles ...string) string {
return greenBg(msg, styles, c)
}
func (c *Color) YellowBg(msg interface{}, styles ...string) string {
return yellowBg(msg, styles, c)
}
func (c *Color) BlueBg(msg interface{}, styles ...string) string {
return blueBg(msg, styles, c)
}
func (c *Color) MagentaBg(msg interface{}, styles ...string) string {
return magentaBg(msg, styles, c)
}
func (c *Color) CyanBg(msg interface{}, styles ...string) string {
return cyanBg(msg, styles, c)
}
func (c *Color) WhiteBg(msg interface{}, styles ...string) string {
return whiteBg(msg, styles, c)
}
func (c *Color) Reset(msg interface{}, styles ...string) string {
return reset(msg, styles, c)
}
func (c *Color) Bold(msg interface{}, styles ...string) string {
return bold(msg, styles, c)
}
func (c *Color) Dim(msg interface{}, styles ...string) string {
return dim(msg, styles, c)
}
func (c *Color) Italic(msg interface{}, styles ...string) string {
return italic(msg, styles, c)
}
func (c *Color) Underline(msg interface{}, styles ...string) string {
return underline(msg, styles, c)
}
func (c *Color) Inverse(msg interface{}, styles ...string) string {
return inverse(msg, styles, c)
}
func (c *Color) Hidden(msg interface{}, styles ...string) string {
return hidden(msg, styles, c)
}
func (c *Color) Strikeout(msg interface{}, styles ...string) string {
return strikeout(msg, styles, c)
}
// Output returns the output.
func Output() io.Writer {
return global.output
}
// SetOutput sets the output.
func SetOutput(w io.Writer) {
global.SetOutput(w)
}
func Disable() {
global.Disable()
}
func Enable() {
global.Enable()
}
// Print is analogous to `fmt.Print` with termial detection.
func Print(args ...interface{}) {
global.Print(args...)
}
// Println is analogous to `fmt.Println` with termial detection.
func Println(args ...interface{}) {
global.Println(args...)
}
// Printf is analogous to `fmt.Printf` with termial detection.
func Printf(format string, args ...interface{}) {
global.Printf(format, args...)
}
func Black(msg interface{}, styles ...string) string {
return global.Black(msg, styles...)
}
func Red(msg interface{}, styles ...string) string {
return global.Red(msg, styles...)
}
func Green(msg interface{}, styles ...string) string {
return global.Green(msg, styles...)
}
func Yellow(msg interface{}, styles ...string) string {
return global.Yellow(msg, styles...)
}
func Blue(msg interface{}, styles ...string) string {
return global.Blue(msg, styles...)
}
func Magenta(msg interface{}, styles ...string) string {
return global.Magenta(msg, styles...)
}
func Cyan(msg interface{}, styles ...string) string {
return global.Cyan(msg, styles...)
}
func White(msg interface{}, styles ...string) string {
return global.White(msg, styles...)
}
func Grey(msg interface{}, styles ...string) string {
return global.Grey(msg, styles...)
}
func BlackBg(msg interface{}, styles ...string) string {
return global.BlackBg(msg, styles...)
}
func RedBg(msg interface{}, styles ...string) string {
return global.RedBg(msg, styles...)
}
func GreenBg(msg interface{}, styles ...string) string {
return global.GreenBg(msg, styles...)
}
func YellowBg(msg interface{}, styles ...string) string {
return global.YellowBg(msg, styles...)
}
func BlueBg(msg interface{}, styles ...string) string {
return global.BlueBg(msg, styles...)
}
func MagentaBg(msg interface{}, styles ...string) string {
return global.MagentaBg(msg, styles...)
}
func CyanBg(msg interface{}, styles ...string) string {
return global.CyanBg(msg, styles...)
}
func WhiteBg(msg interface{}, styles ...string) string {
return global.WhiteBg(msg, styles...)
}
func Reset(msg interface{}, styles ...string) string {
return global.Reset(msg, styles...)
}
func Bold(msg interface{}, styles ...string) string {
return global.Bold(msg, styles...)
}
func Dim(msg interface{}, styles ...string) string {
return global.Dim(msg, styles...)
}
func Italic(msg interface{}, styles ...string) string {
return global.Italic(msg, styles...)
}
func Underline(msg interface{}, styles ...string) string {
return global.Underline(msg, styles...)
}
func Inverse(msg interface{}, styles ...string) string {
return global.Inverse(msg, styles...)
}
func Hidden(msg interface{}, styles ...string) string {
return global.Hidden(msg, styles...)
}
func Strikeout(msg interface{}, styles ...string) string {
return global.Strikeout(msg, styles...)
}

5
vendor/github.com/labstack/gommon/log/README.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
## WORK IN PROGRESS
### Usage
[log_test.go](log_test.go)

13
vendor/github.com/labstack/gommon/log/color.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// +build !appengine
package log
import (
"io"
"github.com/mattn/go-colorable"
)
func output() io.Writer {
return colorable.NewColorableStdout()
}

416
vendor/github.com/labstack/gommon/log/log.go generated vendored Normal file
View File

@@ -0,0 +1,416 @@
package log
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path"
"runtime"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/mattn/go-isatty"
"github.com/valyala/fasttemplate"
"github.com/labstack/gommon/color"
)
type (
Logger struct {
prefix string
level uint32
skip int
output io.Writer
template *fasttemplate.Template
levels []string
color *color.Color
bufferPool sync.Pool
mutex sync.Mutex
}
Lvl uint8
JSON map[string]interface{}
)
const (
DEBUG Lvl = iota + 1
INFO
WARN
ERROR
OFF
panicLevel
fatalLevel
)
var (
global = New("-")
defaultHeader = `{"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}",` +
`"file":"${short_file}","line":"${line}"}`
)
func init() {
global.skip = 3
}
func New(prefix string) (l *Logger) {
l = &Logger{
level: uint32(INFO),
skip: 2,
prefix: prefix,
template: l.newTemplate(defaultHeader),
color: color.New(),
bufferPool: sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 256))
},
},
}
l.initLevels()
l.SetOutput(output())
return
}
func (l *Logger) initLevels() {
l.levels = []string{
"-",
l.color.Blue("DEBUG"),
l.color.Green("INFO"),
l.color.Yellow("WARN"),
l.color.Red("ERROR"),
"",
l.color.Yellow("PANIC", color.U),
l.color.Red("FATAL", color.U),
}
}
func (l *Logger) newTemplate(format string) *fasttemplate.Template {
return fasttemplate.New(format, "${", "}")
}
func (l *Logger) DisableColor() {
l.color.Disable()
l.initLevels()
}
func (l *Logger) EnableColor() {
l.color.Enable()
l.initLevels()
}
func (l *Logger) Prefix() string {
return l.prefix
}
func (l *Logger) SetPrefix(p string) {
l.prefix = p
}
func (l *Logger) Level() Lvl {
return Lvl(atomic.LoadUint32(&l.level))
}
func (l *Logger) SetLevel(level Lvl) {
atomic.StoreUint32(&l.level, uint32(level))
}
func (l *Logger) Output() io.Writer {
return l.output
}
func (l *Logger) SetOutput(w io.Writer) {
l.output = w
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
l.DisableColor()
}
}
func (l *Logger) Color() *color.Color {
return l.color
}
func (l *Logger) SetHeader(h string) {
l.template = l.newTemplate(h)
}
func (l *Logger) Print(i ...interface{}) {
l.log(0, "", i...)
// fmt.Fprintln(l.output, i...)
}
func (l *Logger) Printf(format string, args ...interface{}) {
l.log(0, format, args...)
}
func (l *Logger) Printj(j JSON) {
l.log(0, "json", j)
}
func (l *Logger) Debug(i ...interface{}) {
l.log(DEBUG, "", i...)
}
func (l *Logger) Debugf(format string, args ...interface{}) {
l.log(DEBUG, format, args...)
}
func (l *Logger) Debugj(j JSON) {
l.log(DEBUG, "json", j)
}
func (l *Logger) Info(i ...interface{}) {
l.log(INFO, "", i...)
}
func (l *Logger) Infof(format string, args ...interface{}) {
l.log(INFO, format, args...)
}
func (l *Logger) Infoj(j JSON) {
l.log(INFO, "json", j)
}
func (l *Logger) Warn(i ...interface{}) {
l.log(WARN, "", i...)
}
func (l *Logger) Warnf(format string, args ...interface{}) {
l.log(WARN, format, args...)
}
func (l *Logger) Warnj(j JSON) {
l.log(WARN, "json", j)
}
func (l *Logger) Error(i ...interface{}) {
l.log(ERROR, "", i...)
}
func (l *Logger) Errorf(format string, args ...interface{}) {
l.log(ERROR, format, args...)
}
func (l *Logger) Errorj(j JSON) {
l.log(ERROR, "json", j)
}
func (l *Logger) Fatal(i ...interface{}) {
l.log(fatalLevel, "", i...)
os.Exit(1)
}
func (l *Logger) Fatalf(format string, args ...interface{}) {
l.log(fatalLevel, format, args...)
os.Exit(1)
}
func (l *Logger) Fatalj(j JSON) {
l.log(fatalLevel, "json", j)
os.Exit(1)
}
func (l *Logger) Panic(i ...interface{}) {
l.log(panicLevel, "", i...)
panic(fmt.Sprint(i...))
}
func (l *Logger) Panicf(format string, args ...interface{}) {
l.log(panicLevel, format, args...)
panic(fmt.Sprintf(format, args...))
}
func (l *Logger) Panicj(j JSON) {
l.log(panicLevel, "json", j)
panic(j)
}
func DisableColor() {
global.DisableColor()
}
func EnableColor() {
global.EnableColor()
}
func Prefix() string {
return global.Prefix()
}
func SetPrefix(p string) {
global.SetPrefix(p)
}
func Level() Lvl {
return global.Level()
}
func SetLevel(level Lvl) {
global.SetLevel(level)
}
func Output() io.Writer {
return global.Output()
}
func SetOutput(w io.Writer) {
global.SetOutput(w)
}
func SetHeader(h string) {
global.SetHeader(h)
}
func Print(i ...interface{}) {
global.Print(i...)
}
func Printf(format string, args ...interface{}) {
global.Printf(format, args...)
}
func Printj(j JSON) {
global.Printj(j)
}
func Debug(i ...interface{}) {
global.Debug(i...)
}
func Debugf(format string, args ...interface{}) {
global.Debugf(format, args...)
}
func Debugj(j JSON) {
global.Debugj(j)
}
func Info(i ...interface{}) {
global.Info(i...)
}
func Infof(format string, args ...interface{}) {
global.Infof(format, args...)
}
func Infoj(j JSON) {
global.Infoj(j)
}
func Warn(i ...interface{}) {
global.Warn(i...)
}
func Warnf(format string, args ...interface{}) {
global.Warnf(format, args...)
}
func Warnj(j JSON) {
global.Warnj(j)
}
func Error(i ...interface{}) {
global.Error(i...)
}
func Errorf(format string, args ...interface{}) {
global.Errorf(format, args...)
}
func Errorj(j JSON) {
global.Errorj(j)
}
func Fatal(i ...interface{}) {
global.Fatal(i...)
}
func Fatalf(format string, args ...interface{}) {
global.Fatalf(format, args...)
}
func Fatalj(j JSON) {
global.Fatalj(j)
}
func Panic(i ...interface{}) {
global.Panic(i...)
}
func Panicf(format string, args ...interface{}) {
global.Panicf(format, args...)
}
func Panicj(j JSON) {
global.Panicj(j)
}
func (l *Logger) log(level Lvl, format string, args ...interface{}) {
if level >= l.Level() || level == 0 {
buf := l.bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer l.bufferPool.Put(buf)
_, file, line, _ := runtime.Caller(l.skip)
message := ""
if format == "" {
message = fmt.Sprint(args...)
} else if format == "json" {
b, err := json.Marshal(args[0])
if err != nil {
panic(err)
}
message = string(b)
} else {
message = fmt.Sprintf(format, args...)
}
_, err := l.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
switch tag {
case "time_rfc3339":
return w.Write([]byte(time.Now().Format(time.RFC3339)))
case "time_rfc3339_nano":
return w.Write([]byte(time.Now().Format(time.RFC3339Nano)))
case "level":
return w.Write([]byte(l.levels[level]))
case "prefix":
return w.Write([]byte(l.prefix))
case "long_file":
return w.Write([]byte(file))
case "short_file":
return w.Write([]byte(path.Base(file)))
case "line":
return w.Write([]byte(strconv.Itoa(line)))
}
return 0, nil
})
if err == nil {
s := buf.String()
i := buf.Len() - 1
if s[i] == '}' {
// JSON header
buf.Truncate(i)
buf.WriteByte(',')
if format == "json" {
buf.WriteString(message[1:])
} else {
buf.WriteString(`"message":`)
buf.WriteString(strconv.Quote(message))
buf.WriteString(`}`)
}
} else {
// Text header
buf.WriteByte(' ')
buf.WriteString(message)
}
buf.WriteByte('\n')
l.mutex.Lock()
defer l.mutex.Unlock()
l.output.Write(buf.Bytes())
}
}
}

12
vendor/github.com/labstack/gommon/log/white.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build appengine
package log
import (
"io"
"os"
)
func output() io.Writer {
return os.Stdout
}

48
vendor/github.com/labstack/gommon/random/random.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package random
import (
"math/rand"
"strings"
"time"
)
type (
Random struct {
}
)
// Charsets
const (
Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Lowercase = "abcdefghijklmnopqrstuvwxyz"
Alphabetic = Uppercase + Lowercase
Numeric = "0123456789"
Alphanumeric = Alphabetic + Numeric
Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?`
Hex = Numeric + "abcdef"
)
var (
global = New()
)
func New() *Random {
rand.Seed(time.Now().UnixNano())
return new(Random)
}
func (r *Random) String(length uint8, charsets ...string) string {
charset := strings.Join(charsets, "")
if charset == "" {
charset = Alphanumeric
}
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Int63()%int64(len(charset))]
}
return string(b)
}
func String(length uint8, charsets ...string) string {
return global.String(length, charsets...)
}