mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-07-04 08:57:44 +00:00
Update vendor (#1414)
This commit is contained in:
1
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
1
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
@ -5,3 +5,4 @@ vendor
|
||||
.idea
|
||||
*.iml
|
||||
*.out
|
||||
.vscode
|
||||
|
4
vendor/github.com/labstack/echo/v4/.travis.yml
generated
vendored
4
vendor/github.com/labstack/echo/v4/.travis.yml
generated
vendored
@ -1,3 +1,7 @@
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.14.x
|
||||
|
95
vendor/github.com/labstack/echo/v4/CHANGELOG.md
generated
vendored
Normal file
95
vendor/github.com/labstack/echo/v4/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
# Changelog
|
||||
|
||||
## v4.2.1 - 2020-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 - 2020-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
|
31
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
31
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
@ -1,3 +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"
|
||||
|
13
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
13
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
@ -1,12 +1,12 @@
|
||||
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
|
||||
|
||||
[](https://sourcegraph.com/github.com/labstack/echo?badge)
|
||||
[](http://godoc.org/github.com/labstack/echo)
|
||||
[](https://pkg.go.dev/github.com/labstack/echo/v4)
|
||||
[](https://goreportcard.com/report/github.com/labstack/echo)
|
||||
[](https://travis-ci.org/labstack/echo)
|
||||
[](https://codecov.io/gh/labstack/echo)
|
||||
[](https://gitter.im/labstack/echo)
|
||||
[](https://forum.labstack.com)
|
||||
[](https://github.com/labstack/echo/discussions)
|
||||
[](https://twitter.com/labstack)
|
||||
[](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
|
||||
|
||||
@ -42,11 +42,14 @@ For older versions, please use the latest v3 tag.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Date: 2018/03/15<br>
|
||||
Date: 2020/11/11<br>
|
||||
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||
Lower is better!
|
||||
|
||||
<img src="https://i.imgur.com/I32VdMJ.png">
|
||||
<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)
|
||||
|
||||
@ -91,7 +94,7 @@ func hello(c echo.Context) error {
|
||||
|
||||
## Help
|
||||
|
||||
- [Forum](https://forum.labstack.com)
|
||||
- [Forum](https://github.com/labstack/echo/discussions)
|
||||
- [Chat](https://gitter.im/labstack/echo)
|
||||
|
||||
## Contribute
|
||||
|
61
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
61
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
@ -30,10 +30,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// Bind implements the `Binder#Bind` function.
|
||||
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
req := c.Request()
|
||||
|
||||
// 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{}
|
||||
@ -43,12 +41,28 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
if err := b.bindData(i, params, "param"); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
|
||||
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):
|
||||
@ -80,15 +94,35 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
default:
|
||||
return ErrUnsupportedMediaType
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
|
||||
if ptr == nil || len(data) == 0 {
|
||||
// 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
|
||||
}
|
||||
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
|
||||
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
|
||||
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
|
||||
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
|
||||
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
|
||||
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(ptr).Elem()
|
||||
val := reflect.ValueOf(ptr).Elem()
|
||||
typ := reflect.TypeOf(destination).Elem()
|
||||
val := reflect.ValueOf(destination).Elem()
|
||||
|
||||
// Map
|
||||
if typ.Kind() == reflect.Map {
|
||||
@ -113,14 +147,15 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
|
||||
inputFieldName := typeField.Tag.Get(tag)
|
||||
|
||||
if inputFieldName == "" {
|
||||
inputFieldName = typeField.Name
|
||||
// If tag is nil, we inspect if the field is a struct.
|
||||
// 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
|
||||
}
|
||||
continue
|
||||
}
|
||||
// does not have explicit tag and is not an ordinary struct - so move to next field
|
||||
continue
|
||||
}
|
||||
|
||||
inputValue, exists := data[inputFieldName]
|
||||
|
1230
vendor/github.com/labstack/echo/v4/binder.go
generated
vendored
Normal file
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
11
vendor/github.com/labstack/echo/v4/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
patch:
|
||||
default:
|
||||
threshold: 1%
|
||||
|
||||
comment:
|
||||
require_changes: true
|
34
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
34
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
@ -246,7 +246,7 @@ func (c *context) IsTLS() bool {
|
||||
|
||||
func (c *context) IsWebSocket() bool {
|
||||
upgrade := c.request.Header.Get(HeaderUpgrade)
|
||||
return strings.ToLower(upgrade) == "websocket"
|
||||
return strings.EqualFold(upgrade, "websocket")
|
||||
}
|
||||
|
||||
func (c *context) Scheme() string {
|
||||
@ -276,7 +276,11 @@ func (c *context) RealIP() string {
|
||||
}
|
||||
// Fall back to legacy behavior
|
||||
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||
return strings.Split(ip, ", ")[0]
|
||||
i := strings.IndexAny(ip, ", ")
|
||||
if i > 0 {
|
||||
return ip[:i]
|
||||
}
|
||||
return ip
|
||||
}
|
||||
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||
return ip
|
||||
@ -310,7 +314,19 @@ func (c *context) ParamNames() []string {
|
||||
|
||||
func (c *context) SetParamNames(names ...string) {
|
||||
c.pnames = names
|
||||
*c.echo.maxParam = len(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 {
|
||||
@ -318,7 +334,15 @@ func (c *context) ParamValues() []string {
|
||||
}
|
||||
|
||||
func (c *context) SetParamValues(values ...string) {
|
||||
c.pvalues = values
|
||||
// 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 {
|
||||
@ -361,7 +385,7 @@ func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
f.Close()
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
|
135
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
135
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
@ -49,7 +49,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@ -68,6 +67,9 @@ type (
|
||||
// Echo is the top-level framework instance.
|
||||
Echo struct {
|
||||
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
|
||||
@ -92,6 +94,7 @@ type (
|
||||
Renderer Renderer
|
||||
Logger Logger
|
||||
IPExtractor IPExtractor
|
||||
ListenerNetwork string
|
||||
}
|
||||
|
||||
// Route contains a handler and information for matching against requests.
|
||||
@ -231,7 +234,7 @@ const (
|
||||
|
||||
const (
|
||||
// Version of Echo
|
||||
Version = "4.1.17"
|
||||
Version = "4.2.1"
|
||||
website = "https://echo.labstack.com"
|
||||
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
||||
banner = `
|
||||
@ -281,6 +284,7 @@ var (
|
||||
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
|
||||
@ -302,9 +306,10 @@ func New() (e *Echo) {
|
||||
AutoTLSManager: autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
},
|
||||
Logger: log.New("echo"),
|
||||
colorer: color.New(),
|
||||
maxParam: new(int),
|
||||
Logger: log.New("echo"),
|
||||
colorer: color.New(),
|
||||
maxParam: new(int),
|
||||
ListenerNetwork: "tcp",
|
||||
}
|
||||
e.Server.Handler = e
|
||||
e.TLSServer.Handler = e
|
||||
@ -362,10 +367,12 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
// Issue #1426
|
||||
code := he.Code
|
||||
message := he.Message
|
||||
if e.Debug {
|
||||
message = err.Error()
|
||||
} else if m, ok := message.(string); ok {
|
||||
message = Map{"message": m}
|
||||
if m, ok := he.Message.(string); ok {
|
||||
if e.Debug {
|
||||
message = Map{"message": m, "error": err.Error()}
|
||||
} else {
|
||||
message = Map{"message": m}
|
||||
}
|
||||
}
|
||||
|
||||
// Send response
|
||||
@ -481,7 +488,7 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
|
||||
return err
|
||||
}
|
||||
|
||||
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
|
||||
name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
// The access path does not exist
|
||||
@ -496,8 +503,15 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
|
||||
}
|
||||
return c.File(name)
|
||||
}
|
||||
if prefix == "/" {
|
||||
return get(prefix+"*", h)
|
||||
// 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)
|
||||
}
|
||||
@ -570,7 +584,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string {
|
||||
for _, r := range e.router.routes {
|
||||
if r.Name == name {
|
||||
for i, l := 0, len(r.Path); i < l; i++ {
|
||||
if r.Path[i] == ':' && n < ln {
|
||||
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
|
||||
for ; i < l && r.Path[i] != '/'; i++ {
|
||||
}
|
||||
uri.WriteString(fmt.Sprintf("%v", params[n]))
|
||||
@ -612,7 +626,6 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Acquire context
|
||||
c := e.pool.Get().(*context)
|
||||
c.Reset(r, w)
|
||||
|
||||
h := NotFoundHandler
|
||||
|
||||
if e.premiddleware == nil {
|
||||
@ -640,21 +653,30 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Start starts an HTTP server.
|
||||
func (e *Echo) Start(address string) error {
|
||||
e.startupMutex.Lock()
|
||||
e.Server.Addr = address
|
||||
return e.StartServer(e.Server)
|
||||
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
|
||||
}
|
||||
|
||||
@ -662,10 +684,17 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro
|
||||
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
|
||||
}
|
||||
|
||||
return e.startTLS(address)
|
||||
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) {
|
||||
@ -681,24 +710,45 @@ func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
|
||||
|
||||
// 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)
|
||||
return e.startTLS(address)
|
||||
|
||||
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) startTLS(address string) error {
|
||||
func (e *Echo) configureTLS(address string) {
|
||||
s := e.TLSServer
|
||||
s.Addr = address
|
||||
if !e.DisableHTTP2 {
|
||||
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
|
||||
}
|
||||
return e.StartServer(e.TLSServer)
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -713,7 +763,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||
|
||||
if s.TLSConfig == nil {
|
||||
if e.Listener == nil {
|
||||
e.Listener, err = newListener(s.Addr)
|
||||
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -721,10 +771,10 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||
if !e.HidePort {
|
||||
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
|
||||
}
|
||||
return s.Serve(e.Listener)
|
||||
return nil
|
||||
}
|
||||
if e.TLSListener == nil {
|
||||
l, err := newListener(s.Addr)
|
||||
l, err := newListener(s.Addr, e.ListenerNetwork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -733,11 +783,32 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||
if !e.HidePort {
|
||||
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
|
||||
}
|
||||
return s.Serve(e.TLSListener)
|
||||
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
|
||||
@ -753,20 +824,24 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
|
||||
}
|
||||
|
||||
if e.Listener == nil {
|
||||
e.Listener, err = newListener(s.Addr)
|
||||
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
|
||||
}
|
||||
@ -776,6 +851,8 @@ func (e *Echo) Close() error {
|
||||
// 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
|
||||
}
|
||||
@ -833,6 +910,9 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
@ -883,8 +963,11 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func newListener(address string) (*tcpKeepAliveListener, error) {
|
||||
l, err := net.Listen("tcp", address)
|
||||
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
|
||||
}
|
||||
|
1
vendor/github.com/labstack/echo/v4/go.mod
generated
vendored
1
vendor/github.com/labstack/echo/v4/go.mod
generated
vendored
@ -12,4 +12,5 @@ require (
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
||||
)
|
||||
|
2
vendor/github.com/labstack/echo/v4/go.sum
generated
vendored
2
vendor/github.com/labstack/echo/v4/go.sum
generated
vendored
@ -46,6 +46,8 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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=
|
||||
|
2
vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
generated
vendored
2
vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
generated
vendored
@ -73,7 +73,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
auth := c.Request().Header.Get(echo.HeaderAuthorization)
|
||||
l := len(basic)
|
||||
|
||||
if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
|
||||
if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) {
|
||||
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
|
26
vendor/github.com/labstack/echo/v4/middleware/compress.go
generated
vendored
26
vendor/github.com/labstack/echo/v4/middleware/compress.go
generated
vendored
@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@ -58,6 +59,8 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
config.Level = DefaultGzipConfig.Level
|
||||
}
|
||||
|
||||
pool := gzipCompressPool(config)
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
@ -68,11 +71,13 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
|
||||
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
|
||||
res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
rw := res.Writer
|
||||
w, err := gzip.NewWriterLevel(rw, config.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
i := pool.Get()
|
||||
w, ok := i.(*gzip.Writer)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
|
||||
}
|
||||
rw := res.Writer
|
||||
w.Reset(rw)
|
||||
defer func() {
|
||||
if res.Size == 0 {
|
||||
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
|
||||
@ -85,6 +90,7 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
w.Reset(ioutil.Discard)
|
||||
}
|
||||
w.Close()
|
||||
pool.Put(w)
|
||||
}()
|
||||
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
|
||||
res.Writer = grw
|
||||
@ -126,3 +132,15 @@ func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
}
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
90
vendor/github.com/labstack/echo/v4/middleware/cors.go
generated
vendored
90
vendor/github.com/labstack/echo/v4/middleware/cors.go
generated
vendored
@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -18,6 +19,13 @@ type (
|
||||
// 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.
|
||||
@ -76,6 +84,15 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
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, ",")
|
||||
@ -92,25 +109,73 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
origin := req.Header.Get(echo.HeaderOrigin)
|
||||
allowOrigin := ""
|
||||
|
||||
// Check allowed origins
|
||||
for _, o := range config.AllowOrigins {
|
||||
if o == "*" && config.AllowCredentials {
|
||||
allowOrigin = origin
|
||||
break
|
||||
preflight := req.Method == http.MethodOptions
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
|
||||
// No Origin provided
|
||||
if origin == "" {
|
||||
if !preflight {
|
||||
return next(c)
|
||||
}
|
||||
if o == "*" || o == origin {
|
||||
allowOrigin = o
|
||||
break
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
if config.AllowOriginFunc != nil {
|
||||
allowed, err := config.AllowOriginFunc(origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matchSubdomain(origin, o) {
|
||||
if allowed {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowed origin patterns
|
||||
for _, re := range allowOriginPatterns {
|
||||
if allowOrigin == "" {
|
||||
didx := strings.Index(origin, "://")
|
||||
if didx == -1 {
|
||||
continue
|
||||
}
|
||||
domAuth := origin[didx+3:]
|
||||
// to avoid regex cost by invalid long domain
|
||||
if len(domAuth) > 253 {
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Simple request
|
||||
if req.Method != http.MethodOptions {
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
if !preflight {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
|
||||
if config.AllowCredentials {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
|
||||
@ -122,7 +187,6 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// Preflight request
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
|
||||
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
|
||||
|
23
vendor/github.com/labstack/echo/v4/middleware/csrf.go
generated
vendored
23
vendor/github.com/labstack/echo/v4/middleware/csrf.go
generated
vendored
@ -57,6 +57,10 @@ type (
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
||||
@ -67,12 +71,13 @@ type (
|
||||
var (
|
||||
// DefaultCSRFConfig is the default CSRF middleware config.
|
||||
DefaultCSRFConfig = CSRFConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
TokenLength: 32,
|
||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||
ContextKey: "csrf",
|
||||
CookieName: "_csrf",
|
||||
CookieMaxAge: 86400,
|
||||
Skipper: DefaultSkipper,
|
||||
TokenLength: 32,
|
||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||
ContextKey: "csrf",
|
||||
CookieName: "_csrf",
|
||||
CookieMaxAge: 86400,
|
||||
CookieSameSite: http.SameSiteDefaultMode,
|
||||
}
|
||||
)
|
||||
|
||||
@ -105,6 +110,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
if config.CookieMaxAge == 0 {
|
||||
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
|
||||
}
|
||||
if config.CookieSameSite == SameSiteNoneMode {
|
||||
config.CookieSecure = true
|
||||
}
|
||||
|
||||
// Initialize
|
||||
parts := strings.Split(config.TokenLookup, ":")
|
||||
@ -157,6 +165,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
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
|
||||
|
12
vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go
generated
vendored
Normal file
12
vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build go1.13
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
|
||||
SameSiteNoneMode http.SameSite = http.SameSiteNoneMode
|
||||
)
|
12
vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go
generated
vendored
Normal file
12
vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !go1.13
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
|
||||
SameSiteNoneMode http.SameSite = 4
|
||||
)
|
120
vendor/github.com/labstack/echo/v4/middleware/decompress.go
generated
vendored
Normal file
120
vendor/github.com/labstack/echo/v4/middleware/decompress.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"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{} {
|
||||
// create with an empty reader (but with GZIP header)
|
||||
w, err := gzip.NewWriterLevel(ioutil.Discard, gzip.BestSpeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
w.Reset(b)
|
||||
w.Flush()
|
||||
w.Close()
|
||||
|
||||
r, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
switch c.Request().Header.Get(echo.HeaderContentEncoding) {
|
||||
case GZIPEncoding:
|
||||
b := c.Request().Body
|
||||
|
||||
i := pool.Get()
|
||||
gr, ok := i.(*gzip.Reader)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
|
||||
}
|
||||
|
||||
if err := gr.Reset(b); err != nil {
|
||||
pool.Put(gr)
|
||||
if err == io.EOF { //ignore if body is empty
|
||||
return next(c)
|
||||
}
|
||||
return err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, gr)
|
||||
|
||||
gr.Close()
|
||||
pool.Put(gr)
|
||||
|
||||
b.Close() // http.Request.Body is closed by the Server, but because we are replacing it, it must be closed here
|
||||
|
||||
r := ioutil.NopCloser(&buf)
|
||||
c.Request().Body = r
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
19
vendor/github.com/labstack/echo/v4/middleware/jwt.go
generated
vendored
19
vendor/github.com/labstack/echo/v4/middleware/jwt.go
generated
vendored
@ -57,6 +57,7 @@ type (
|
||||
// - "query:<name>"
|
||||
// - "param:<name>"
|
||||
// - "cookie:<name>"
|
||||
// - "form:<name>"
|
||||
TokenLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
@ -86,6 +87,7 @@ const (
|
||||
// Errors
|
||||
var (
|
||||
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
|
||||
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
|
||||
)
|
||||
|
||||
var (
|
||||
@ -166,6 +168,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
extractor = jwtFromParam(parts[1])
|
||||
case "cookie":
|
||||
extractor = jwtFromCookie(parts[1])
|
||||
case "form":
|
||||
extractor = jwtFromForm(parts[1])
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
@ -213,8 +217,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
return config.ErrorHandlerWithContext(err, c)
|
||||
}
|
||||
return &echo.HTTPError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid or expired jwt",
|
||||
Code: ErrJWTInvalid.Code,
|
||||
Message: ErrJWTInvalid.Message,
|
||||
Internal: err,
|
||||
}
|
||||
}
|
||||
@ -265,3 +269,14 @@ func jwtFromCookie(name string) jwtExtractor {
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// jwtFromForm returns a `jwtExtractor` that extracts token from the form field.
|
||||
func jwtFromForm(name string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
field := c.FormValue(name)
|
||||
if field == "" {
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
return field, nil
|
||||
}
|
||||
}
|
||||
|
43
vendor/github.com/labstack/echo/v4/middleware/middleware.go
generated
vendored
43
vendor/github.com/labstack/echo/v4/middleware/middleware.go
generated
vendored
@ -1,6 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -32,6 +34,47 @@ func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
|
||||
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 rewritePath(rewriteRegex map[*regexp.Regexp]string, req *http.Request) {
|
||||
for k, v := range rewriteRegex {
|
||||
rawPath := req.URL.RawPath
|
||||
if rawPath != "" {
|
||||
// RawPath is only set when there has been escaping done. In that case Path must be deduced from rewritten RawPath
|
||||
// because encoded Path could match rules that RawPath did not
|
||||
if replacer := captureTokens(k, rawPath); replacer != nil {
|
||||
rawPath = replacer.Replace(v)
|
||||
|
||||
req.URL.RawPath = rawPath
|
||||
req.URL.Path, _ = url.PathUnescape(rawPath)
|
||||
|
||||
return // rewrite only once
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if replacer := captureTokens(k, req.URL.Path); replacer != nil {
|
||||
req.URL.Path = replacer.Replace(v)
|
||||
|
||||
return // rewrite only once
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSkipper returns false which processes the middleware.
|
||||
func DefaultSkipper(echo.Context) bool {
|
||||
return false
|
||||
|
31
vendor/github.com/labstack/echo/v4/middleware/proxy.go
generated
vendored
31
vendor/github.com/labstack/echo/v4/middleware/proxy.go
generated
vendored
@ -8,7 +8,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -37,6 +36,13 @@ type (
|
||||
// "/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
|
||||
@ -47,8 +53,6 @@ type (
|
||||
|
||||
// ModifyResponse defines function to modify response from ProxyTarget.
|
||||
ModifyResponse func(*http.Response) error
|
||||
|
||||
rewriteRegex map[*regexp.Regexp]string
|
||||
}
|
||||
|
||||
// ProxyTarget defines the upstream target.
|
||||
@ -206,12 +210,14 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
|
||||
if config.Balancer == nil {
|
||||
panic("echo: proxy middleware requires balancer")
|
||||
}
|
||||
config.rewriteRegex = map[*regexp.Regexp]string{}
|
||||
|
||||
// Initialize
|
||||
for k, v := range config.Rewrite {
|
||||
k = strings.Replace(k, "*", "(\\S*)", -1)
|
||||
config.rewriteRegex[regexp.MustCompile(k)] = v
|
||||
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 {
|
||||
@ -225,13 +231,8 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
|
||||
tgt := config.Balancer.Next(c)
|
||||
c.Set(config.ContextKey, tgt)
|
||||
|
||||
// Rewrite
|
||||
for k, v := range config.rewriteRegex {
|
||||
replacer := captureTokens(k, echo.GetPath(req))
|
||||
if replacer != nil {
|
||||
req.URL.Path = replacer.Replace(v)
|
||||
}
|
||||
}
|
||||
// Set rewrite path and raw path
|
||||
rewritePath(config.RegexRewrite, req)
|
||||
|
||||
// Fix header
|
||||
// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.
|
||||
|
24
vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
generated
vendored
24
vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
generated
vendored
@ -3,13 +3,22 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
@ -17,7 +26,20 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle
|
||||
if tgt.Name != "" {
|
||||
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
|
||||
}
|
||||
c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)))
|
||||
// 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
|
||||
|
266
vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
generated
vendored
Normal file
266
vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
generated
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
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
|
||||
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). 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.Minutes},
|
||||
)
|
||||
*/
|
||||
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
|
||||
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
|
36
vendor/github.com/labstack/echo/v4/middleware/rewrite.go
generated
vendored
36
vendor/github.com/labstack/echo/v4/middleware/rewrite.go
generated
vendored
@ -2,7 +2,6 @@ package middleware
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@ -23,7 +22,12 @@ type (
|
||||
// Required.
|
||||
Rules map[string]string `yaml:"rules"`
|
||||
|
||||
rulesRegex map[*regexp.Regexp]string
|
||||
// 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"`
|
||||
}
|
||||
)
|
||||
|
||||
@ -47,20 +51,19 @@ func Rewrite(rules map[string]string) echo.MiddlewareFunc {
|
||||
// See: `Rewrite()`.
|
||||
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Rules == nil {
|
||||
panic("echo: rewrite middleware requires url path rewrite rules")
|
||||
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
|
||||
}
|
||||
config.rulesRegex = map[*regexp.Regexp]string{}
|
||||
|
||||
// Initialize
|
||||
for k, v := range config.Rules {
|
||||
k = regexp.QuoteMeta(k)
|
||||
k = strings.Replace(k, `\*`, "(.*)", -1)
|
||||
k = k + "$"
|
||||
config.rulesRegex[regexp.MustCompile(k)] = v
|
||||
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 {
|
||||
@ -70,15 +73,8 @@ func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
|
||||
// Rewrite
|
||||
for k, v := range config.rulesRegex {
|
||||
replacer := captureTokens(k, req.URL.Path)
|
||||
if replacer != nil {
|
||||
req.URL.Path = replacer.Replace(v)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Set rewrite path and raw path
|
||||
rewritePath(config.RegexRules, req)
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
13
vendor/github.com/labstack/echo/v4/middleware/slash.go
generated
vendored
13
vendor/github.com/labstack/echo/v4/middleware/slash.go
generated
vendored
@ -60,7 +60,7 @@ func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, uri)
|
||||
return c.Redirect(config.RedirectCode, sanitizeURI(uri))
|
||||
}
|
||||
|
||||
// Forward
|
||||
@ -108,7 +108,7 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, uri)
|
||||
return c.Redirect(config.RedirectCode, sanitizeURI(uri))
|
||||
}
|
||||
|
||||
// Forward
|
||||
@ -119,3 +119,12 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
17
vendor/github.com/labstack/echo/v4/middleware/static.go
generated
vendored
17
vendor/github.com/labstack/echo/v4/middleware/static.go
generated
vendored
@ -36,6 +36,12 @@ type (
|
||||
// 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"`
|
||||
}
|
||||
)
|
||||
|
||||
@ -161,7 +167,16 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
|
111
vendor/github.com/labstack/echo/v4/middleware/timeout.go
generated
vendored
Normal file
111
vendor/github.com/labstack/echo/v4/middleware/timeout.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
// +build go1.13
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 recovers from panics anywhere in the chain
|
||||
// and handles the control to the centralized HTTPErrorHandler.
|
||||
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 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
|
||||
|
||||
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
|
||||
t.ctx.Response().Writer = originalWriter
|
||||
if err != nil {
|
||||
t.errChan <- err
|
||||
}
|
||||
}
|
4
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
4
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
@ -56,11 +56,11 @@ func (r *Response) WriteHeader(code int) {
|
||||
r.echo.Logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.Status = code
|
||||
for _, fn := range r.beforeFuncs {
|
||||
fn()
|
||||
}
|
||||
r.Status = code
|
||||
r.Writer.WriteHeader(code)
|
||||
r.Writer.WriteHeader(r.Status)
|
||||
r.Committed = true
|
||||
}
|
||||
|
||||
|
482
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
482
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
@ -2,7 +2,6 @@ package echo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -14,14 +13,16 @@ type (
|
||||
echo *Echo
|
||||
}
|
||||
node struct {
|
||||
kind kind
|
||||
label byte
|
||||
prefix string
|
||||
parent *node
|
||||
children children
|
||||
ppath string
|
||||
pnames []string
|
||||
methodHandler *methodHandler
|
||||
kind kind
|
||||
label byte
|
||||
prefix string
|
||||
parent *node
|
||||
staticChildren children
|
||||
ppath string
|
||||
pnames []string
|
||||
methodHandler *methodHandler
|
||||
paramChild *node
|
||||
anyChild *node
|
||||
}
|
||||
kind uint8
|
||||
children []*node
|
||||
@ -41,9 +42,12 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
skind kind = iota
|
||||
pkind
|
||||
akind
|
||||
staticKind kind = iota
|
||||
paramKind
|
||||
anyKind
|
||||
|
||||
paramLabel = byte(':')
|
||||
anyLabel = byte('*')
|
||||
)
|
||||
|
||||
// NewRouter returns a new Router instance.
|
||||
@ -69,120 +73,147 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
|
||||
pnames := []string{} // Param names
|
||||
ppath := path // Pristine path
|
||||
|
||||
for i, l := 0, len(path); i < l; i++ {
|
||||
for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
|
||||
if path[i] == ':' {
|
||||
j := i + 1
|
||||
|
||||
r.insert(method, path[:i], nil, skind, "", nil)
|
||||
for ; i < l && path[i] != '/'; i++ {
|
||||
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, l = j, len(path)
|
||||
i, lcpIndex = j, len(path)
|
||||
|
||||
if i == l {
|
||||
r.insert(method, path[:i], h, pkind, ppath, pnames)
|
||||
if i == lcpIndex {
|
||||
r.insert(method, path[:i], h, paramKind, ppath, pnames)
|
||||
} else {
|
||||
r.insert(method, path[:i], nil, pkind, "", nil)
|
||||
r.insert(method, path[:i], nil, paramKind, "", nil)
|
||||
}
|
||||
} else if path[i] == '*' {
|
||||
r.insert(method, path[:i], nil, skind, "", nil)
|
||||
r.insert(method, path[:i], nil, staticKind, "", nil)
|
||||
pnames = append(pnames, "*")
|
||||
r.insert(method, path[:i+1], h, akind, ppath, pnames)
|
||||
r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
|
||||
}
|
||||
}
|
||||
|
||||
r.insert(method, path, h, skind, 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
|
||||
l := len(pnames)
|
||||
if *r.echo.maxParam < l {
|
||||
*r.echo.maxParam = l
|
||||
paramLen := len(pnames)
|
||||
if *r.echo.maxParam < paramLen {
|
||||
*r.echo.maxParam = paramLen
|
||||
}
|
||||
|
||||
cn := r.tree // Current node as root
|
||||
if cn == nil {
|
||||
currentNode := r.tree // Current node as root
|
||||
if currentNode == nil {
|
||||
panic("echo: invalid method")
|
||||
}
|
||||
search := path
|
||||
|
||||
for {
|
||||
sl := len(search)
|
||||
pl := len(cn.prefix)
|
||||
l := 0
|
||||
searchLen := len(search)
|
||||
prefixLen := len(currentNode.prefix)
|
||||
lcpLen := 0
|
||||
|
||||
// LCP
|
||||
max := pl
|
||||
if sl < max {
|
||||
max = sl
|
||||
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
|
||||
max := prefixLen
|
||||
if searchLen < max {
|
||||
max = searchLen
|
||||
}
|
||||
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
|
||||
}
|
||||
|
||||
if l == 0 {
|
||||
if lcpLen == 0 {
|
||||
// At root node
|
||||
cn.label = search[0]
|
||||
cn.prefix = search
|
||||
currentNode.label = search[0]
|
||||
currentNode.prefix = search
|
||||
if h != nil {
|
||||
cn.kind = t
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
cn.pnames = pnames
|
||||
currentNode.kind = t
|
||||
currentNode.addHandler(method, h)
|
||||
currentNode.ppath = ppath
|
||||
currentNode.pnames = pnames
|
||||
}
|
||||
} else if l < pl {
|
||||
} else if lcpLen < prefixLen {
|
||||
// Split node
|
||||
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
|
||||
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 cn.children {
|
||||
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
|
||||
cn.kind = skind
|
||||
cn.label = cn.prefix[0]
|
||||
cn.prefix = cn.prefix[:l]
|
||||
cn.children = nil
|
||||
cn.methodHandler = new(methodHandler)
|
||||
cn.ppath = ""
|
||||
cn.pnames = nil
|
||||
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
|
||||
|
||||
cn.addChild(n)
|
||||
// Only Static children could reach here
|
||||
currentNode.addStaticChild(n)
|
||||
|
||||
if l == sl {
|
||||
if lcpLen == searchLen {
|
||||
// At parent node
|
||||
cn.kind = t
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
cn.pnames = pnames
|
||||
currentNode.kind = t
|
||||
currentNode.addHandler(method, h)
|
||||
currentNode.ppath = ppath
|
||||
currentNode.pnames = pnames
|
||||
} else {
|
||||
// Create child node
|
||||
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
|
||||
n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
|
||||
n.addHandler(method, h)
|
||||
cn.addChild(n)
|
||||
// Only Static children could reach here
|
||||
currentNode.addStaticChild(n)
|
||||
}
|
||||
} else if l < sl {
|
||||
search = search[l:]
|
||||
c := cn.findChildWithLabel(search[0])
|
||||
} else if lcpLen < searchLen {
|
||||
search = search[lcpLen:]
|
||||
c := currentNode.findChildWithLabel(search[0])
|
||||
if c != nil {
|
||||
// Go deeper
|
||||
cn = c
|
||||
currentNode = c
|
||||
continue
|
||||
}
|
||||
// Create child node
|
||||
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
|
||||
n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
|
||||
n.addHandler(method, h)
|
||||
cn.addChild(n)
|
||||
switch t {
|
||||
case staticKind:
|
||||
currentNode.addStaticChild(n)
|
||||
case paramKind:
|
||||
currentNode.paramChild = n
|
||||
case anyKind:
|
||||
currentNode.anyChild = n
|
||||
}
|
||||
} else {
|
||||
// Node already exists
|
||||
if h != nil {
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
if len(cn.pnames) == 0 { // Issue #729
|
||||
cn.pnames = pnames
|
||||
currentNode.addHandler(method, h)
|
||||
currentNode.ppath = ppath
|
||||
if len(currentNode.pnames) == 0 { // Issue #729
|
||||
currentNode.pnames = pnames
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,34 +221,27 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
|
||||
}
|
||||
}
|
||||
|
||||
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
|
||||
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,
|
||||
children: c,
|
||||
ppath: ppath,
|
||||
pnames: pnames,
|
||||
methodHandler: mh,
|
||||
kind: t,
|
||||
label: pre[0],
|
||||
prefix: pre,
|
||||
parent: p,
|
||||
staticChildren: sc,
|
||||
ppath: ppath,
|
||||
pnames: pnames,
|
||||
methodHandler: mh,
|
||||
paramChild: paramChildren,
|
||||
anyChild: anyChildren,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) addChild(c *node) {
|
||||
n.children = append(n.children, c)
|
||||
func (n *node) addStaticChild(c *node) {
|
||||
n.staticChildren = append(n.staticChildren, c)
|
||||
}
|
||||
|
||||
func (n *node) findChild(l byte, t kind) *node {
|
||||
for _, c := range n.children {
|
||||
if c.label == l && c.kind == t {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildWithLabel(l byte) *node {
|
||||
for _, c := range n.children {
|
||||
func (n *node) findStaticChild(l byte) *node {
|
||||
for _, c := range n.staticChildren {
|
||||
if c.label == l {
|
||||
return c
|
||||
}
|
||||
@ -225,12 +249,18 @@ func (n *node) findChildWithLabel(l byte) *node {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildByKind(t kind) *node {
|
||||
for _, c := range n.children {
|
||||
if c.kind == t {
|
||||
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
|
||||
}
|
||||
|
||||
@ -310,180 +340,152 @@ func (n *node) checkMethodNotAllowed() HandlerFunc {
|
||||
func (r *Router) Find(method, path string, c Context) {
|
||||
ctx := c.(*context)
|
||||
ctx.path = path
|
||||
cn := r.tree // Current node as root
|
||||
currentNode := r.tree // Current node as root
|
||||
|
||||
var (
|
||||
search = path
|
||||
child *node // Child node
|
||||
n int // Param counter
|
||||
nk kind // Next kind
|
||||
nn *node // Next node
|
||||
ns string // Next search
|
||||
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
|
||||
// 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
|
||||
)
|
||||
|
||||
// Search order static > param > any
|
||||
// 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
|
||||
// NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
|
||||
// always `static` or `any`
|
||||
// If this is changed then for any route next kind would be `static` and this statement should be changed
|
||||
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])
|
||||
}
|
||||
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 {
|
||||
if search == "" {
|
||||
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)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 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 an leaf node
|
||||
if search == "" && currentNode.ppath != "" {
|
||||
break
|
||||
}
|
||||
|
||||
pl := 0 // Prefix length
|
||||
l := 0 // LCP length
|
||||
|
||||
if cn.label != ':' {
|
||||
sl := len(search)
|
||||
pl = len(cn.prefix)
|
||||
|
||||
// LCP
|
||||
max := pl
|
||||
if sl < max {
|
||||
max = sl
|
||||
}
|
||||
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||
}
|
||||
}
|
||||
|
||||
if l == pl {
|
||||
// Continue search
|
||||
search = search[l:]
|
||||
// Finish routing if no remaining search and we are on an leaf node
|
||||
if search == "" && (nn == nil || cn.parent == nil || cn.ppath != "") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to go back up the tree on no matching prefix or no remaining search
|
||||
if l != pl || search == "" {
|
||||
// Handle special case of trailing slash route with existing any route (see #1526)
|
||||
if path[len(path)-1] == '/' && cn.findChildByKind(akind) != nil {
|
||||
goto Any
|
||||
}
|
||||
if nn == nil { // Issue #1348
|
||||
return // Not found
|
||||
}
|
||||
cn = nn
|
||||
search = ns
|
||||
if nk == pkind {
|
||||
goto Param
|
||||
} else if nk == akind {
|
||||
goto Any
|
||||
}
|
||||
}
|
||||
|
||||
// Static node
|
||||
if child = cn.findChild(search[0], skind); child != nil {
|
||||
// Save next
|
||||
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||
nk = pkind
|
||||
nn = cn
|
||||
ns = search
|
||||
if search != "" {
|
||||
if child := currentNode.findStaticChild(search[0]); child != nil {
|
||||
currentNode = child
|
||||
continue
|
||||
}
|
||||
cn = child
|
||||
continue
|
||||
}
|
||||
|
||||
Param:
|
||||
// Param node
|
||||
if child = cn.findChildByKind(pkind); child != nil {
|
||||
// Issue #378
|
||||
if len(pvalues) == n {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save next
|
||||
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||
nk = akind
|
||||
nn = cn
|
||||
ns = search
|
||||
}
|
||||
|
||||
cn = child
|
||||
if child := currentNode.paramChild; search != "" && child != nil {
|
||||
currentNode = child
|
||||
// FIXME: 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 := 0, len(search)
|
||||
for ; i < l && search[i] != '/'; i++ {
|
||||
}
|
||||
pvalues[n] = search[:i]
|
||||
n++
|
||||
paramValues[paramIndex] = search[:i]
|
||||
paramIndex++
|
||||
search = search[i:]
|
||||
searchIndex = searchIndex + i
|
||||
continue
|
||||
}
|
||||
|
||||
Any:
|
||||
// Any node
|
||||
if cn = cn.findChildByKind(akind); cn != nil {
|
||||
// If any node is found, use remaining path for pvalues
|
||||
pvalues[len(cn.pnames)-1] = search
|
||||
if child := currentNode.anyChild; child != nil {
|
||||
// If any node is found, use remaining path for paramValues
|
||||
currentNode = child
|
||||
paramValues[len(currentNode.pnames)-1] = search
|
||||
break
|
||||
}
|
||||
|
||||
// No node found, continue at stored next node
|
||||
// or find nearest "any" route
|
||||
if nn != nil {
|
||||
// No next node to go down in routing (issue #954)
|
||||
// Find nearest "any" route going up the routing tree
|
||||
search = ns
|
||||
np := nn.parent
|
||||
// Consider param route one level up only
|
||||
if cn = nn.findChildByKind(pkind); cn != nil {
|
||||
pos := strings.IndexByte(ns, '/')
|
||||
if pos == -1 {
|
||||
// If no slash is remaining in search string set param value
|
||||
pvalues[len(cn.pnames)-1] = search
|
||||
break
|
||||
} else if pos > 0 {
|
||||
// Otherwise continue route processing with restored next node
|
||||
cn = nn
|
||||
nn = nil
|
||||
ns = ""
|
||||
goto Param
|
||||
}
|
||||
}
|
||||
// No param route found, try to resolve nearest any route
|
||||
for {
|
||||
np = nn.parent
|
||||
if cn = nn.findChildByKind(akind); cn != nil {
|
||||
break
|
||||
}
|
||||
if np == nil {
|
||||
break // no further parent nodes in tree, abort
|
||||
}
|
||||
var str strings.Builder
|
||||
str.WriteString(nn.prefix)
|
||||
str.WriteString(search)
|
||||
search = str.String()
|
||||
nn = np
|
||||
}
|
||||
if cn != nil { // use the found "any" route and update path
|
||||
pvalues[len(cn.pnames)-1] = search
|
||||
break
|
||||
}
|
||||
}
|
||||
return // Not found
|
||||
|
||||
}
|
||||
|
||||
ctx.handler = cn.findHandler(method)
|
||||
ctx.path = cn.ppath
|
||||
ctx.pnames = cn.pnames
|
||||
|
||||
// NOTE: Slow zone...
|
||||
if ctx.handler == nil {
|
||||
ctx.handler = cn.checkMethodNotAllowed()
|
||||
|
||||
// Dig further for any, might have an empty value for *, e.g.
|
||||
// serving a directory. Issue #207.
|
||||
if cn = cn.findChildByKind(akind); cn == nil {
|
||||
// Let's backtrack to the first possible alternative node of the decision path
|
||||
nk, ok := backtrackToNextNodeKind(anyKind)
|
||||
if !ok {
|
||||
return // No other possibilities on the decision path
|
||||
} else if nk == paramKind {
|
||||
goto Param
|
||||
} else if nk == anyKind {
|
||||
goto Any
|
||||
} else {
|
||||
// Not found
|
||||
return
|
||||
}
|
||||
if h := cn.findHandler(method); h != nil {
|
||||
ctx.handler = h
|
||||
} else {
|
||||
ctx.handler = cn.checkMethodNotAllowed()
|
||||
}
|
||||
ctx.path = cn.ppath
|
||||
ctx.pnames = cn.pnames
|
||||
pvalues[len(cn.pnames)-1] = ""
|
||||
}
|
||||
|
||||
ctx.handler = currentNode.findHandler(method)
|
||||
ctx.path = currentNode.ppath
|
||||
ctx.pnames = currentNode.pnames
|
||||
|
||||
if ctx.handler == nil {
|
||||
ctx.handler = currentNode.checkMethodNotAllowed()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Reference in New Issue
Block a user