Create new type and remove un-used code
Some checks failed
Create Release & Upload Assets / Upload Assets To Gitea w/ goreleaser (push) Failing after 11s
Some checks failed
Create Release & Upload Assets / Upload Assets To Gitea w/ goreleaser (push) Failing after 11s
This commit is contained in:
25
vendor/github.com/labstack/echo/v4/.editorconfig
generated
vendored
Normal file
25
vendor/github.com/labstack/echo/v4/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# EditorConfig coding styles definitions. For more information about the
|
||||
# properties used in this file, please see the EditorConfig documentation:
|
||||
# http://editorconfig.org/
|
||||
|
||||
# indicate this is the root of the project
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
20
vendor/github.com/labstack/echo/v4/.gitattributes
generated
vendored
Normal file
20
vendor/github.com/labstack/echo/v4/.gitattributes
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Automatically normalize line endings for all text-based files
|
||||
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
|
||||
* text=auto
|
||||
|
||||
# For the following file types, normalize line endings to LF on checking and
|
||||
# prevent conversion to CRLF when they are checked out (this is required in
|
||||
# order to prevent newline related issues)
|
||||
.* text eol=lf
|
||||
*.go text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.html text eol=lf
|
||||
*.css text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
LICENSE text eol=lf
|
||||
|
||||
# Exclude `website` and `cookbook` from GitHub's language statistics
|
||||
# https://github.com/github/linguist#using-gitattributes
|
||||
cookbook/* linguist-documentation
|
||||
website/* linguist-documentation
|
||||
8
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
Normal file
8
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
coverage.txt
|
||||
_test
|
||||
vendor
|
||||
.idea
|
||||
*.iml
|
||||
*.out
|
||||
.vscode
|
||||
538
vendor/github.com/labstack/echo/v4/CHANGELOG.md
generated
vendored
Normal file
538
vendor/github.com/labstack/echo/v4/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,538 @@
|
||||
# Changelog
|
||||
|
||||
## v4.13.3 - 2024-12-19
|
||||
|
||||
**Security**
|
||||
|
||||
* Update golang.org/x/net dependency [GO-2024-3333](https://pkg.go.dev/vuln/GO-2024-3333) in https://github.com/labstack/echo/pull/2722
|
||||
|
||||
|
||||
## v4.13.2 - 2024-12-12
|
||||
|
||||
**Security**
|
||||
|
||||
* Update dependencies (dependabot reports [GO-2024-3321](https://pkg.go.dev/vuln/GO-2024-3321)) in https://github.com/labstack/echo/pull/2721
|
||||
|
||||
|
||||
## v4.13.1 - 2024-12-11
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix BindBody ignoring `Transfer-Encoding: chunked` requests by @178inaba in https://github.com/labstack/echo/pull/2717
|
||||
|
||||
|
||||
|
||||
## v4.13.0 - 2024-12-04
|
||||
|
||||
**BREAKING CHANGE** JWT Middleware Removed from Core use [labstack/echo-jwt](https://github.com/labstack/echo-jwt) instead
|
||||
|
||||
The JWT middleware has been **removed from Echo core** due to another security vulnerability, [CVE-2024-51744](https://nvd.nist.gov/vuln/detail/CVE-2024-51744). For more details, refer to issue [#2699](https://github.com/labstack/echo/issues/2699). A drop-in replacement is available in the [labstack/echo-jwt](https://github.com/labstack/echo-jwt) repository.
|
||||
|
||||
**Important**: Direct assignments like `token := c.Get("user").(*jwt.Token)` will now cause a panic due to an invalid cast. Update your code accordingly. Replace the current imports from `"github.com/golang-jwt/jwt"` in your handlers to the new middleware version using `"github.com/golang-jwt/jwt/v5"`.
|
||||
|
||||
|
||||
Background:
|
||||
|
||||
The version of `golang-jwt/jwt` (v3.2.2) previously used in Echo core has been in an unmaintained state for some time. This is not the first vulnerability affecting this library; earlier issues were addressed in [PR #1946](https://github.com/labstack/echo/pull/1946).
|
||||
JWT middleware was marked as deprecated in Echo core as of [v4.10.0](https://github.com/labstack/echo/releases/tag/v4.10.0) on 2022-12-27. If you did not notice that, consider leveraging tools like [Staticcheck](https://staticcheck.dev/) to catch such deprecations earlier in you dev/CI flow. For bonus points - check out [gosec](https://github.com/securego/gosec).
|
||||
|
||||
We sincerely apologize for any inconvenience caused by this change. While we strive to maintain backward compatibility within Echo core, recurring security issues with third-party dependencies have forced this decision.
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* remove jwt middleware by @stevenwhitehead in https://github.com/labstack/echo/pull/2701
|
||||
* optimization: struct alignment by @behnambm in https://github.com/labstack/echo/pull/2636
|
||||
* bind: Maintain backwards compatibility for map[string]interface{} binding by @thesaltree in https://github.com/labstack/echo/pull/2656
|
||||
* Add Go 1.23 to CI by @aldas in https://github.com/labstack/echo/pull/2675
|
||||
* improve `MultipartForm` test by @martinyonatann in https://github.com/labstack/echo/pull/2682
|
||||
* `bind` : add support of multipart multi files by @martinyonatann in https://github.com/labstack/echo/pull/2684
|
||||
* Add TemplateRenderer struct to ease creating renderers for `html/template` and `text/template` packages. by @aldas in https://github.com/labstack/echo/pull/2690
|
||||
* Refactor TestBasicAuth to utilize table-driven test format by @ErikOlson in https://github.com/labstack/echo/pull/2688
|
||||
* Remove broken header by @aldas in https://github.com/labstack/echo/pull/2705
|
||||
* fix(bind body): content-length can be -1 by @phamvinhdat in https://github.com/labstack/echo/pull/2710
|
||||
* CORS middleware should compile allowOrigin regexp at creation by @aldas in https://github.com/labstack/echo/pull/2709
|
||||
* Shorten Github issue template and add test example by @aldas in https://github.com/labstack/echo/pull/2711
|
||||
|
||||
|
||||
## v4.12.0 - 2024-04-15
|
||||
|
||||
**Security**
|
||||
|
||||
* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625
|
||||
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554
|
||||
* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579
|
||||
* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581
|
||||
* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584
|
||||
* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568
|
||||
* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588
|
||||
* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574
|
||||
* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461
|
||||
* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603
|
||||
* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596
|
||||
* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595
|
||||
* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604
|
||||
* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605
|
||||
* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606
|
||||
* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550
|
||||
* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607
|
||||
* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608
|
||||
* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611
|
||||
* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616
|
||||
* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624
|
||||
|
||||
|
||||
## v4.11.4 - 2023-12-20
|
||||
|
||||
**Security**
|
||||
|
||||
* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563)
|
||||
* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543)
|
||||
|
||||
|
||||
## v4.11.3 - 2023-11-07
|
||||
|
||||
**Security**
|
||||
|
||||
* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540)
|
||||
* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537)
|
||||
* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536)
|
||||
|
||||
|
||||
## v4.11.2 - 2023-10-11
|
||||
|
||||
**Security**
|
||||
|
||||
* Bump golang.org/x/net to prevent CVE-2023-39325 / CVE-2023-44487 HTTP/2 Rapid Reset Attack [#2527](https://github.com/labstack/echo/pull/2527)
|
||||
* fix(sec): randomString bias introduced by #2490 [#2492](https://github.com/labstack/echo/pull/2492)
|
||||
* CSRF/RequestID mw: switch math/random usage to crypto/random [#2490](https://github.com/labstack/echo/pull/2490)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Delete unused context in body_limit.go [#2483](https://github.com/labstack/echo/pull/2483)
|
||||
* Use Go 1.21 in CI [#2505](https://github.com/labstack/echo/pull/2505)
|
||||
* Fix some typos [#2511](https://github.com/labstack/echo/pull/2511)
|
||||
* Allow CORS middleware to send Access-Control-Max-Age: 0 [#2518](https://github.com/labstack/echo/pull/2518)
|
||||
* Bump dependancies [#2522](https://github.com/labstack/echo/pull/2522)
|
||||
|
||||
## v4.11.1 - 2023-07-16
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix `Gzip` middleware not sending response code for no content responses (404, 301/302 redirects etc) [#2481](https://github.com/labstack/echo/pull/2481)
|
||||
|
||||
|
||||
## v4.11.0 - 2023-07-14
|
||||
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fixes the proxy middleware concurrency issue of calling the Next() proxy target on Round Robin Balancer [#2409](https://github.com/labstack/echo/pull/2409)
|
||||
* Fix `group.RouteNotFound` not working when group has attached middlewares [#2411](https://github.com/labstack/echo/pull/2411)
|
||||
* Fix global error handler return error message when message is an error [#2456](https://github.com/labstack/echo/pull/2456)
|
||||
* Do not use global timeNow variables [#2477](https://github.com/labstack/echo/pull/2477)
|
||||
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added a optional config variable to disable centralized error handler in recovery middleware [#2410](https://github.com/labstack/echo/pull/2410)
|
||||
* refactor: use `strings.ReplaceAll` directly [#2424](https://github.com/labstack/echo/pull/2424)
|
||||
* Add support for Go1.20 `http.rwUnwrapper` to Response struct [#2425](https://github.com/labstack/echo/pull/2425)
|
||||
* Check whether is nil before invoking centralized error handling [#2429](https://github.com/labstack/echo/pull/2429)
|
||||
* Proper colon support in `echo.Reverse` method [#2416](https://github.com/labstack/echo/pull/2416)
|
||||
* Fix misuses of a vs an in documentation comments [#2436](https://github.com/labstack/echo/pull/2436)
|
||||
* Add link to slog.Handler library for Echo logging into README.md [#2444](https://github.com/labstack/echo/pull/2444)
|
||||
* In proxy middleware Support retries of failed proxy requests [#2414](https://github.com/labstack/echo/pull/2414)
|
||||
* gofmt fixes to comments [#2452](https://github.com/labstack/echo/pull/2452)
|
||||
* gzip response only if it exceeds a minimal length [#2267](https://github.com/labstack/echo/pull/2267)
|
||||
* Upgrade packages [#2475](https://github.com/labstack/echo/pull/2475)
|
||||
|
||||
|
||||
## v4.10.2 - 2023-02-22
|
||||
|
||||
**Security**
|
||||
|
||||
* `filepath.Clean` behaviour has changed in Go 1.20 - adapt to it [#2406](https://github.com/labstack/echo/pull/2406)
|
||||
* Add `middleware.CORSConfig.UnsafeWildcardOriginWithAllowCredentials` to make UNSAFE usages of wildcard origin + allow cretentials less likely [#2405](https://github.com/labstack/echo/pull/2405)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add more HTTP error values [#2277](https://github.com/labstack/echo/pull/2277)
|
||||
|
||||
|
||||
## v4.10.1 - 2023-02-19
|
||||
|
||||
**Security**
|
||||
|
||||
* Upgrade deps due to the latest golang.org/x/net vulnerability [#2402](https://github.com/labstack/echo/pull/2402)
|
||||
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add new JWT repository to the README [#2377](https://github.com/labstack/echo/pull/2377)
|
||||
* Return an empty string for ctx.path if there is no registered path [#2385](https://github.com/labstack/echo/pull/2385)
|
||||
* Add context timeout middleware [#2380](https://github.com/labstack/echo/pull/2380)
|
||||
* Update link to jaegertracing [#2394](https://github.com/labstack/echo/pull/2394)
|
||||
|
||||
|
||||
## v4.10.0 - 2022-12-27
|
||||
|
||||
**Security**
|
||||
|
||||
* We are deprecating JWT middleware in this repository. Please use https://github.com/labstack/echo-jwt instead.
|
||||
|
||||
JWT middleware is moved to separate repository to allow us to bump/upgrade version of JWT implementation (`github.com/golang-jwt/jwt`) we are using
|
||||
which we can not do in Echo core because this would break backwards compatibility guarantees we try to maintain.
|
||||
|
||||
* This minor version bumps minimum Go version to 1.17 (from 1.16) due `golang.org/x/` packages we depend on. There are
|
||||
several vulnerabilities fixed in these libraries.
|
||||
|
||||
Echo still tries to support last 4 Go versions but there are occasions we can not guarantee this promise.
|
||||
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Bump x/text to 0.3.8 [#2305](https://github.com/labstack/echo/pull/2305)
|
||||
* Bump dependencies and add notes about Go releases we support [#2336](https://github.com/labstack/echo/pull/2336)
|
||||
* Add helper interface for ProxyBalancer interface [#2316](https://github.com/labstack/echo/pull/2316)
|
||||
* Expose `middleware.CreateExtractors` function so we can use it from echo-contrib repository [#2338](https://github.com/labstack/echo/pull/2338)
|
||||
* Refactor func(Context) error to HandlerFunc [#2315](https://github.com/labstack/echo/pull/2315)
|
||||
* Improve function comments [#2329](https://github.com/labstack/echo/pull/2329)
|
||||
* Add new method HTTPError.WithInternal [#2340](https://github.com/labstack/echo/pull/2340)
|
||||
* Replace io/ioutil package usages [#2342](https://github.com/labstack/echo/pull/2342)
|
||||
* Add staticcheck to CI flow [#2343](https://github.com/labstack/echo/pull/2343)
|
||||
* Replace relative path determination from proprietary to std [#2345](https://github.com/labstack/echo/pull/2345)
|
||||
* Remove square brackets from ipv6 addresses in XFF (X-Forwarded-For header) [#2182](https://github.com/labstack/echo/pull/2182)
|
||||
* Add testcases for some BodyLimit middleware configuration options [#2350](https://github.com/labstack/echo/pull/2350)
|
||||
* Additional configuration options for RequestLogger and Logger middleware [#2341](https://github.com/labstack/echo/pull/2341)
|
||||
* Add route to request log [#2162](https://github.com/labstack/echo/pull/2162)
|
||||
* GitHub Workflows security hardening [#2358](https://github.com/labstack/echo/pull/2358)
|
||||
* Add govulncheck to CI and bump dependencies [#2362](https://github.com/labstack/echo/pull/2362)
|
||||
* Fix rate limiter docs [#2366](https://github.com/labstack/echo/pull/2366)
|
||||
* Refactor how `e.Routes()` work and introduce `e.OnAddRouteHandler` callback [#2337](https://github.com/labstack/echo/pull/2337)
|
||||
|
||||
|
||||
## v4.9.1 - 2022-10-12
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272)
|
||||
* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291)
|
||||
* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254)
|
||||
* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275)
|
||||
* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301)
|
||||
|
||||
## v4.9.0 - 2022-09-04
|
||||
|
||||
**Security**
|
||||
|
||||
* Fix open redirect vulnerability in handlers serving static directories (e.Static, e.StaticFs, echo.StaticDirectoryHandler) [#2260](https://github.com/labstack/echo/pull/2260)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Allow configuring ErrorHandler in CSRF middleware [#2257](https://github.com/labstack/echo/pull/2257)
|
||||
* Replace HTTP method constants in tests with stdlib constants [#2247](https://github.com/labstack/echo/pull/2247)
|
||||
|
||||
|
||||
## v4.8.0 - 2022-08-10
|
||||
|
||||
**Most notable things**
|
||||
|
||||
You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237)
|
||||
```go
|
||||
e.Add("COPY", "/*", func(c echo.Context) error
|
||||
return c.String(http.StatusOK, "OK COPY")
|
||||
})
|
||||
```
|
||||
|
||||
You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217)
|
||||
```go
|
||||
e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
|
||||
|
||||
g := e.Group("/images")
|
||||
g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
|
||||
```
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127)
|
||||
* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145)
|
||||
* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187)
|
||||
* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191)
|
||||
* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176)
|
||||
* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209)
|
||||
* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217)
|
||||
* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227)
|
||||
* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237)
|
||||
|
||||
## v4.7.2 - 2022-03-16
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
|
||||
* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
|
||||
* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
|
||||
|
||||
|
||||
## v4.7.1 - 2022-03-13
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
|
||||
|
||||
|
||||
## v4.7.0 - 2022-03-01
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
|
||||
* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
|
||||
* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
|
||||
* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
|
||||
* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
|
||||
|
||||
**General**
|
||||
|
||||
* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
|
||||
* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
|
||||
* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
|
||||
* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
|
||||
|
||||
## v4.6.3 - 2022-01-10
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fixed Echo version number in greeting message which was not incremented to `4.6.2` [#2066](https://github.com/labstack/echo/issues/2066)
|
||||
|
||||
|
||||
## v4.6.2 - 2022-01-08
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fixed route containing escaped colon should be matchable but is not matched to request path [#2047](https://github.com/labstack/echo/pull/2047)
|
||||
* Fixed a problem that returned wrong content-encoding when the gzip compressed content was empty. [#1921](https://github.com/labstack/echo/pull/1921)
|
||||
* Update (test) dependencies [#2021](https://github.com/labstack/echo/pull/2021)
|
||||
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add support for configurable target header for the request_id middleware [#2040](https://github.com/labstack/echo/pull/2040)
|
||||
* Change decompress middleware to use stream decompression instead of buffering [#2018](https://github.com/labstack/echo/pull/2018)
|
||||
* Documentation updates
|
||||
|
||||
|
||||
## v4.6.1 - 2021-09-26
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add start time to request logger middleware values [#1991](https://github.com/labstack/echo/pull/1991)
|
||||
|
||||
## v4.6.0 - 2021-09-20
|
||||
|
||||
Introduced a new [request logger](https://github.com/labstack/echo/blob/master/middleware/request_logger.go) middleware
|
||||
to help with cases when you want to use some other logging library in your application.
|
||||
|
||||
**Fixes**
|
||||
|
||||
* fix timeout middleware warning: superfluous response.WriteHeader [#1905](https://github.com/labstack/echo/issues/1905)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add Cookie to KeyAuth middleware's KeyLookup [#1929](https://github.com/labstack/echo/pull/1929)
|
||||
* JWT middleware should ignore case of auth scheme in request header [#1951](https://github.com/labstack/echo/pull/1951)
|
||||
* Refactor default error handler to return first if response is already committed [#1956](https://github.com/labstack/echo/pull/1956)
|
||||
* Added request logger middleware which helps to use custom logger library for logging requests. [#1980](https://github.com/labstack/echo/pull/1980)
|
||||
* Allow escaping of colon in route path so Google Cloud API "custom methods" could be implemented [#1988](https://github.com/labstack/echo/pull/1988)
|
||||
|
||||
## v4.5.0 - 2021-08-01
|
||||
|
||||
**Important notes**
|
||||
|
||||
A **BREAKING CHANGE** is introduced for JWT middleware users.
|
||||
The JWT library used for the JWT middleware had to be changed from [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) to
|
||||
[github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) due former library being unmaintained and affected by security
|
||||
issues.
|
||||
The [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) project is a drop-in replacement, but supports only the latest 2 Go versions.
|
||||
So for JWT middleware users Go 1.15+ is required. For detailed information please read [#1940](https://github.com/labstack/echo/discussions/)
|
||||
|
||||
To change the library imports in all .go files in your project replace all occurrences of `dgrijalva/jwt-go` with `golang-jwt/jwt`.
|
||||
|
||||
For Linux CLI you can use:
|
||||
```bash
|
||||
find -type f -name "*.go" -exec sed -i "s/dgrijalva\/jwt-go/golang-jwt\/jwt/g" {} \;
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Change JWT library to `github.com/golang-jwt/jwt` [#1946](https://github.com/labstack/echo/pull/1946)
|
||||
|
||||
## v4.4.0 - 2021-07-12
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Split HeaderXForwardedFor header only by comma [#1878](https://github.com/labstack/echo/pull/1878)
|
||||
* Fix Timeout middleware Context propagation [#1910](https://github.com/labstack/echo/pull/1910)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Bind data using headers as source [#1866](https://github.com/labstack/echo/pull/1866)
|
||||
* Adds JWTConfig.ParseTokenFunc to JWT middleware to allow different libraries implementing JWT parsing. [#1887](https://github.com/labstack/echo/pull/1887)
|
||||
* Adding tests for Echo#Host [#1895](https://github.com/labstack/echo/pull/1895)
|
||||
* Adds RequestIDHandler function to RequestID middleware [#1898](https://github.com/labstack/echo/pull/1898)
|
||||
* Allow for custom JSON encoding implementations [#1880](https://github.com/labstack/echo/pull/1880)
|
||||
|
||||
## v4.3.0 - 2021-05-08
|
||||
|
||||
**Important notes**
|
||||
|
||||
* Route matching has improvements for following cases:
|
||||
1. Correctly match routes with parameter part as last part of route (with trailing backslash)
|
||||
2. Considering handlers when resolving routes and search for matching http method handler
|
||||
* Echo minimal Go version is now 1.13.
|
||||
|
||||
**Fixes**
|
||||
|
||||
* When url ends with slash first param route is the match [#1804](https://github.com/labstack/echo/pull/1812)
|
||||
* Router should check if node is suitable as matching route by path+method and if not then continue search in tree [#1808](https://github.com/labstack/echo/issues/1808)
|
||||
* Fix timeout middleware not writing response correctly when handler panics [#1864](https://github.com/labstack/echo/pull/1864)
|
||||
* Fix binder not working with embedded pointer structs [#1861](https://github.com/labstack/echo/pull/1861)
|
||||
* Add Go 1.16 to CI and drop 1.12 specific code [#1850](https://github.com/labstack/echo/pull/1850)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Make KeyFunc public in JWT middleware [#1756](https://github.com/labstack/echo/pull/1756)
|
||||
* Add support for optional filesystem to the static middleware [#1797](https://github.com/labstack/echo/pull/1797)
|
||||
* Add a custom error handler to key-auth middleware [#1847](https://github.com/labstack/echo/pull/1847)
|
||||
* Allow JWT token to be looked up from multiple sources [#1845](https://github.com/labstack/echo/pull/1845)
|
||||
|
||||
## v4.2.2 - 2021-04-07
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Allow proxy middleware to use query part in rewrite (#1802)
|
||||
* Fix timeout middleware not sending status code when handler returns an error (#1805)
|
||||
* Fix Bind() when target is array/slice and path/query params complains bind target not being struct (#1835)
|
||||
* Fix panic in redirect middleware on short host name (#1813)
|
||||
* Fix timeout middleware docs (#1836)
|
||||
|
||||
## v4.2.1 - 2021-03-08
|
||||
|
||||
**Important notes**
|
||||
|
||||
Due to a datarace the config parameters for the newly added timeout middleware required a change.
|
||||
See the [docs](https://echo.labstack.com/middleware/timeout).
|
||||
A performance regression has been fixed, even bringing better performance than before for some routing scenarios.
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas)
|
||||
* Avoid context canceled errors (#1789, clwluvw)
|
||||
* Improve router to use on stack backtracking (#1791, aldas, stffabi)
|
||||
* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas)
|
||||
* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas)
|
||||
* Apply go fmt (#1788, Le0tk0k)
|
||||
* Uses strings.Equalfold (#1790, rkilingr)
|
||||
* Improve code quality (#1792, withshubh)
|
||||
|
||||
This release was made possible by our **contributors**:
|
||||
aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh
|
||||
|
||||
## v4.2.0 - 2021-02-11
|
||||
|
||||
**Important notes**
|
||||
|
||||
The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by
|
||||
enforcing `explicit tagging` for processing parameters. This **may break** your code if you
|
||||
expect combined handling of query/path/form params.
|
||||
Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request)
|
||||
|
||||
The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules.
|
||||
Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details.
|
||||
|
||||
**Security**
|
||||
|
||||
* Fix directory traversal vulnerability for Windows (#1718, little-cui)
|
||||
* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye)
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Add Echo#ListenerNetwork as configuration (#1667, pafuent)
|
||||
* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari)
|
||||
* Echo server startup to allow data race free access to listener address
|
||||
* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas)
|
||||
* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas)
|
||||
* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas)
|
||||
* Router: Performance improvements for missed routes (#1689, pafuent)
|
||||
* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb)
|
||||
* Middleware: Support real regex rules for rewrite and proxy middleware (#1767)
|
||||
* Middleware: New rate limiting middleware (#1724, iambenkay)
|
||||
* Middleware: New timeout middleware implementation for go1.13+ (#1743, )
|
||||
* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew)
|
||||
* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay)
|
||||
* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid)
|
||||
* Middleware: Support form fields in JWT middleware (#1704, rkfg)
|
||||
* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent)
|
||||
* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009)
|
||||
* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni)
|
||||
* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head)
|
||||
|
||||
**Fixes**
|
||||
|
||||
* Fix handling of special trailing slash case for partial prefix (#1741, stffabi)
|
||||
* Fix handling of static routes with trailing slash (#1747)
|
||||
* Fix Static files route not working (#1671, pwli0755, lammel)
|
||||
* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow)
|
||||
* Fix Echo#Reverse for Any type routes (#1695, pafuent)
|
||||
* Fix Router#Find panic with infinite loop (#1661, pafuent)
|
||||
* Fix Router#Find panic fails on Param paths (#1659, pafuent)
|
||||
* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel)
|
||||
* Fix incorrect CORS headers (#1669, ulasakdeniz)
|
||||
* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009)
|
||||
* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009)
|
||||
* Remove unless defer (#1656, imxyb)
|
||||
|
||||
**General**
|
||||
|
||||
* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent)
|
||||
* Add GitHub action to compare benchmarks (#1702, pafuent)
|
||||
* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas)
|
||||
* Add support for Go 1.15 in CI (#1683, asahasrabuddhe)
|
||||
* Add test for request id to remain unchanged if provided (#1719, iambenkay)
|
||||
* Refactor echo instance listener access and startup to speed up testing (#1735, aldas)
|
||||
* Refactor and improve various tests for binding and routing
|
||||
* Run test workflow only for relevant changes (#1637, #1636, pofl)
|
||||
* Update .travis.yml (#1662, santosh653)
|
||||
* Update README.md with an recents framework benchmark (#1679, pafuent)
|
||||
|
||||
This release was made possible by **over 100 commits** from more than **20 contributors**:
|
||||
asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb,
|
||||
juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari,
|
||||
rkfg, santosh653, segfiner, stffabi, ulasakdeniz
|
||||
21
vendor/github.com/labstack/echo/v4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/labstack/echo/v4/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 LabStack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
36
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
Normal file
36
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
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 install golang.org/x/lint/golint@latest
|
||||
@go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
|
||||
lint: ## Lint the files
|
||||
@staticcheck ${PKG_LIST}
|
||||
@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.20"
|
||||
test_version: ## Run tests inside Docker with given version (defaults to 1.20 oldest supported). Example: make test_version goversion=1.20
|
||||
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
|
||||
158
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
Normal file
158
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
[](https://sourcegraph.com/github.com/labstack/echo?badge)
|
||||
[](https://pkg.go.dev/github.com/labstack/echo/v4)
|
||||
[](https://goreportcard.com/report/github.com/labstack/echo)
|
||||
[](https://github.com/labstack/echo/actions)
|
||||
[](https://codecov.io/gh/labstack/echo)
|
||||
[](https://github.com/labstack/echo/discussions)
|
||||
[](https://twitter.com/labstack)
|
||||
[](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
|
||||
|
||||
## Echo
|
||||
|
||||
High performance, extensible, minimalist Go web framework.
|
||||
|
||||
* [Official website](https://echo.labstack.com)
|
||||
* [Quick start](https://echo.labstack.com/docs/quick-start)
|
||||
* [Middlewares](https://echo.labstack.com/docs/category/middleware)
|
||||
|
||||
Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
|
||||
|
||||
|
||||
### Feature Overview
|
||||
|
||||
- Optimized HTTP router which smartly prioritize routes
|
||||
- Build robust and scalable RESTful APIs
|
||||
- Group APIs
|
||||
- Extensible middleware framework
|
||||
- Define middleware at root, group or route level
|
||||
- Data binding for JSON, XML and form payload
|
||||
- Handy functions to send variety of HTTP responses
|
||||
- Centralized HTTP error handling
|
||||
- Template rendering with any template engine
|
||||
- Define your format for the logger
|
||||
- Highly customizable
|
||||
- Automatic TLS via Let’s Encrypt
|
||||
- HTTP/2 support
|
||||
|
||||
## Sponsors
|
||||
|
||||
<div>
|
||||
<a href="https://encore.dev" style="display: inline-flex; align-items: center; gap: 10px">
|
||||
<img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" height="28px" alt="encore icon"></img>
|
||||
<b>Encore – the platform for building Go-based cloud backends</b>
|
||||
</a>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Date: 2020/11/11<br>
|
||||
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||
Lower is better!
|
||||
|
||||
<img src="https://i.imgur.com/qwPNQbl.png">
|
||||
<img src="https://i.imgur.com/s8yKQjx.png">
|
||||
|
||||
The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
|
||||
|
||||
## [Guide](https://echo.labstack.com/guide)
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
// go get github.com/labstack/echo/{version}
|
||||
go get github.com/labstack/echo/v4
|
||||
```
|
||||
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Echo instance
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Routes
|
||||
e.GET("/", hello)
|
||||
|
||||
// Start server
|
||||
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
slog.Error("failed to start server", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Handler
|
||||
func hello(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
# Official middleware repositories
|
||||
|
||||
Following list of middleware is maintained by Echo team.
|
||||
|
||||
| Repository | Description |
|
||||
|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt) | [JWT](https://github.com/golang-jwt/jwt) middleware |
|
||||
| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](https://github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares |
|
||||
|
||||
# Third-party middleware repositories
|
||||
|
||||
Be careful when adding 3rd party middleware. Echo teams does not have time or manpower to guarantee safety and quality
|
||||
of middlewares in this list.
|
||||
|
||||
| Repository | Description |
|
||||
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
|
||||
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
|
||||
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
|
||||
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
|
||||
| [github.com/samber/slog-echo](https://github.com/samber/slog-echo) | Go [slog](https://pkg.go.dev/golang.org/x/exp/slog) logging library wrapper for Echo logger interface. |
|
||||
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
|
||||
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
|
||||
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code |
|
||||
|
||||
Please send a PR to add your own library here.
|
||||
|
||||
## Contribute
|
||||
|
||||
**Use issues for everything**
|
||||
|
||||
- For a small change, just send a PR.
|
||||
- For bigger changes open an issue for discussion before sending a PR.
|
||||
- PR should have:
|
||||
- Test case
|
||||
- Documentation
|
||||
- Example (If it makes sense)
|
||||
- You can also contribute by:
|
||||
- Reporting issues
|
||||
- Suggesting new features or enhancements
|
||||
- Improve/fix documentation
|
||||
|
||||
## Credits
|
||||
|
||||
- [Vishal Rana](https://github.com/vishr) (Author)
|
||||
- [Nitin Rana](https://github.com/nr17) (Consultant)
|
||||
- [Roland Lammel](https://github.com/lammel) (Maintainer)
|
||||
- [Martti T.](https://github.com/aldas) (Maintainer)
|
||||
- [Pablo Andres Fuente](https://github.com/pafuent) (Maintainer)
|
||||
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
|
||||
466
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
Normal file
466
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
Normal file
@@ -0,0 +1,466 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Binder is the interface that wraps the Bind method.
|
||||
type Binder interface {
|
||||
Bind(i interface{}, c Context) error
|
||||
}
|
||||
|
||||
// DefaultBinder is the default implementation of the Binder interface.
|
||||
type DefaultBinder struct{}
|
||||
|
||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||
// Types that don't implement this, but do implement encoding.TextUnmarshaler
|
||||
// will use that interface instead.
|
||||
type BindUnmarshaler interface {
|
||||
// UnmarshalParam decodes and assigns a value from an form or query param.
|
||||
UnmarshalParam(param string) error
|
||||
}
|
||||
|
||||
// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
|
||||
// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
|
||||
// for `a` following slice `["1", "2"] will be passed to unmarshaller.
|
||||
type bindMultipleUnmarshaler interface {
|
||||
UnmarshalParams(params []string) error
|
||||
}
|
||||
|
||||
// BindPathParams binds path params to bindable object
|
||||
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
|
||||
names := c.ParamNames()
|
||||
values := c.ParamValues()
|
||||
params := map[string][]string{}
|
||||
for i, name := range names {
|
||||
params[name] = []string{values[i]}
|
||||
}
|
||||
if err := b.bindData(i, params, "param", nil); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindQueryParams binds query params to bindable object
|
||||
func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
|
||||
if err := b.bindData(i, c.QueryParams(), "query", nil); 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
|
||||
}
|
||||
|
||||
// mediatype is found like `mime.ParseMediaType()` does it
|
||||
base, _, _ := strings.Cut(req.Header.Get(HeaderContentType), ";")
|
||||
mediatype := strings.TrimSpace(base)
|
||||
|
||||
switch mediatype {
|
||||
case MIMEApplicationJSON:
|
||||
if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil {
|
||||
switch err.(type) {
|
||||
case *HTTPError:
|
||||
return err
|
||||
default:
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
}
|
||||
case MIMEApplicationXML, MIMETextXML:
|
||||
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
||||
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
|
||||
} else if se, ok := err.(*xml.SyntaxError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
|
||||
}
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
case MIMEApplicationForm:
|
||||
params, err := c.FormParams()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
if err = b.bindData(i, params, "form", nil); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
case MIMEMultipartForm:
|
||||
params, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
if err = b.bindData(i, params.Value, "form", params.File); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedMediaType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindHeaders binds HTTP headers to a bindable object
|
||||
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
|
||||
if err := b.bindData(i, c.Request().Header, "header", nil); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind implements the `Binder#Bind` function.
|
||||
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
|
||||
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
|
||||
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
if err := b.BindPathParams(c, i); err != nil {
|
||||
return err
|
||||
}
|
||||
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
|
||||
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
|
||||
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
|
||||
method := c.Request().Method
|
||||
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
|
||||
if err = b.BindQueryParams(c, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return b.BindBody(c, i)
|
||||
}
|
||||
|
||||
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
|
||||
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string, dataFiles map[string][]*multipart.FileHeader) error {
|
||||
if destination == nil || (len(data) == 0 && len(dataFiles) == 0) {
|
||||
return nil
|
||||
}
|
||||
hasFiles := len(dataFiles) > 0
|
||||
typ := reflect.TypeOf(destination).Elem()
|
||||
val := reflect.ValueOf(destination).Elem()
|
||||
|
||||
// Support binding to limited Map destinations:
|
||||
// - map[string][]string,
|
||||
// - map[string]string <-- (binds first value from data slice)
|
||||
// - map[string]interface{}
|
||||
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
|
||||
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
|
||||
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
|
||||
k := typ.Elem().Kind()
|
||||
isElemInterface := k == reflect.Interface
|
||||
isElemString := k == reflect.String
|
||||
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
|
||||
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
|
||||
return nil
|
||||
}
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.MakeMap(typ))
|
||||
}
|
||||
for k, v := range data {
|
||||
if isElemString {
|
||||
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
|
||||
} else if isElemInterface {
|
||||
// To maintain backward compatibility, we always bind to the first string value
|
||||
// and not the slice of strings when dealing with map[string]interface{}{}
|
||||
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
|
||||
} else {
|
||||
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// !struct
|
||||
if typ.Kind() != reflect.Struct {
|
||||
if tag == "param" || tag == "query" || tag == "header" {
|
||||
// incompatible type, data is probably to be found in the body
|
||||
return nil
|
||||
}
|
||||
return errors.New("binding element must be a struct")
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ { // iterate over all destination fields
|
||||
typeField := typ.Field(i)
|
||||
structField := val.Field(i)
|
||||
if typeField.Anonymous {
|
||||
if structField.Kind() == reflect.Ptr {
|
||||
structField = structField.Elem()
|
||||
}
|
||||
}
|
||||
if !structField.CanSet() {
|
||||
continue
|
||||
}
|
||||
structFieldKind := structField.Kind()
|
||||
inputFieldName := typeField.Tag.Get(tag)
|
||||
if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
|
||||
// if anonymous struct with query/param/form tags, report an error
|
||||
return errors.New("query/param/form tags are not allowed with anonymous struct field")
|
||||
}
|
||||
|
||||
if inputFieldName == "" {
|
||||
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contain fields with tags).
|
||||
// structs that implement BindUnmarshaler are bound 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, dataFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// does not have explicit tag and is not an ordinary struct - so move to next field
|
||||
continue
|
||||
}
|
||||
|
||||
if hasFiles {
|
||||
if ok, err := isFieldMultipartFile(structField.Type()); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
if ok := setMultipartFileHeaderTypes(structField, inputFieldName, dataFiles); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputValue, exists := data[inputFieldName]
|
||||
if !exists {
|
||||
// Go json.Unmarshal supports case-insensitive binding. However the
|
||||
// url params are bound case-sensitive which is inconsistent. To
|
||||
// fix this we must check all of the map values in a
|
||||
// case-insensitive search.
|
||||
for k, v := range data {
|
||||
if strings.EqualFold(k, inputFieldName) {
|
||||
inputValue = v
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int`
|
||||
// but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` .
|
||||
|
||||
// try unmarshalling first, in case we're dealing with an alias to an array type
|
||||
if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField); ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// we could be dealing with pointer to slice `*[]string` so dereference it. There are wierd OpenAPI generators
|
||||
// that could create struct fields like that.
|
||||
if structFieldKind == reflect.Pointer {
|
||||
structFieldKind = structField.Elem().Kind()
|
||||
structField = structField.Elem()
|
||||
}
|
||||
|
||||
if structFieldKind == reflect.Slice {
|
||||
sliceOf := structField.Type().Elem().Kind()
|
||||
numElems := len(inputValue)
|
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||
for j := 0; j < numElems; j++ {
|
||||
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
structField.Set(slice)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
||||
// But also call it here, in case we're dealing with an array of BindUnmarshalers
|
||||
if ok, err := unmarshalInputToField(valueKind, val, structField); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
switch valueKind {
|
||||
case reflect.Ptr:
|
||||
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
|
||||
case reflect.Int:
|
||||
return setIntField(val, 0, structField)
|
||||
case reflect.Int8:
|
||||
return setIntField(val, 8, structField)
|
||||
case reflect.Int16:
|
||||
return setIntField(val, 16, structField)
|
||||
case reflect.Int32:
|
||||
return setIntField(val, 32, structField)
|
||||
case reflect.Int64:
|
||||
return setIntField(val, 64, structField)
|
||||
case reflect.Uint:
|
||||
return setUintField(val, 0, structField)
|
||||
case reflect.Uint8:
|
||||
return setUintField(val, 8, structField)
|
||||
case reflect.Uint16:
|
||||
return setUintField(val, 16, structField)
|
||||
case reflect.Uint32:
|
||||
return setUintField(val, 32, structField)
|
||||
case reflect.Uint64:
|
||||
return setUintField(val, 64, structField)
|
||||
case reflect.Bool:
|
||||
return setBoolField(val, structField)
|
||||
case reflect.Float32:
|
||||
return setFloatField(val, 32, structField)
|
||||
case reflect.Float64:
|
||||
return setFloatField(val, 64, structField)
|
||||
case reflect.String:
|
||||
structField.SetString(val)
|
||||
default:
|
||||
return errors.New("unknown type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) {
|
||||
if valueKind == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
fieldIValue := field.Addr().Interface()
|
||||
unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
return true, unmarshaler.UnmarshalParams(values)
|
||||
}
|
||||
|
||||
func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
|
||||
if valueKind == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
fieldIValue := field.Addr().Interface()
|
||||
switch unmarshaler := fieldIValue.(type) {
|
||||
case BindUnmarshaler:
|
||||
return true, unmarshaler.UnmarshalParam(val)
|
||||
case encoding.TextUnmarshaler:
|
||||
return true, unmarshaler.UnmarshalText([]byte(val))
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func setIntField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0"
|
||||
}
|
||||
intVal, err := strconv.ParseInt(value, 10, bitSize)
|
||||
if err == nil {
|
||||
field.SetInt(intVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setUintField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0"
|
||||
}
|
||||
uintVal, err := strconv.ParseUint(value, 10, bitSize)
|
||||
if err == nil {
|
||||
field.SetUint(uintVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setBoolField(value string, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "false"
|
||||
}
|
||||
boolVal, err := strconv.ParseBool(value)
|
||||
if err == nil {
|
||||
field.SetBool(boolVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setFloatField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(value, bitSize)
|
||||
if err == nil {
|
||||
field.SetFloat(floatVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
// NOT supported by bind as you can NOT check easily empty struct being actual file or not
|
||||
multipartFileHeaderType = reflect.TypeOf(multipart.FileHeader{})
|
||||
// supported by bind as you can check by nil value if file existed or not
|
||||
multipartFileHeaderPointerType = reflect.TypeOf(&multipart.FileHeader{})
|
||||
multipartFileHeaderSliceType = reflect.TypeOf([]multipart.FileHeader(nil))
|
||||
multipartFileHeaderPointerSliceType = reflect.TypeOf([]*multipart.FileHeader(nil))
|
||||
)
|
||||
|
||||
func isFieldMultipartFile(field reflect.Type) (bool, error) {
|
||||
switch field {
|
||||
case multipartFileHeaderPointerType,
|
||||
multipartFileHeaderSliceType,
|
||||
multipartFileHeaderPointerSliceType:
|
||||
return true, nil
|
||||
case multipartFileHeaderType:
|
||||
return true, errors.New("binding to multipart.FileHeader struct is not supported, use pointer to struct")
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setMultipartFileHeaderTypes(structField reflect.Value, inputFieldName string, files map[string][]*multipart.FileHeader) bool {
|
||||
fileHeaders := files[inputFieldName]
|
||||
if len(fileHeaders) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
result := true
|
||||
switch structField.Type() {
|
||||
case multipartFileHeaderPointerSliceType:
|
||||
structField.Set(reflect.ValueOf(fileHeaders))
|
||||
case multipartFileHeaderSliceType:
|
||||
headers := make([]multipart.FileHeader, len(fileHeaders))
|
||||
for i, fileHeader := range fileHeaders {
|
||||
headers[i] = *fileHeader
|
||||
}
|
||||
structField.Set(reflect.ValueOf(headers))
|
||||
case multipartFileHeaderPointerType:
|
||||
structField.Set(reflect.ValueOf(fileHeaders[0]))
|
||||
default:
|
||||
result = false
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
1333
vendor/github.com/labstack/echo/v4/binder.go
generated
vendored
Normal file
1333
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
|
||||
660
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
Normal file
660
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Context represents the context of the current HTTP request. It holds request and
|
||||
// response objects, path, path parameters, data and registered handler.
|
||||
type Context interface {
|
||||
// Request returns `*http.Request`.
|
||||
Request() *http.Request
|
||||
|
||||
// SetRequest sets `*http.Request`.
|
||||
SetRequest(r *http.Request)
|
||||
|
||||
// SetResponse sets `*Response`.
|
||||
SetResponse(r *Response)
|
||||
|
||||
// Response returns `*Response`.
|
||||
Response() *Response
|
||||
|
||||
// IsTLS returns true if HTTP connection is TLS otherwise false.
|
||||
IsTLS() bool
|
||||
|
||||
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
|
||||
IsWebSocket() bool
|
||||
|
||||
// Scheme returns the HTTP protocol scheme, `http` or `https`.
|
||||
Scheme() string
|
||||
|
||||
// RealIP returns the client's network address based on `X-Forwarded-For`
|
||||
// or `X-Real-IP` request header.
|
||||
// The behavior can be configured using `Echo#IPExtractor`.
|
||||
RealIP() string
|
||||
|
||||
// Path returns the registered path for the handler.
|
||||
Path() string
|
||||
|
||||
// SetPath sets the registered path for the handler.
|
||||
SetPath(p string)
|
||||
|
||||
// Param returns path parameter by name.
|
||||
Param(name string) string
|
||||
|
||||
// ParamNames returns path parameter names.
|
||||
ParamNames() []string
|
||||
|
||||
// SetParamNames sets path parameter names.
|
||||
SetParamNames(names ...string)
|
||||
|
||||
// ParamValues returns path parameter values.
|
||||
ParamValues() []string
|
||||
|
||||
// SetParamValues sets path parameter values.
|
||||
SetParamValues(values ...string)
|
||||
|
||||
// QueryParam returns the query param for the provided name.
|
||||
QueryParam(name string) string
|
||||
|
||||
// QueryParams returns the query parameters as `url.Values`.
|
||||
QueryParams() url.Values
|
||||
|
||||
// QueryString returns the URL query string.
|
||||
QueryString() string
|
||||
|
||||
// FormValue returns the form field value for the provided name.
|
||||
FormValue(name string) string
|
||||
|
||||
// FormParams returns the form parameters as `url.Values`.
|
||||
FormParams() (url.Values, error)
|
||||
|
||||
// FormFile returns the multipart form file for the provided name.
|
||||
FormFile(name string) (*multipart.FileHeader, error)
|
||||
|
||||
// MultipartForm returns the multipart form.
|
||||
MultipartForm() (*multipart.Form, error)
|
||||
|
||||
// Cookie returns the named cookie provided in the request.
|
||||
Cookie(name string) (*http.Cookie, error)
|
||||
|
||||
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||
SetCookie(cookie *http.Cookie)
|
||||
|
||||
// Cookies returns the HTTP cookies sent with the request.
|
||||
Cookies() []*http.Cookie
|
||||
|
||||
// Get retrieves data from the context.
|
||||
Get(key string) interface{}
|
||||
|
||||
// Set saves data in the context.
|
||||
Set(key string, val interface{})
|
||||
|
||||
// Bind binds path params, query params and the request body into provided type `i`. The default binder
|
||||
// binds body based on Content-Type header.
|
||||
Bind(i interface{}) error
|
||||
|
||||
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
|
||||
// Validator must be registered using `Echo#Validator`.
|
||||
Validate(i interface{}) error
|
||||
|
||||
// Render renders a template with data and sends a text/html response with status
|
||||
// code. Renderer must be registered using `Echo.Renderer`.
|
||||
Render(code int, name string, data interface{}) error
|
||||
|
||||
// HTML sends an HTTP response with status code.
|
||||
HTML(code int, html string) error
|
||||
|
||||
// HTMLBlob sends an HTTP blob response with status code.
|
||||
HTMLBlob(code int, b []byte) error
|
||||
|
||||
// String sends a string response with status code.
|
||||
String(code int, s string) error
|
||||
|
||||
// JSON sends a JSON response with status code.
|
||||
JSON(code int, i interface{}) error
|
||||
|
||||
// JSONPretty sends a pretty-print JSON with status code.
|
||||
JSONPretty(code int, i interface{}, indent string) error
|
||||
|
||||
// JSONBlob sends a JSON blob response with status code.
|
||||
JSONBlob(code int, b []byte) error
|
||||
|
||||
// JSONP sends a JSONP response with status code. It uses `callback` to construct
|
||||
// the JSONP payload.
|
||||
JSONP(code int, callback string, i interface{}) error
|
||||
|
||||
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
|
||||
// to construct the JSONP payload.
|
||||
JSONPBlob(code int, callback string, b []byte) error
|
||||
|
||||
// XML sends an XML response with status code.
|
||||
XML(code int, i interface{}) error
|
||||
|
||||
// XMLPretty sends a pretty-print XML with status code.
|
||||
XMLPretty(code int, i interface{}, indent string) error
|
||||
|
||||
// XMLBlob sends an XML blob response with status code.
|
||||
XMLBlob(code int, b []byte) error
|
||||
|
||||
// Blob sends a blob response with status code and content type.
|
||||
Blob(code int, contentType string, b []byte) error
|
||||
|
||||
// Stream sends a streaming response with status code and content type.
|
||||
Stream(code int, contentType string, r io.Reader) error
|
||||
|
||||
// File sends a response with the content of the file.
|
||||
File(file string) error
|
||||
|
||||
// Attachment sends a response as attachment, prompting client to save the
|
||||
// file.
|
||||
Attachment(file string, name string) error
|
||||
|
||||
// Inline sends a response as inline, opening the file in the browser.
|
||||
Inline(file string, name string) error
|
||||
|
||||
// NoContent sends a response with no body and a status code.
|
||||
NoContent(code int) error
|
||||
|
||||
// Redirect redirects the request to a provided URL with status code.
|
||||
Redirect(code int, url string) error
|
||||
|
||||
// Error invokes the registered global HTTP error handler. Generally used by middleware.
|
||||
// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
|
||||
// middlewares up in chain can not change Response status code or Response body anymore.
|
||||
//
|
||||
// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
|
||||
Error(err error)
|
||||
|
||||
// Handler returns the matched handler by router.
|
||||
Handler() HandlerFunc
|
||||
|
||||
// SetHandler sets the matched handler by router.
|
||||
SetHandler(h HandlerFunc)
|
||||
|
||||
// Logger returns the `Logger` instance.
|
||||
Logger() Logger
|
||||
|
||||
// SetLogger Set the logger
|
||||
SetLogger(l Logger)
|
||||
|
||||
// Echo returns the `Echo` instance.
|
||||
Echo() *Echo
|
||||
|
||||
// Reset resets the context after request completes. It must be called along
|
||||
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
|
||||
// See `Echo#ServeHTTP()`
|
||||
Reset(r *http.Request, w http.ResponseWriter)
|
||||
}
|
||||
|
||||
type context struct {
|
||||
logger Logger
|
||||
request *http.Request
|
||||
response *Response
|
||||
query url.Values
|
||||
echo *Echo
|
||||
|
||||
store Map
|
||||
lock sync.RWMutex
|
||||
|
||||
// following fields are set by Router
|
||||
handler HandlerFunc
|
||||
|
||||
// path is route path that Router matched. It is empty string where there is no route match.
|
||||
// Route registered with RouteNotFound is considered as a match and path therefore is not empty.
|
||||
path string
|
||||
|
||||
// Usually echo.Echo is sizing pvalues but there could be user created middlewares that decide to
|
||||
// overwrite parameter by calling SetParamNames + SetParamValues.
|
||||
// When echo.Echo allocated that slice it length/capacity is tied to echo.Echo.maxParam value.
|
||||
//
|
||||
// It is important that pvalues size is always equal or bigger to pnames length.
|
||||
pvalues []string
|
||||
|
||||
// pnames length is tied to param count for the matched route
|
||||
pnames []string
|
||||
}
|
||||
|
||||
const (
|
||||
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
|
||||
// Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests.
|
||||
// It is added to context only when Router does not find matching method handler for request.
|
||||
ContextKeyHeaderAllow = "echo_header_allow"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
indexPage = "index.html"
|
||||
defaultIndent = " "
|
||||
)
|
||||
|
||||
func (c *context) writeContentType(value string) {
|
||||
header := c.Response().Header()
|
||||
if header.Get(HeaderContentType) == "" {
|
||||
header.Set(HeaderContentType, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Request() *http.Request {
|
||||
return c.request
|
||||
}
|
||||
|
||||
func (c *context) SetRequest(r *http.Request) {
|
||||
c.request = r
|
||||
}
|
||||
|
||||
func (c *context) Response() *Response {
|
||||
return c.response
|
||||
}
|
||||
|
||||
func (c *context) SetResponse(r *Response) {
|
||||
c.response = r
|
||||
}
|
||||
|
||||
func (c *context) IsTLS() bool {
|
||||
return c.request.TLS != nil
|
||||
}
|
||||
|
||||
func (c *context) IsWebSocket() bool {
|
||||
upgrade := c.request.Header.Get(HeaderUpgrade)
|
||||
return strings.EqualFold(upgrade, "websocket")
|
||||
}
|
||||
|
||||
func (c *context) Scheme() string {
|
||||
// Can't use `r.Request.URL.Scheme`
|
||||
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
|
||||
if c.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
|
||||
return "https"
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (c *context) RealIP() string {
|
||||
if c.echo != nil && c.echo.IPExtractor != nil {
|
||||
return c.echo.IPExtractor(c.request)
|
||||
}
|
||||
// Fall back to legacy behavior
|
||||
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||
i := strings.IndexAny(ip, ",")
|
||||
if i > 0 {
|
||||
xffip := strings.TrimSpace(ip[:i])
|
||||
xffip = strings.TrimPrefix(xffip, "[")
|
||||
xffip = strings.TrimSuffix(xffip, "]")
|
||||
return xffip
|
||||
}
|
||||
return ip
|
||||
}
|
||||
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||
ip = strings.TrimPrefix(ip, "[")
|
||||
ip = strings.TrimSuffix(ip, "]")
|
||||
return ip
|
||||
}
|
||||
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
|
||||
return ra
|
||||
}
|
||||
|
||||
func (c *context) Path() string {
|
||||
return c.path
|
||||
}
|
||||
|
||||
func (c *context) SetPath(p string) {
|
||||
c.path = p
|
||||
}
|
||||
|
||||
func (c *context) Param(name string) string {
|
||||
for i, n := range c.pnames {
|
||||
if i < len(c.pvalues) {
|
||||
if n == name {
|
||||
return c.pvalues[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *context) ParamNames() []string {
|
||||
return c.pnames
|
||||
}
|
||||
|
||||
func (c *context) SetParamNames(names ...string) {
|
||||
c.pnames = names
|
||||
|
||||
l := len(names)
|
||||
if 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 overridden in a Context#SetParamValues
|
||||
newPvalues := make([]string, l)
|
||||
copy(newPvalues, c.pvalues)
|
||||
c.pvalues = newPvalues
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) ParamValues() []string {
|
||||
return c.pvalues[:len(c.pnames)]
|
||||
}
|
||||
|
||||
func (c *context) SetParamValues(values ...string) {
|
||||
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam (or bigger) at all times
|
||||
// It will brake the Router#Find code
|
||||
limit := len(values)
|
||||
if limit > len(c.pvalues) {
|
||||
c.pvalues = make([]string, limit)
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
c.pvalues[i] = values[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) QueryParam(name string) string {
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query.Get(name)
|
||||
}
|
||||
|
||||
func (c *context) QueryParams() url.Values {
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query
|
||||
}
|
||||
|
||||
func (c *context) QueryString() string {
|
||||
return c.request.URL.RawQuery
|
||||
}
|
||||
|
||||
func (c *context) FormValue(name string) string {
|
||||
return c.request.FormValue(name)
|
||||
}
|
||||
|
||||
func (c *context) FormParams() (url.Values, error) {
|
||||
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
|
||||
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := c.request.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.request.Form, nil
|
||||
}
|
||||
|
||||
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
f, fh, err := c.request.FormFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Close()
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
func (c *context) MultipartForm() (*multipart.Form, error) {
|
||||
err := c.request.ParseMultipartForm(defaultMemory)
|
||||
return c.request.MultipartForm, err
|
||||
}
|
||||
|
||||
func (c *context) Cookie(name string) (*http.Cookie, error) {
|
||||
return c.request.Cookie(name)
|
||||
}
|
||||
|
||||
func (c *context) SetCookie(cookie *http.Cookie) {
|
||||
http.SetCookie(c.Response(), cookie)
|
||||
}
|
||||
|
||||
func (c *context) Cookies() []*http.Cookie {
|
||||
return c.request.Cookies()
|
||||
}
|
||||
|
||||
func (c *context) Get(key string) interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.store[key]
|
||||
}
|
||||
|
||||
func (c *context) Set(key string, val interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.store == nil {
|
||||
c.store = make(Map)
|
||||
}
|
||||
c.store[key] = val
|
||||
}
|
||||
|
||||
func (c *context) Bind(i interface{}) error {
|
||||
return c.echo.Binder.Bind(i, c)
|
||||
}
|
||||
|
||||
func (c *context) Validate(i interface{}) error {
|
||||
if c.echo.Validator == nil {
|
||||
return ErrValidatorNotRegistered
|
||||
}
|
||||
return c.echo.Validator.Validate(i)
|
||||
}
|
||||
|
||||
func (c *context) Render(code int, name string, data interface{}) (err error) {
|
||||
if c.echo.Renderer == nil {
|
||||
return ErrRendererNotRegistered
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
|
||||
return
|
||||
}
|
||||
return c.HTMLBlob(code, buf.Bytes())
|
||||
}
|
||||
|
||||
func (c *context) HTML(code int, html string) (err error) {
|
||||
return c.HTMLBlob(code, []byte(html))
|
||||
}
|
||||
|
||||
func (c *context) HTMLBlob(code int, b []byte) (err error) {
|
||||
return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
|
||||
}
|
||||
|
||||
func (c *context) String(code int, s string) (err error) {
|
||||
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
|
||||
}
|
||||
|
||||
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
|
||||
indent := ""
|
||||
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||
indent = defaultIndent
|
||||
}
|
||||
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.echo.JSONSerializer.Serialize(c, i, indent); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.response.Write([]byte(");")); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) json(code int, i interface{}, indent string) error {
|
||||
c.writeContentType(MIMEApplicationJSON)
|
||||
c.response.Status = code
|
||||
return c.echo.JSONSerializer.Serialize(c, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) JSON(code int, i interface{}) (err error) {
|
||||
indent := ""
|
||||
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||
indent = defaultIndent
|
||||
}
|
||||
return c.json(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
|
||||
return c.json(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) JSONBlob(code int, b []byte) (err error) {
|
||||
return c.Blob(code, MIMEApplicationJSON, b)
|
||||
}
|
||||
|
||||
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
||||
return c.jsonPBlob(code, callback, i)
|
||||
}
|
||||
|
||||
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.response.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.response.Write([]byte(");"))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) xml(code int, i interface{}, indent string) (err error) {
|
||||
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
enc := xml.NewEncoder(c.response)
|
||||
if indent != "" {
|
||||
enc.Indent("", indent)
|
||||
}
|
||||
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||
return
|
||||
}
|
||||
return enc.Encode(i)
|
||||
}
|
||||
|
||||
func (c *context) XML(code int, i interface{}) (err error) {
|
||||
indent := ""
|
||||
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||
indent = defaultIndent
|
||||
}
|
||||
return c.xml(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
|
||||
return c.xml(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.response.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
|
||||
c.writeContentType(contentType)
|
||||
c.response.WriteHeader(code)
|
||||
_, err = c.response.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
|
||||
c.writeContentType(contentType)
|
||||
c.response.WriteHeader(code)
|
||||
_, err = io.Copy(c.response, r)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Attachment(file, name string) error {
|
||||
return c.contentDisposition(file, name, "attachment")
|
||||
}
|
||||
|
||||
func (c *context) Inline(file, name string) error {
|
||||
return c.contentDisposition(file, name, "inline")
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func (c *context) contentDisposition(file, name, dispositionType string) error {
|
||||
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name)))
|
||||
return c.File(file)
|
||||
}
|
||||
|
||||
func (c *context) NoContent(code int) error {
|
||||
c.response.WriteHeader(code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Redirect(code int, url string) error {
|
||||
if code < 300 || code > 308 {
|
||||
return ErrInvalidRedirectCode
|
||||
}
|
||||
c.response.Header().Set(HeaderLocation, url)
|
||||
c.response.WriteHeader(code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Error(err error) {
|
||||
c.echo.HTTPErrorHandler(err, c)
|
||||
}
|
||||
|
||||
func (c *context) Echo() *Echo {
|
||||
return c.echo
|
||||
}
|
||||
|
||||
func (c *context) Handler() HandlerFunc {
|
||||
return c.handler
|
||||
}
|
||||
|
||||
func (c *context) SetHandler(h HandlerFunc) {
|
||||
c.handler = h
|
||||
}
|
||||
|
||||
func (c *context) Logger() Logger {
|
||||
res := c.logger
|
||||
if res != nil {
|
||||
return res
|
||||
}
|
||||
return c.echo.Logger
|
||||
}
|
||||
|
||||
func (c *context) SetLogger(l Logger) {
|
||||
c.logger = l
|
||||
}
|
||||
|
||||
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
|
||||
c.request = r
|
||||
c.response.reset(w)
|
||||
c.query = nil
|
||||
c.handler = NotFoundHandler
|
||||
c.store = nil
|
||||
c.path = ""
|
||||
c.pnames = nil
|
||||
c.logger = nil
|
||||
// NOTE: Don't reset because it has to have length c.echo.maxParam (or bigger) at all times
|
||||
for i := 0; i < len(c.pvalues); i++ {
|
||||
c.pvalues[i] = ""
|
||||
}
|
||||
}
|
||||
52
vendor/github.com/labstack/echo/v4/context_fs.go
generated
vendored
Normal file
52
vendor/github.com/labstack/echo/v4/context_fs.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (c *context) File(file string) error {
|
||||
return fsFile(c, file, c.echo.Filesystem)
|
||||
}
|
||||
|
||||
// FileFS serves file from given file system.
|
||||
//
|
||||
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||
// including `assets/images` as their prefix.
|
||||
func (c *context) FileFS(file string, filesystem fs.FS) error {
|
||||
return fsFile(c, file, filesystem)
|
||||
}
|
||||
|
||||
func fsFile(c Context, file string, filesystem fs.FS) error {
|
||||
f, err := filesystem.Open(file)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, _ := f.Stat()
|
||||
if fi.IsDir() {
|
||||
file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
|
||||
f, err = filesystem.Open(file)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
defer f.Close()
|
||||
if fi, err = f.Stat(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ff, ok := f.(io.ReadSeeker)
|
||||
if !ok {
|
||||
return errors.New("file does not implement io.ReadSeeker")
|
||||
}
|
||||
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
|
||||
return nil
|
||||
}
|
||||
1015
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
Normal file
1015
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
162
vendor/github.com/labstack/echo/v4/echo_fs.go
generated
vendored
Normal file
162
vendor/github.com/labstack/echo/v4/echo_fs.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type filesystem struct {
|
||||
// Filesystem is file system used by Static and File handlers to access files.
|
||||
// Defaults to os.DirFS(".")
|
||||
//
|
||||
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||
// including `assets/images` as their prefix.
|
||||
Filesystem fs.FS
|
||||
}
|
||||
|
||||
func createFilesystem() filesystem {
|
||||
return filesystem{
|
||||
Filesystem: newDefaultFS(),
|
||||
}
|
||||
}
|
||||
|
||||
// Static registers a new route with path prefix to serve static files from the provided root directory.
|
||||
func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
|
||||
subFs := MustSubFS(e.Filesystem, fsRoot)
|
||||
return e.Add(
|
||||
http.MethodGet,
|
||||
pathPrefix+"*",
|
||||
StaticDirectoryHandler(subFs, false),
|
||||
)
|
||||
}
|
||||
|
||||
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
|
||||
//
|
||||
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||
// including `assets/images` as their prefix.
|
||||
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
|
||||
return e.Add(
|
||||
http.MethodGet,
|
||||
pathPrefix+"*",
|
||||
StaticDirectoryHandler(filesystem, false),
|
||||
)
|
||||
}
|
||||
|
||||
// StaticDirectoryHandler creates handler function to serve files from provided file system
|
||||
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
|
||||
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
|
||||
return func(c Context) error {
|
||||
p := c.Param("*")
|
||||
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
|
||||
tmpPath, err := url.PathUnescape(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unescape path variable: %w", err)
|
||||
}
|
||||
p = tmpPath
|
||||
}
|
||||
|
||||
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
|
||||
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
|
||||
fi, err := fs.Stat(fileSystem, name)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// If the request is for a directory and does not end with "/"
|
||||
p = c.Request().URL.Path // path must not be empty.
|
||||
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
|
||||
// Redirect to ends with "/"
|
||||
return c.Redirect(http.StatusMovedPermanently, sanitizeURI(p+"/"))
|
||||
}
|
||||
return fsFile(c, name, fileSystem)
|
||||
}
|
||||
}
|
||||
|
||||
// FileFS registers a new route with path to serve file from the provided file system.
|
||||
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
|
||||
return e.GET(path, StaticFileHandler(file, filesystem), m...)
|
||||
}
|
||||
|
||||
// StaticFileHandler creates handler function to serve file from provided file system
|
||||
func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
|
||||
return func(c Context) error {
|
||||
return fsFile(c, file, filesystem)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`.
|
||||
// v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface.
|
||||
// Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/`
|
||||
// etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not
|
||||
// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to
|
||||
// traverse up from current executable run path.
|
||||
// NB: private because you really should use fs.FS implementation instances
|
||||
type defaultFS struct {
|
||||
fs fs.FS
|
||||
prefix string
|
||||
}
|
||||
|
||||
func newDefaultFS() *defaultFS {
|
||||
dir, _ := os.Getwd()
|
||||
return &defaultFS{
|
||||
prefix: dir,
|
||||
fs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs defaultFS) Open(name string) (fs.File, error) {
|
||||
if fs.fs == nil {
|
||||
return os.Open(name)
|
||||
}
|
||||
return fs.fs.Open(name)
|
||||
}
|
||||
|
||||
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
|
||||
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
|
||||
if dFS, ok := currentFs.(*defaultFS); ok {
|
||||
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
|
||||
// fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
|
||||
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
|
||||
if !filepath.IsAbs(root) {
|
||||
root = filepath.Join(dFS.prefix, root)
|
||||
}
|
||||
return &defaultFS{
|
||||
prefix: root,
|
||||
fs: os.DirFS(root),
|
||||
}, nil
|
||||
}
|
||||
return fs.Sub(currentFs, root)
|
||||
}
|
||||
|
||||
// MustSubFS creates sub FS from current filesystem or panic on failure.
|
||||
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
|
||||
//
|
||||
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
|
||||
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
|
||||
// create sub fs which uses necessary prefix for directory path.
|
||||
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
|
||||
subFs, err := subFS(currentFs, fsRoot)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
|
||||
}
|
||||
return subFs
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
129
vendor/github.com/labstack/echo/v4/group.go
generated
vendored
Normal file
129
vendor/github.com/labstack/echo/v4/group.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Group is a set of sub-routes for a specified route. It can be used for inner
|
||||
// routes that share a common middleware or functionality that should be separate
|
||||
// from the parent echo instance while still inheriting from it.
|
||||
type Group struct {
|
||||
common
|
||||
host string
|
||||
prefix string
|
||||
echo *Echo
|
||||
middleware []MiddlewareFunc
|
||||
}
|
||||
|
||||
// Use implements `Echo#Use()` for sub-routes within the Group.
|
||||
func (g *Group) Use(middleware ...MiddlewareFunc) {
|
||||
g.middleware = append(g.middleware, middleware...)
|
||||
if len(g.middleware) == 0 {
|
||||
return
|
||||
}
|
||||
// group level middlewares are different from Echo `Pre` and `Use` middlewares (those are global). Group level middlewares
|
||||
// are only executed if they are added to the Router with route.
|
||||
// So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the
|
||||
// Router would find route to match our request path and therefore guarantee the middleware(s) will get executed.
|
||||
g.RouteNotFound("", NotFoundHandler)
|
||||
g.RouteNotFound("/*", NotFoundHandler)
|
||||
}
|
||||
|
||||
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
|
||||
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodConnect, path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
|
||||
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodDelete, path, h, m...)
|
||||
}
|
||||
|
||||
// GET implements `Echo#GET()` for sub-routes within the Group.
|
||||
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodGet, path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
|
||||
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodHead, path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
|
||||
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodOptions, path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
|
||||
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPatch, path, h, m...)
|
||||
}
|
||||
|
||||
// POST implements `Echo#POST()` for sub-routes within the Group.
|
||||
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPost, path, h, m...)
|
||||
}
|
||||
|
||||
// PUT implements `Echo#PUT()` for sub-routes within the Group.
|
||||
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPut, path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
|
||||
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodTrace, path, h, m...)
|
||||
}
|
||||
|
||||
// Any implements `Echo#Any()` for sub-routes within the Group.
|
||||
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Match implements `Echo#Match()` for sub-routes within the Group.
|
||||
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
|
||||
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
|
||||
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||
m = append(m, g.middleware...)
|
||||
m = append(m, middleware...)
|
||||
sg = g.echo.Group(g.prefix+prefix, m...)
|
||||
sg.host = g.host
|
||||
return
|
||||
}
|
||||
|
||||
// File implements `Echo#File()` for sub-routes within the Group.
|
||||
func (g *Group) File(path, file string) {
|
||||
g.file(path, file, g.GET)
|
||||
}
|
||||
|
||||
// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
|
||||
//
|
||||
// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
|
||||
func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(RouteNotFound, path, h, m...)
|
||||
}
|
||||
|
||||
// Add implements `Echo#Add()` for sub-routes within the Group.
|
||||
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||
// Combine into a new slice to avoid accidentally passing the same slice for
|
||||
// multiple routes, which would lead to later add() calls overwriting the
|
||||
// middleware from earlier calls.
|
||||
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||
m = append(m, g.middleware...)
|
||||
m = append(m, middleware...)
|
||||
return g.echo.add(g.host, method, g.prefix+path, handler, m...)
|
||||
}
|
||||
33
vendor/github.com/labstack/echo/v4/group_fs.go
generated
vendored
Normal file
33
vendor/github.com/labstack/echo/v4/group_fs.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Static implements `Echo#Static()` for sub-routes within the Group.
|
||||
func (g *Group) Static(pathPrefix, fsRoot string) {
|
||||
subFs := MustSubFS(g.echo.Filesystem, fsRoot)
|
||||
g.StaticFS(pathPrefix, subFs)
|
||||
}
|
||||
|
||||
// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
|
||||
//
|
||||
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||
// including `assets/images` as their prefix.
|
||||
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) {
|
||||
g.Add(
|
||||
http.MethodGet,
|
||||
pathPrefix+"*",
|
||||
StaticDirectoryHandler(filesystem, false),
|
||||
)
|
||||
}
|
||||
|
||||
// FileFS implements `Echo#FileFS()` for sub-routes within the Group.
|
||||
func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
|
||||
return g.GET(path, StaticFileHandler(file, filesystem), m...)
|
||||
}
|
||||
277
vendor/github.com/labstack/echo/v4/ip.go
generated
vendored
Normal file
277
vendor/github.com/labstack/echo/v4/ip.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/**
|
||||
By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 )
|
||||
Source: https://echo.labstack.com/guide/ip-address/
|
||||
|
||||
IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.
|
||||
Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.
|
||||
|
||||
However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
|
||||
In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
|
||||
Otherwise, you might give someone a chance of deceiving you. **A security risk!**
|
||||
|
||||
To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
|
||||
In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
|
||||
This guides show you why and how.
|
||||
|
||||
> Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.
|
||||
|
||||
Let's start from two questions to know the right direction:
|
||||
|
||||
1. Do you put any HTTP (L7) proxy in front of the application?
|
||||
- It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway).
|
||||
2. If yes, what HTTP header do your proxies use to pass client IP to the application?
|
||||
|
||||
## Case 1. With no proxy
|
||||
|
||||
If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.
|
||||
Any HTTP header is untrustable because the clients have full control what headers to be set.
|
||||
|
||||
In this case, use `echo.ExtractIPDirect()`.
|
||||
|
||||
```go
|
||||
e.IPExtractor = echo.ExtractIPDirect()
|
||||
```
|
||||
|
||||
## Case 2. With proxies using `X-Forwarded-For` header
|
||||
|
||||
[`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header
|
||||
to relay clients' IP addresses.
|
||||
At each hop on the proxies, they append the request IP address at the end of the header.
|
||||
|
||||
Following example diagram illustrates this behavior.
|
||||
|
||||
```text
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ "Origin" │───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
|
||||
│ (IP: a) │ │ (IP: b) │ │ (IP: c) │ │ │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
|
||||
Case 1.
|
||||
XFF: "" "a" "a, b"
|
||||
~~~~~~
|
||||
Case 2.
|
||||
XFF: "x" "x, a" "x, a, b"
|
||||
~~~~~~~~~
|
||||
↑ What your app will see
|
||||
```
|
||||
|
||||
In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
|
||||
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure".
|
||||
In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
|
||||
|
||||
In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
|
||||
|
||||
```go
|
||||
e.IPExtractor = echo.ExtractIPFromXFFHeader()
|
||||
```
|
||||
|
||||
By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address
|
||||
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
|
||||
[RFC4193](https://tools.ietf.org/html/rfc4193)).
|
||||
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
|
||||
|
||||
E.g.:
|
||||
|
||||
```go
|
||||
e.IPExtractor = echo.ExtractIPFromXFFHeader(
|
||||
TrustLinkLocal(false),
|
||||
TrustIPRanges(lbIPRange),
|
||||
)
|
||||
```
|
||||
|
||||
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
|
||||
|
||||
## Case 3. With proxies using `X-Real-IP` header
|
||||
|
||||
`X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF.
|
||||
|
||||
If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`.
|
||||
|
||||
```go
|
||||
e.IPExtractor = echo.ExtractIPFromRealIPHeader()
|
||||
```
|
||||
|
||||
Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address
|
||||
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
|
||||
[RFC4193](https://tools.ietf.org/html/rfc4193)).
|
||||
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
|
||||
|
||||
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
|
||||
|
||||
> **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**.
|
||||
> Otherwise there is a chance of fraud, as it is what clients can control.
|
||||
|
||||
## About default behavior
|
||||
|
||||
In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer.
|
||||
|
||||
As you might already notice, after reading this article, this is not good.
|
||||
Sole reason this is default is just backward compatibility.
|
||||
|
||||
## Private IP ranges
|
||||
|
||||
See: https://en.wikipedia.org/wiki/Private_network
|
||||
|
||||
Private IPv4 address ranges (RFC 1918):
|
||||
* 10.0.0.0 – 10.255.255.255 (24-bit block)
|
||||
* 172.16.0.0 – 172.31.255.255 (20-bit block)
|
||||
* 192.168.0.0 – 192.168.255.255 (16-bit block)
|
||||
|
||||
Private IPv6 address ranges:
|
||||
* fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA)
|
||||
|
||||
*/
|
||||
|
||||
type ipChecker struct {
|
||||
trustExtraRanges []*net.IPNet
|
||||
trustLoopback bool
|
||||
trustLinkLocal bool
|
||||
trustPrivateNet bool
|
||||
}
|
||||
|
||||
// TrustOption is config for which IP address to trust
|
||||
type TrustOption func(*ipChecker)
|
||||
|
||||
// TrustLoopback configures if you trust loopback address (default: true).
|
||||
func TrustLoopback(v bool) TrustOption {
|
||||
return func(c *ipChecker) {
|
||||
c.trustLoopback = v
|
||||
}
|
||||
}
|
||||
|
||||
// TrustLinkLocal configures if you trust link-local address (default: true).
|
||||
func TrustLinkLocal(v bool) TrustOption {
|
||||
return func(c *ipChecker) {
|
||||
c.trustLinkLocal = v
|
||||
}
|
||||
}
|
||||
|
||||
// TrustPrivateNet configures if you trust private network address (default: true).
|
||||
func TrustPrivateNet(v bool) TrustOption {
|
||||
return func(c *ipChecker) {
|
||||
c.trustPrivateNet = v
|
||||
}
|
||||
}
|
||||
|
||||
// TrustIPRange add trustable IP ranges using CIDR notation.
|
||||
func TrustIPRange(ipRange *net.IPNet) TrustOption {
|
||||
return func(c *ipChecker) {
|
||||
c.trustExtraRanges = append(c.trustExtraRanges, ipRange)
|
||||
}
|
||||
}
|
||||
|
||||
func newIPChecker(configs []TrustOption) *ipChecker {
|
||||
checker := &ipChecker{trustLoopback: true, trustLinkLocal: true, trustPrivateNet: true}
|
||||
for _, configure := range configs {
|
||||
configure(checker)
|
||||
}
|
||||
return checker
|
||||
}
|
||||
|
||||
// Go1.16+ added `ip.IsPrivate()` but until that use this implementation
|
||||
func isPrivateIPRange(ip net.IP) bool {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4[0] == 10 ||
|
||||
ip4[0] == 172 && ip4[1]&0xf0 == 16 ||
|
||||
ip4[0] == 192 && ip4[1] == 168
|
||||
}
|
||||
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
|
||||
}
|
||||
|
||||
func (c *ipChecker) trust(ip net.IP) bool {
|
||||
if c.trustLoopback && ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
if c.trustLinkLocal && ip.IsLinkLocalUnicast() {
|
||||
return true
|
||||
}
|
||||
if c.trustPrivateNet && isPrivateIPRange(ip) {
|
||||
return true
|
||||
}
|
||||
for _, trustedRange := range c.trustExtraRanges {
|
||||
if trustedRange.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IPExtractor is a function to extract IP addr from http.Request.
|
||||
// Set appropriate one to Echo#IPExtractor.
|
||||
// See https://echo.labstack.com/guide/ip-address for more details.
|
||||
type IPExtractor func(*http.Request) string
|
||||
|
||||
// ExtractIPDirect extracts IP address using actual IP address.
|
||||
// Use this if your server faces to internet directory (i.e.: uses no proxy).
|
||||
func ExtractIPDirect() IPExtractor {
|
||||
return extractIP
|
||||
}
|
||||
|
||||
func extractIP(req *http.Request) string {
|
||||
ra, _, _ := net.SplitHostPort(req.RemoteAddr)
|
||||
return ra
|
||||
}
|
||||
|
||||
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
|
||||
// Use this if you put proxy which uses this header.
|
||||
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
|
||||
checker := newIPChecker(options)
|
||||
return func(req *http.Request) string {
|
||||
directIP := extractIP(req)
|
||||
realIP := req.Header.Get(HeaderXRealIP)
|
||||
if realIP == "" {
|
||||
return directIP
|
||||
}
|
||||
|
||||
if checker.trust(net.ParseIP(directIP)) {
|
||||
realIP = strings.TrimPrefix(realIP, "[")
|
||||
realIP = strings.TrimSuffix(realIP, "]")
|
||||
if rIP := net.ParseIP(realIP); rIP != nil {
|
||||
return realIP
|
||||
}
|
||||
}
|
||||
|
||||
return directIP
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractIPFromXFFHeader extracts IP address using x-forwarded-for header.
|
||||
// Use this if you put proxy which uses this header.
|
||||
// This returns nearest untrustable IP. If all IPs are trustable, returns furthest one (i.e.: XFF[0]).
|
||||
func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
|
||||
checker := newIPChecker(options)
|
||||
return func(req *http.Request) string {
|
||||
directIP := extractIP(req)
|
||||
xffs := req.Header[HeaderXForwardedFor]
|
||||
if len(xffs) == 0 {
|
||||
return directIP
|
||||
}
|
||||
ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP)
|
||||
for i := len(ips) - 1; i >= 0; i-- {
|
||||
ips[i] = strings.TrimSpace(ips[i])
|
||||
ips[i] = strings.TrimPrefix(ips[i], "[")
|
||||
ips[i] = strings.TrimSuffix(ips[i], "]")
|
||||
ip := net.ParseIP(ips[i])
|
||||
if ip == nil {
|
||||
// Unable to parse IP; cannot trust entire records
|
||||
return directIP
|
||||
}
|
||||
if !checker.trust(ip) {
|
||||
return ip.String()
|
||||
}
|
||||
}
|
||||
// All of the IPs are trusted; return first element because it is furthest from server (best effort strategy).
|
||||
return strings.TrimSpace(ips[0])
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/labstack/echo/v4/json.go
generated
vendored
Normal file
34
vendor/github.com/labstack/echo/v4/json.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DefaultJSONSerializer implements JSON encoding using encoding/json.
|
||||
type DefaultJSONSerializer struct{}
|
||||
|
||||
// Serialize converts an interface into a json and writes it to the response.
|
||||
// You can optionally use the indent parameter to produce pretty JSONs.
|
||||
func (d DefaultJSONSerializer) Serialize(c Context, i interface{}, indent string) error {
|
||||
enc := json.NewEncoder(c.Response())
|
||||
if indent != "" {
|
||||
enc.SetIndent("", indent)
|
||||
}
|
||||
return enc.Encode(i)
|
||||
}
|
||||
|
||||
// Deserialize reads a JSON from a request body and converts it into an interface.
|
||||
func (d DefaultJSONSerializer) Deserialize(c Context, i interface{}) error {
|
||||
err := json.NewDecoder(c.Request().Body).Decode(i)
|
||||
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
|
||||
} else if se, ok := err.(*json.SyntaxError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
41
vendor/github.com/labstack/echo/v4/log.go
generated
vendored
Normal file
41
vendor/github.com/labstack/echo/v4/log.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"github.com/labstack/gommon/log"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Logger defines the logging interface.
|
||||
type Logger interface {
|
||||
Output() io.Writer
|
||||
SetOutput(w io.Writer)
|
||||
Prefix() string
|
||||
SetPrefix(p string)
|
||||
Level() log.Lvl
|
||||
SetLevel(v log.Lvl)
|
||||
SetHeader(h string)
|
||||
Print(i ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Printj(j log.JSON)
|
||||
Debug(i ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Debugj(j log.JSON)
|
||||
Info(i ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Infoj(j log.JSON)
|
||||
Warn(i ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warnj(j log.JSON)
|
||||
Error(i ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Errorj(j log.JSON)
|
||||
Fatal(i ...interface{})
|
||||
Fatalj(j log.JSON)
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panic(i ...interface{})
|
||||
Panicj(j log.JSON)
|
||||
Panicf(format string, args ...interface{})
|
||||
}
|
||||
111
vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
generated
vendored
Normal file
111
vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// BasicAuthConfig defines the config for BasicAuth middleware.
|
||||
type BasicAuthConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Validator is a function to validate BasicAuth credentials.
|
||||
// Required.
|
||||
Validator BasicAuthValidator
|
||||
|
||||
// Realm is a string to define realm attribute of BasicAuth.
|
||||
// Default value "Restricted".
|
||||
Realm string
|
||||
}
|
||||
|
||||
// BasicAuthValidator defines a function to validate BasicAuth credentials.
|
||||
// The function should return a boolean indicating whether the credentials are valid,
|
||||
// and an error if any error occurs during the validation process.
|
||||
type BasicAuthValidator func(string, string, echo.Context) (bool, error)
|
||||
|
||||
const (
|
||||
basic = "basic"
|
||||
defaultRealm = "Restricted"
|
||||
)
|
||||
|
||||
// DefaultBasicAuthConfig is the default BasicAuth middleware config.
|
||||
var DefaultBasicAuthConfig = BasicAuthConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Realm: defaultRealm,
|
||||
}
|
||||
|
||||
// BasicAuth returns an BasicAuth middleware.
|
||||
//
|
||||
// For valid credentials it calls the next handler.
|
||||
// For missing or invalid credentials, it sends "401 - Unauthorized" response.
|
||||
func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
|
||||
c := DefaultBasicAuthConfig
|
||||
c.Validator = fn
|
||||
return BasicAuthWithConfig(c)
|
||||
}
|
||||
|
||||
// BasicAuthWithConfig returns an BasicAuth middleware with config.
|
||||
// See `BasicAuth()`.
|
||||
func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Validator == nil {
|
||||
panic("echo: basic-auth middleware requires a validator function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBasicAuthConfig.Skipper
|
||||
}
|
||||
if config.Realm == "" {
|
||||
config.Realm = defaultRealm
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
auth := c.Request().Header.Get(echo.HeaderAuthorization)
|
||||
l := len(basic)
|
||||
|
||||
if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) {
|
||||
// Invalid base64 shouldn't be treated as error
|
||||
// instead should be treated as invalid client input
|
||||
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err)
|
||||
}
|
||||
|
||||
cred := string(b)
|
||||
for i := 0; i < len(cred); i++ {
|
||||
if cred[i] == ':' {
|
||||
// Verify credentials
|
||||
valid, err := config.Validator(cred[:i], cred[i+1:], c)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if valid {
|
||||
return next(c)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
realm := defaultRealm
|
||||
if config.Realm != defaultRealm {
|
||||
realm = strconv.Quote(config.Realm)
|
||||
}
|
||||
|
||||
// Need to return `401` for browsers to pop-up login box.
|
||||
c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
}
|
||||
}
|
||||
113
vendor/github.com/labstack/echo/v4/middleware/body_dump.go
generated
vendored
Normal file
113
vendor/github.com/labstack/echo/v4/middleware/body_dump.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// BodyDumpConfig defines the config for BodyDump middleware.
|
||||
type BodyDumpConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Handler receives request and response payload.
|
||||
// Required.
|
||||
Handler BodyDumpHandler
|
||||
}
|
||||
|
||||
// BodyDumpHandler receives the request and response payload.
|
||||
type BodyDumpHandler func(echo.Context, []byte, []byte)
|
||||
|
||||
type bodyDumpResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
// DefaultBodyDumpConfig is the default BodyDump middleware config.
|
||||
var DefaultBodyDumpConfig = BodyDumpConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
|
||||
// BodyDump returns a BodyDump middleware.
|
||||
//
|
||||
// BodyDump middleware captures the request and response payload and calls the
|
||||
// registered handler.
|
||||
func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
|
||||
c := DefaultBodyDumpConfig
|
||||
c.Handler = handler
|
||||
return BodyDumpWithConfig(c)
|
||||
}
|
||||
|
||||
// BodyDumpWithConfig returns a BodyDump middleware with config.
|
||||
// See: `BodyDump()`.
|
||||
func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Handler == nil {
|
||||
panic("echo: body-dump middleware requires a handler function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Request
|
||||
reqBody := []byte{}
|
||||
if c.Request().Body != nil { // Read
|
||||
reqBody, _ = io.ReadAll(c.Request().Body)
|
||||
}
|
||||
c.Request().Body = io.NopCloser(bytes.NewBuffer(reqBody)) // Reset
|
||||
|
||||
// Response
|
||||
resBody := new(bytes.Buffer)
|
||||
mw := io.MultiWriter(c.Response().Writer, resBody)
|
||||
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
|
||||
c.Response().Writer = writer
|
||||
|
||||
if err = next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
|
||||
// Callback
|
||||
config.Handler(c, reqBody, resBody.Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Flush() {
|
||||
err := http.NewResponseController(w.ResponseWriter).Flush()
|
||||
if err != nil && errors.Is(err, http.ErrNotSupported) {
|
||||
panic(errors.New("response writer flushing is not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return http.NewResponseController(w.ResponseWriter).Hijack()
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
}
|
||||
114
vendor/github.com/labstack/echo/v4/middleware/body_limit.go
generated
vendored
Normal file
114
vendor/github.com/labstack/echo/v4/middleware/body_limit.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/bytes"
|
||||
)
|
||||
|
||||
// BodyLimitConfig defines the config for BodyLimit middleware.
|
||||
type BodyLimitConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Maximum allowed size for a request body, it can be specified
|
||||
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
|
||||
Limit string `yaml:"limit"`
|
||||
limit int64
|
||||
}
|
||||
|
||||
type limitedReader struct {
|
||||
BodyLimitConfig
|
||||
reader io.ReadCloser
|
||||
read int64
|
||||
}
|
||||
|
||||
// DefaultBodyLimitConfig is the default BodyLimit middleware config.
|
||||
var DefaultBodyLimitConfig = BodyLimitConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
|
||||
// BodyLimit returns a BodyLimit middleware.
|
||||
//
|
||||
// BodyLimit middleware sets the maximum allowed size for a request body, if the
|
||||
// size exceeds the configured limit, it sends "413 - Request Entity Too Large"
|
||||
// response. The BodyLimit is determined based on both `Content-Length` request
|
||||
// header and actual content read, which makes it super secure.
|
||||
// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
|
||||
// G, T or P.
|
||||
func BodyLimit(limit string) echo.MiddlewareFunc {
|
||||
c := DefaultBodyLimitConfig
|
||||
c.Limit = limit
|
||||
return BodyLimitWithConfig(c)
|
||||
}
|
||||
|
||||
// BodyLimitWithConfig returns a BodyLimit middleware with config.
|
||||
// See: `BodyLimit()`.
|
||||
func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyLimitConfig.Skipper
|
||||
}
|
||||
|
||||
limit, err := bytes.Parse(config.Limit)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit))
|
||||
}
|
||||
config.limit = limit
|
||||
pool := limitedReaderPool(config)
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
|
||||
// Based on content length
|
||||
if req.ContentLength > config.limit {
|
||||
return echo.ErrStatusRequestEntityTooLarge
|
||||
}
|
||||
|
||||
// Based on content read
|
||||
r := pool.Get().(*limitedReader)
|
||||
r.Reset(req.Body)
|
||||
defer pool.Put(r)
|
||||
req.Body = r
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *limitedReader) Read(b []byte) (n int, err error) {
|
||||
n, err = r.reader.Read(b)
|
||||
r.read += int64(n)
|
||||
if r.read > r.limit {
|
||||
return n, echo.ErrStatusRequestEntityTooLarge
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *limitedReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
func (r *limitedReader) Reset(reader io.ReadCloser) {
|
||||
r.reader = reader
|
||||
r.read = 0
|
||||
}
|
||||
|
||||
func limitedReaderPool(c BodyLimitConfig) sync.Pool {
|
||||
return sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &limitedReader{BodyLimitConfig: c}
|
||||
},
|
||||
}
|
||||
}
|
||||
230
vendor/github.com/labstack/echo/v4/middleware/compress.go
generated
vendored
Normal file
230
vendor/github.com/labstack/echo/v4/middleware/compress.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// GzipConfig defines the config for Gzip middleware.
|
||||
type GzipConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Gzip compression level.
|
||||
// Optional. Default value -1.
|
||||
Level int `yaml:"level"`
|
||||
|
||||
// Length threshold before gzip compression is applied.
|
||||
// Optional. Default value 0.
|
||||
//
|
||||
// Most of the time you will not need to change the default. Compressing
|
||||
// a short response might increase the transmitted data because of the
|
||||
// gzip format overhead. Compressing the response will also consume CPU
|
||||
// and time on the server and the client (for decompressing). Depending on
|
||||
// your use case such a threshold might be useful.
|
||||
//
|
||||
// See also:
|
||||
// https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
|
||||
MinLength int
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
wroteHeader bool
|
||||
wroteBody bool
|
||||
minLength int
|
||||
minLengthExceeded bool
|
||||
buffer *bytes.Buffer
|
||||
code int
|
||||
}
|
||||
|
||||
const (
|
||||
gzipScheme = "gzip"
|
||||
)
|
||||
|
||||
// DefaultGzipConfig is the default Gzip middleware config.
|
||||
var DefaultGzipConfig = GzipConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Level: -1,
|
||||
MinLength: 0,
|
||||
}
|
||||
|
||||
// Gzip returns a middleware which compresses HTTP response using gzip compression
|
||||
// scheme.
|
||||
func Gzip() echo.MiddlewareFunc {
|
||||
return GzipWithConfig(DefaultGzipConfig)
|
||||
}
|
||||
|
||||
// GzipWithConfig return Gzip middleware with config.
|
||||
// See: `Gzip()`.
|
||||
func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultGzipConfig.Skipper
|
||||
}
|
||||
if config.Level == 0 {
|
||||
config.Level = DefaultGzipConfig.Level
|
||||
}
|
||||
if config.MinLength < 0 {
|
||||
config.MinLength = DefaultGzipConfig.MinLength
|
||||
}
|
||||
|
||||
pool := gzipCompressPool(config)
|
||||
bpool := bufferPool()
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
res := c.Response()
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
|
||||
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
|
||||
i := pool.Get()
|
||||
w, ok := i.(*gzip.Writer)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
|
||||
}
|
||||
rw := res.Writer
|
||||
w.Reset(rw)
|
||||
|
||||
buf := bpool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
|
||||
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw, minLength: config.MinLength, buffer: buf}
|
||||
defer func() {
|
||||
// There are different reasons for cases when we have not yet written response to the client and now need to do so.
|
||||
// a) handler response had only response code and no response body (ala 404 or redirects etc). Response code need to be written now.
|
||||
// b) body is shorter than our minimum length threshold and being buffered currently and needs to be written
|
||||
if !grw.wroteBody {
|
||||
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
|
||||
res.Header().Del(echo.HeaderContentEncoding)
|
||||
}
|
||||
if grw.wroteHeader {
|
||||
rw.WriteHeader(grw.code)
|
||||
}
|
||||
// We have to reset response to it's pristine state when
|
||||
// nothing is written to body or error is returned.
|
||||
// See issue #424, #407.
|
||||
res.Writer = rw
|
||||
w.Reset(io.Discard)
|
||||
} else if !grw.minLengthExceeded {
|
||||
// Write uncompressed response
|
||||
res.Writer = rw
|
||||
if grw.wroteHeader {
|
||||
grw.ResponseWriter.WriteHeader(grw.code)
|
||||
}
|
||||
grw.buffer.WriteTo(rw)
|
||||
w.Reset(io.Discard)
|
||||
}
|
||||
w.Close()
|
||||
bpool.Put(buf)
|
||||
pool.Put(w)
|
||||
}()
|
||||
res.Writer = grw
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) WriteHeader(code int) {
|
||||
w.Header().Del(echo.HeaderContentLength) // Issue #444
|
||||
|
||||
w.wroteHeader = true
|
||||
|
||||
// Delay writing of the header until we know if we'll actually compress the response
|
||||
w.code = code
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.Header().Get(echo.HeaderContentType) == "" {
|
||||
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
|
||||
}
|
||||
w.wroteBody = true
|
||||
|
||||
if !w.minLengthExceeded {
|
||||
n, err := w.buffer.Write(b)
|
||||
|
||||
if w.buffer.Len() >= w.minLength {
|
||||
w.minLengthExceeded = true
|
||||
|
||||
// The minimum length is exceeded, add Content-Encoding header and write the header
|
||||
w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
if w.wroteHeader {
|
||||
w.ResponseWriter.WriteHeader(w.code)
|
||||
}
|
||||
|
||||
return w.Writer.Write(w.buffer.Bytes())
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Flush() {
|
||||
if !w.minLengthExceeded {
|
||||
// Enforce compression because we will not know how much more data will come
|
||||
w.minLengthExceeded = true
|
||||
w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
if w.wroteHeader {
|
||||
w.ResponseWriter.WriteHeader(w.code)
|
||||
}
|
||||
|
||||
w.Writer.Write(w.buffer.Bytes())
|
||||
}
|
||||
|
||||
w.Writer.(*gzip.Writer).Flush()
|
||||
_ = http.NewResponseController(w.ResponseWriter).Flush()
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return http.NewResponseController(w.ResponseWriter).Hijack()
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if p, ok := w.ResponseWriter.(http.Pusher); ok {
|
||||
return p.Push(target, opts)
|
||||
}
|
||||
return http.ErrNotSupported
|
||||
}
|
||||
|
||||
func gzipCompressPool(config GzipConfig) sync.Pool {
|
||||
return sync.Pool{
|
||||
New: func() interface{} {
|
||||
w, err := gzip.NewWriterLevel(io.Discard, config.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func bufferPool() sync.Pool {
|
||||
return sync.Pool{
|
||||
New: func() interface{} {
|
||||
b := &bytes.Buffer{}
|
||||
return b
|
||||
},
|
||||
}
|
||||
}
|
||||
75
vendor/github.com/labstack/echo/v4/middleware/context_timeout.go
generated
vendored
Normal file
75
vendor/github.com/labstack/echo/v4/middleware/context_timeout.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ContextTimeoutConfig defines the config for ContextTimeout middleware.
|
||||
type ContextTimeoutConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// ErrorHandler is a function when error aries in middleware execution.
|
||||
ErrorHandler func(err error, c echo.Context) error
|
||||
|
||||
// Timeout configures a timeout for the middleware, defaults to 0 for no timeout
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// ContextTimeout returns a middleware which returns error (503 Service Unavailable error) to client
|
||||
// when underlying method returns context.DeadlineExceeded error.
|
||||
func ContextTimeout(timeout time.Duration) echo.MiddlewareFunc {
|
||||
return ContextTimeoutWithConfig(ContextTimeoutConfig{Timeout: timeout})
|
||||
}
|
||||
|
||||
// ContextTimeoutWithConfig returns a Timeout middleware with config.
|
||||
func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.MiddlewareFunc {
|
||||
mw, err := config.ToMiddleware()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mw
|
||||
}
|
||||
|
||||
// ToMiddleware converts Config to middleware.
|
||||
func (config ContextTimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
|
||||
if config.Timeout == 0 {
|
||||
return nil, errors.New("timeout must be set")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultSkipper
|
||||
}
|
||||
if config.ErrorHandler == nil {
|
||||
config.ErrorHandler = func(err error, c echo.Context) error {
|
||||
if err != nil && errors.Is(err, context.DeadlineExceeded) {
|
||||
return echo.ErrServiceUnavailable.WithInternal(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
timeoutContext, cancel := context.WithTimeout(c.Request().Context(), config.Timeout)
|
||||
defer cancel()
|
||||
|
||||
c.SetRequest(c.Request().WithContext(timeoutContext))
|
||||
|
||||
if err := next(c); err != nil {
|
||||
return config.ErrorHandler(err, c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
307
vendor/github.com/labstack/echo/v4/middleware/cors.go
generated
vendored
Normal file
307
vendor/github.com/labstack/echo/v4/middleware/cors.go
generated
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// CORSConfig defines the config for CORS middleware.
|
||||
type CORSConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// AllowOrigins determines the value of the Access-Control-Allow-Origin
|
||||
// response header. This header defines a list of origins that may access the
|
||||
// resource. The wildcard characters '*' and '?' are supported and are
|
||||
// converted to regex fragments '.*' and '.' accordingly.
|
||||
//
|
||||
// Security: use extreme caution when handling the origin, and carefully
|
||||
// validate any logic. Remember that attackers may register hostile domain names.
|
||||
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
|
||||
//
|
||||
// Optional. Default value []string{"*"}.
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||
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.
|
||||
//
|
||||
// Security: use extreme caution when handling the origin, and carefully
|
||||
// validate any logic. Remember that attackers may register hostile domain names.
|
||||
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
|
||||
//
|
||||
// Optional.
|
||||
AllowOriginFunc func(origin string) (bool, error) `yaml:"-"`
|
||||
|
||||
// AllowMethods determines the value of the Access-Control-Allow-Methods
|
||||
// response header. This header specified the list of methods allowed when
|
||||
// accessing the resource. This is used in response to a preflight request.
|
||||
//
|
||||
// Optional. Default value DefaultCORSConfig.AllowMethods.
|
||||
// If `allowMethods` is left empty, this middleware will fill for preflight
|
||||
// request `Access-Control-Allow-Methods` header value
|
||||
// from `Allow` header that echo.Router set into context.
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
||||
AllowMethods []string `yaml:"allow_methods"`
|
||||
|
||||
// AllowHeaders determines the value of the Access-Control-Allow-Headers
|
||||
// response header. This header is used in response to a preflight request to
|
||||
// indicate which HTTP headers can be used when making the actual request.
|
||||
//
|
||||
// Optional. Default value []string{}.
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
|
||||
AllowHeaders []string `yaml:"allow_headers"`
|
||||
|
||||
// AllowCredentials determines the value of the
|
||||
// Access-Control-Allow-Credentials response header. This header indicates
|
||||
// whether or not the response to the request can be exposed when the
|
||||
// credentials mode (Request.credentials) is true. When used as part of a
|
||||
// response to a preflight request, this indicates whether or not the actual
|
||||
// request can be made using credentials. See also
|
||||
// [MDN: Access-Control-Allow-Credentials].
|
||||
//
|
||||
// Optional. Default value false, in which case the header is not set.
|
||||
//
|
||||
// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
|
||||
// See "Exploiting CORS misconfigurations for Bitcoins and bounties",
|
||||
// https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
||||
AllowCredentials bool `yaml:"allow_credentials"`
|
||||
|
||||
// UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials
|
||||
// flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header.
|
||||
//
|
||||
// This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties)
|
||||
// attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject.
|
||||
//
|
||||
// Optional. Default value is false.
|
||||
UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"`
|
||||
|
||||
// ExposeHeaders determines the value of Access-Control-Expose-Headers, which
|
||||
// defines a list of headers that clients are allowed to access.
|
||||
//
|
||||
// Optional. Default value []string{}, in which case the header is not set.
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header
|
||||
ExposeHeaders []string `yaml:"expose_headers"`
|
||||
|
||||
// MaxAge determines the value of the Access-Control-Max-Age response header.
|
||||
// This header indicates how long (in seconds) the results of a preflight
|
||||
// request can be cached.
|
||||
// The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response.
|
||||
//
|
||||
// Optional. Default value 0 - meaning header is not sent.
|
||||
//
|
||||
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
||||
MaxAge int `yaml:"max_age"`
|
||||
}
|
||||
|
||||
// DefaultCORSConfig is the default CORS middleware config.
|
||||
var DefaultCORSConfig = CORSConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
||||
}
|
||||
|
||||
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
|
||||
// See also [MDN: Cross-Origin Resource Sharing (CORS)].
|
||||
//
|
||||
// Security: Poorly configured CORS can compromise security because it allows
|
||||
// relaxation of the browser's Same-Origin policy. See [Exploiting CORS
|
||||
// misconfigurations for Bitcoins and bounties] and [Portswigger: Cross-origin
|
||||
// resource sharing (CORS)] for more details.
|
||||
//
|
||||
// [MDN: Cross-Origin Resource Sharing (CORS)]: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
|
||||
// [Exploiting CORS misconfigurations for Bitcoins and bounties]: https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
|
||||
// [Portswigger: Cross-origin resource sharing (CORS)]: https://portswigger.net/web-security/cors
|
||||
func CORS() echo.MiddlewareFunc {
|
||||
return CORSWithConfig(DefaultCORSConfig)
|
||||
}
|
||||
|
||||
// CORSWithConfig returns a CORS middleware with config.
|
||||
// See: [CORS].
|
||||
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultCORSConfig.Skipper
|
||||
}
|
||||
if len(config.AllowOrigins) == 0 {
|
||||
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
|
||||
}
|
||||
hasCustomAllowMethods := true
|
||||
if len(config.AllowMethods) == 0 {
|
||||
hasCustomAllowMethods = false
|
||||
config.AllowMethods = DefaultCORSConfig.AllowMethods
|
||||
}
|
||||
|
||||
allowOriginPatterns := make([]*regexp.Regexp, 0, len(config.AllowOrigins))
|
||||
for _, origin := range config.AllowOrigins {
|
||||
if origin == "*" {
|
||||
continue // "*" is handled differently and does not need regexp
|
||||
}
|
||||
pattern := regexp.QuoteMeta(origin)
|
||||
pattern = strings.ReplaceAll(pattern, "\\*", ".*")
|
||||
pattern = strings.ReplaceAll(pattern, "\\?", ".")
|
||||
pattern = "^" + pattern + "$"
|
||||
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
// this is to preserve previous behaviour - invalid patterns were just ignored.
|
||||
// If we would turn this to panic, users with invalid patterns
|
||||
// would have applications crashing in production due unrecovered panic.
|
||||
// TODO: this should be turned to error/panic in `v5`
|
||||
continue
|
||||
}
|
||||
allowOriginPatterns = append(allowOriginPatterns, re)
|
||||
}
|
||||
|
||||
allowMethods := strings.Join(config.AllowMethods, ",")
|
||||
allowHeaders := strings.Join(config.AllowHeaders, ",")
|
||||
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
|
||||
|
||||
maxAge := "0"
|
||||
if config.MaxAge > 0 {
|
||||
maxAge = strconv.Itoa(config.MaxAge)
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
origin := req.Header.Get(echo.HeaderOrigin)
|
||||
allowOrigin := ""
|
||||
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
|
||||
// Preflight request is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method,
|
||||
// Access-Control-Request-Headers, and the Origin header. See: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
|
||||
// For simplicity we just consider method type and later `Origin` header.
|
||||
preflight := req.Method == http.MethodOptions
|
||||
|
||||
// Although router adds special handler in case of OPTIONS method we avoid calling next for OPTIONS in this middleware
|
||||
// as CORS requests do not have cookies / authentication headers by default, so we could get stuck in auth
|
||||
// middlewares by calling next(c).
|
||||
// But we still want to send `Allow` header as response in case of Non-CORS OPTIONS request as router default
|
||||
// handler does.
|
||||
routerAllowMethods := ""
|
||||
if preflight {
|
||||
tmpAllowMethods, ok := c.Get(echo.ContextKeyHeaderAllow).(string)
|
||||
if ok && tmpAllowMethods != "" {
|
||||
routerAllowMethods = tmpAllowMethods
|
||||
c.Response().Header().Set(echo.HeaderAllow, routerAllowMethods)
|
||||
}
|
||||
}
|
||||
|
||||
// No Origin provided. This is (probably) not request from actual browser - proceed executing middleware chain
|
||||
if origin == "" {
|
||||
if !preflight {
|
||||
return next(c)
|
||||
}
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
if config.AllowOriginFunc != nil {
|
||||
allowed, err := config.AllowOriginFunc(origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if allowed {
|
||||
allowOrigin = origin
|
||||
}
|
||||
} else {
|
||||
// Check allowed origins
|
||||
for _, o := range config.AllowOrigins {
|
||||
if o == "*" && config.AllowCredentials && config.UnsafeWildcardOriginWithAllowCredentials {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
if o == "*" || o == origin {
|
||||
allowOrigin = o
|
||||
break
|
||||
}
|
||||
if matchSubdomain(origin, o) {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
checkPatterns := false
|
||||
if allowOrigin == "" {
|
||||
// to avoid regex cost by invalid (long) domains (253 is domain name max limit)
|
||||
if len(origin) <= (253+3+5) && strings.Contains(origin, "://") {
|
||||
checkPatterns = true
|
||||
}
|
||||
}
|
||||
if checkPatterns {
|
||||
for _, re := range allowOriginPatterns {
|
||||
if match := re.MatchString(origin); match {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Origin not allowed
|
||||
if allowOrigin == "" {
|
||||
if !preflight {
|
||||
return next(c)
|
||||
}
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
|
||||
if config.AllowCredentials {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
|
||||
}
|
||||
|
||||
// Simple request
|
||||
if !preflight {
|
||||
if exposeHeaders != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Preflight request
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
|
||||
|
||||
if !hasCustomAllowMethods && routerAllowMethods != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowMethods, routerAllowMethods)
|
||||
} else {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
|
||||
}
|
||||
|
||||
if allowHeaders != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
|
||||
} else {
|
||||
h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
|
||||
if h != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
|
||||
}
|
||||
}
|
||||
if config.MaxAge != 0 {
|
||||
res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
|
||||
}
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
218
vendor/github.com/labstack/echo/v4/middleware/csrf.go
generated
vendored
Normal file
218
vendor/github.com/labstack/echo/v4/middleware/csrf.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// CSRFConfig defines the config for CSRF middleware.
|
||||
type CSRFConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// TokenLength is the length of the generated token.
|
||||
TokenLength uint8 `yaml:"token_length"`
|
||||
// Optional. Default value 32.
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:X-CSRF-Token".
|
||||
// Possible values:
|
||||
// - "header:<name>" or "header:<name>:<cut-prefix>"
|
||||
// - "query:<name>"
|
||||
// - "form:<name>"
|
||||
// Multiple sources example:
|
||||
// - "header:X-CSRF-Token,query:csrf"
|
||||
TokenLookup string `yaml:"token_lookup"`
|
||||
|
||||
// Context key to store generated CSRF token into context.
|
||||
// Optional. Default value "csrf".
|
||||
ContextKey string `yaml:"context_key"`
|
||||
|
||||
// Name of the CSRF cookie. This cookie will store CSRF token.
|
||||
// Optional. Default value "csrf".
|
||||
CookieName string `yaml:"cookie_name"`
|
||||
|
||||
// Domain of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookieDomain string `yaml:"cookie_domain"`
|
||||
|
||||
// Path of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookiePath string `yaml:"cookie_path"`
|
||||
|
||||
// Max age (in seconds) of the CSRF cookie.
|
||||
// Optional. Default value 86400 (24hr).
|
||||
CookieMaxAge int `yaml:"cookie_max_age"`
|
||||
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool `yaml:"cookie_secure"`
|
||||
|
||||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool `yaml:"cookie_http_only"`
|
||||
|
||||
// Indicates SameSite mode of the CSRF cookie.
|
||||
// Optional. Default value SameSiteDefaultMode.
|
||||
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
|
||||
|
||||
// ErrorHandler defines a function which is executed for returning custom errors.
|
||||
ErrorHandler CSRFErrorHandler
|
||||
}
|
||||
|
||||
// CSRFErrorHandler is a function which is executed for creating custom errors.
|
||||
type CSRFErrorHandler func(err error, c echo.Context) error
|
||||
|
||||
// ErrCSRFInvalid is returned when CSRF check fails
|
||||
var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
|
||||
|
||||
// DefaultCSRFConfig is the default CSRF middleware config.
|
||||
var DefaultCSRFConfig = CSRFConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
TokenLength: 32,
|
||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||
ContextKey: "csrf",
|
||||
CookieName: "_csrf",
|
||||
CookieMaxAge: 86400,
|
||||
CookieSameSite: http.SameSiteDefaultMode,
|
||||
}
|
||||
|
||||
// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
|
||||
// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
func CSRF() echo.MiddlewareFunc {
|
||||
c := DefaultCSRFConfig
|
||||
return CSRFWithConfig(c)
|
||||
}
|
||||
|
||||
// CSRFWithConfig returns a CSRF middleware with config.
|
||||
// See `CSRF()`.
|
||||
func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultCSRFConfig.Skipper
|
||||
}
|
||||
if config.TokenLength == 0 {
|
||||
config.TokenLength = DefaultCSRFConfig.TokenLength
|
||||
}
|
||||
|
||||
if config.TokenLookup == "" {
|
||||
config.TokenLookup = DefaultCSRFConfig.TokenLookup
|
||||
}
|
||||
if config.ContextKey == "" {
|
||||
config.ContextKey = DefaultCSRFConfig.ContextKey
|
||||
}
|
||||
if config.CookieName == "" {
|
||||
config.CookieName = DefaultCSRFConfig.CookieName
|
||||
}
|
||||
if config.CookieMaxAge == 0 {
|
||||
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
|
||||
}
|
||||
if config.CookieSameSite == http.SameSiteNoneMode {
|
||||
config.CookieSecure = true
|
||||
}
|
||||
|
||||
extractors, cErr := CreateExtractors(config.TokenLookup)
|
||||
if cErr != nil {
|
||||
panic(cErr)
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
token := ""
|
||||
if k, err := c.Cookie(config.CookieName); err != nil {
|
||||
token = randomString(config.TokenLength)
|
||||
} else {
|
||||
token = k.Value // Reuse token
|
||||
}
|
||||
|
||||
switch c.Request().Method {
|
||||
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
|
||||
default:
|
||||
// Validate token only for requests which are not defined as 'safe' by RFC7231
|
||||
var lastExtractorErr error
|
||||
var lastTokenErr error
|
||||
outer:
|
||||
for _, extractor := range extractors {
|
||||
clientTokens, err := extractor(c)
|
||||
if err != nil {
|
||||
lastExtractorErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, clientToken := range clientTokens {
|
||||
if validateCSRFToken(token, clientToken) {
|
||||
lastTokenErr = nil
|
||||
lastExtractorErr = nil
|
||||
break outer
|
||||
}
|
||||
lastTokenErr = ErrCSRFInvalid
|
||||
}
|
||||
}
|
||||
var finalErr error
|
||||
if lastTokenErr != nil {
|
||||
finalErr = lastTokenErr
|
||||
} else if lastExtractorErr != nil {
|
||||
// ugly part to preserve backwards compatible errors. someone could rely on them
|
||||
if lastExtractorErr == errQueryExtractorValueMissing {
|
||||
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the query string")
|
||||
} else if lastExtractorErr == errFormExtractorValueMissing {
|
||||
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the form parameter")
|
||||
} else if lastExtractorErr == errHeaderExtractorValueMissing {
|
||||
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header")
|
||||
} else {
|
||||
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, lastExtractorErr.Error())
|
||||
}
|
||||
finalErr = lastExtractorErr
|
||||
}
|
||||
|
||||
if finalErr != nil {
|
||||
if config.ErrorHandler != nil {
|
||||
return config.ErrorHandler(finalErr, c)
|
||||
}
|
||||
return finalErr
|
||||
}
|
||||
}
|
||||
|
||||
// Set CSRF cookie
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = config.CookieName
|
||||
cookie.Value = token
|
||||
if config.CookiePath != "" {
|
||||
cookie.Path = config.CookiePath
|
||||
}
|
||||
if config.CookieDomain != "" {
|
||||
cookie.Domain = config.CookieDomain
|
||||
}
|
||||
if config.CookieSameSite != http.SameSiteDefaultMode {
|
||||
cookie.SameSite = config.CookieSameSite
|
||||
}
|
||||
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
|
||||
cookie.Secure = config.CookieSecure
|
||||
cookie.HttpOnly = config.CookieHTTPOnly
|
||||
c.SetCookie(cookie)
|
||||
|
||||
// Store token in the context
|
||||
c.Set(config.ContextKey, token)
|
||||
|
||||
// Protect clients from caching the response
|
||||
c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateCSRFToken(token, clientToken string) bool {
|
||||
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
|
||||
}
|
||||
98
vendor/github.com/labstack/echo/v4/middleware/decompress.go
generated
vendored
Normal file
98
vendor/github.com/labstack/echo/v4/middleware/decompress.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// DecompressConfig defines the config for Decompress middleware.
|
||||
type 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
|
||||
}
|
||||
|
||||
// DefaultDecompressConfig defines the config for decompress middleware
|
||||
var DefaultDecompressConfig = DecompressConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
GzipDecompressPool: &DefaultGzipDecompressPool{},
|
||||
}
|
||||
|
||||
// DefaultGzipDecompressPool is the default implementation of Decompressor interface
|
||||
type DefaultGzipDecompressPool struct {
|
||||
}
|
||||
|
||||
func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
|
||||
return sync.Pool{New: func() interface{} { return new(gzip.Reader) }}
|
||||
}
|
||||
|
||||
// Decompress decompresses request body based if content encoding type is set to "gzip" with default config
|
||||
func Decompress() echo.MiddlewareFunc {
|
||||
return DecompressWithConfig(DefaultDecompressConfig)
|
||||
}
|
||||
|
||||
// DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config
|
||||
func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultGzipConfig.Skipper
|
||||
}
|
||||
if config.GzipDecompressPool == nil {
|
||||
config.GzipDecompressPool = DefaultDecompressConfig.GzipDecompressPool
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
pool := config.GzipDecompressPool.gzipDecompressPool()
|
||||
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if c.Request().Header.Get(echo.HeaderContentEncoding) != GZIPEncoding {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
i := pool.Get()
|
||||
gr, ok := i.(*gzip.Reader)
|
||||
if !ok || gr == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
|
||||
}
|
||||
defer pool.Put(gr)
|
||||
|
||||
b := c.Request().Body
|
||||
defer b.Close()
|
||||
|
||||
if err := gr.Reset(b); err != nil {
|
||||
if err == io.EOF { //ignore if body is empty
|
||||
return next(c)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// only Close gzip reader if it was set to a proper gzip source otherwise it will panic on close.
|
||||
defer gr.Close()
|
||||
|
||||
c.Request().Body = gr
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
207
vendor/github.com/labstack/echo/v4/middleware/extractor.go
generated
vendored
Normal file
207
vendor/github.com/labstack/echo/v4/middleware/extractor.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion
|
||||
// attack vector
|
||||
extractorLimit = 20
|
||||
)
|
||||
|
||||
var errHeaderExtractorValueMissing = errors.New("missing value in request header")
|
||||
var errHeaderExtractorValueInvalid = errors.New("invalid value in request header")
|
||||
var errQueryExtractorValueMissing = errors.New("missing value in the query string")
|
||||
var errParamExtractorValueMissing = errors.New("missing value in path params")
|
||||
var errCookieExtractorValueMissing = errors.New("missing value in cookies")
|
||||
var errFormExtractorValueMissing = errors.New("missing value in the form")
|
||||
|
||||
// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context.
|
||||
type ValuesExtractor func(c echo.Context) ([]string, error)
|
||||
|
||||
// CreateExtractors creates ValuesExtractors from given lookups.
|
||||
// Lookups is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
|
||||
// to extract key from the request.
|
||||
// Possible values:
|
||||
// - "header:<name>" or "header:<name>:<cut-prefix>"
|
||||
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
|
||||
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
|
||||
// want to cut is `<auth-scheme> ` note the space at the end.
|
||||
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
|
||||
// - "query:<name>"
|
||||
// - "param:<name>"
|
||||
// - "form:<name>"
|
||||
// - "cookie:<name>"
|
||||
//
|
||||
// Multiple sources example:
|
||||
// - "header:Authorization,header:X-Api-Key"
|
||||
func CreateExtractors(lookups string) ([]ValuesExtractor, error) {
|
||||
return createExtractors(lookups, "")
|
||||
}
|
||||
|
||||
func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) {
|
||||
if lookups == "" {
|
||||
return nil, nil
|
||||
}
|
||||
sources := strings.Split(lookups, ",")
|
||||
var extractors = make([]ValuesExtractor, 0)
|
||||
for _, source := range sources {
|
||||
parts := strings.Split(source, ":")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source)
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "query":
|
||||
extractors = append(extractors, valuesFromQuery(parts[1]))
|
||||
case "param":
|
||||
extractors = append(extractors, valuesFromParam(parts[1]))
|
||||
case "cookie":
|
||||
extractors = append(extractors, valuesFromCookie(parts[1]))
|
||||
case "form":
|
||||
extractors = append(extractors, valuesFromForm(parts[1]))
|
||||
case "header":
|
||||
prefix := ""
|
||||
if len(parts) > 2 {
|
||||
prefix = parts[2]
|
||||
} else if authScheme != "" && parts[1] == echo.HeaderAuthorization {
|
||||
// backwards compatibility for JWT and KeyAuth:
|
||||
// * we only apply this fix to Authorization as header we use and uses prefixes like "Bearer <token-value>" etc
|
||||
// * previously header extractor assumed that auth-scheme/prefix had a space as suffix we need to retain that
|
||||
// behaviour for default values and Authorization header.
|
||||
prefix = authScheme
|
||||
if !strings.HasSuffix(prefix, " ") {
|
||||
prefix += " "
|
||||
}
|
||||
}
|
||||
extractors = append(extractors, valuesFromHeader(parts[1], prefix))
|
||||
}
|
||||
}
|
||||
return extractors, nil
|
||||
}
|
||||
|
||||
// valuesFromHeader returns a functions that extracts values from the request header.
|
||||
// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static
|
||||
// prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to remove is `<auth-scheme> `
|
||||
// note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove
|
||||
// is `Basic `. In case of JWT tokens `Authorization: Bearer <token>` prefix is `Bearer `.
|
||||
// If prefix is left empty the whole value is returned.
|
||||
func valuesFromHeader(header string, valuePrefix string) ValuesExtractor {
|
||||
prefixLen := len(valuePrefix)
|
||||
// standard library parses http.Request header keys in canonical form but we may provide something else so fix this
|
||||
header = textproto.CanonicalMIMEHeaderKey(header)
|
||||
return func(c echo.Context) ([]string, error) {
|
||||
values := c.Request().Header.Values(header)
|
||||
if len(values) == 0 {
|
||||
return nil, errHeaderExtractorValueMissing
|
||||
}
|
||||
|
||||
result := make([]string, 0)
|
||||
for i, value := range values {
|
||||
if prefixLen == 0 {
|
||||
result = append(result, value)
|
||||
if i >= extractorLimit-1 {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) {
|
||||
result = append(result, value[prefixLen:])
|
||||
if i >= extractorLimit-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
if prefixLen > 0 {
|
||||
return nil, errHeaderExtractorValueInvalid
|
||||
}
|
||||
return nil, errHeaderExtractorValueMissing
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// valuesFromQuery returns a function that extracts values from the query string.
|
||||
func valuesFromQuery(param string) ValuesExtractor {
|
||||
return func(c echo.Context) ([]string, error) {
|
||||
result := c.QueryParams()[param]
|
||||
if len(result) == 0 {
|
||||
return nil, errQueryExtractorValueMissing
|
||||
} else if len(result) > extractorLimit-1 {
|
||||
result = result[:extractorLimit]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// valuesFromParam returns a function that extracts values from the url param string.
|
||||
func valuesFromParam(param string) ValuesExtractor {
|
||||
return func(c echo.Context) ([]string, error) {
|
||||
result := make([]string, 0)
|
||||
paramVales := c.ParamValues()
|
||||
for i, p := range c.ParamNames() {
|
||||
if param == p {
|
||||
result = append(result, paramVales[i])
|
||||
if i >= extractorLimit-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, errParamExtractorValueMissing
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// valuesFromCookie returns a function that extracts values from the named cookie.
|
||||
func valuesFromCookie(name string) ValuesExtractor {
|
||||
return func(c echo.Context) ([]string, error) {
|
||||
cookies := c.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
return nil, errCookieExtractorValueMissing
|
||||
}
|
||||
|
||||
result := make([]string, 0)
|
||||
for i, cookie := range cookies {
|
||||
if name == cookie.Name {
|
||||
result = append(result, cookie.Value)
|
||||
if i >= extractorLimit-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, errCookieExtractorValueMissing
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// valuesFromForm returns a function that extracts values from the form field.
|
||||
func valuesFromForm(name string) ValuesExtractor {
|
||||
return func(c echo.Context) ([]string, error) {
|
||||
if c.Request().Form == nil {
|
||||
_ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does
|
||||
}
|
||||
values := c.Request().Form[name]
|
||||
if len(values) == 0 {
|
||||
return nil, errFormExtractorValueMissing
|
||||
}
|
||||
if len(values) > extractorLimit-1 {
|
||||
values = values[:extractorLimit]
|
||||
}
|
||||
result := append([]string{}, values...)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
179
vendor/github.com/labstack/echo/v4/middleware/key_auth.go
generated
vendored
Normal file
179
vendor/github.com/labstack/echo/v4/middleware/key_auth.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// KeyAuthConfig defines the config for KeyAuth middleware.
|
||||
type KeyAuthConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
|
||||
// to extract key from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>" or "header:<name>:<cut-prefix>"
|
||||
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
|
||||
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
|
||||
// want to cut is `<auth-scheme> ` note the space at the end.
|
||||
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
|
||||
// - "query:<name>"
|
||||
// - "form:<name>"
|
||||
// - "cookie:<name>"
|
||||
// Multiple sources example:
|
||||
// - "header:Authorization,header:X-Api-Key"
|
||||
KeyLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
|
||||
// Validator is a function to validate key.
|
||||
// Required.
|
||||
Validator KeyAuthValidator
|
||||
|
||||
// ErrorHandler defines a function which is executed for an invalid key.
|
||||
// It may be used to define a custom error.
|
||||
ErrorHandler KeyAuthErrorHandler
|
||||
|
||||
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
|
||||
// ignore the error (by returning `nil`).
|
||||
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
|
||||
// In that case you can use ErrorHandler to set a default public key auth value in the request context
|
||||
// and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
|
||||
ContinueOnIgnoredError bool
|
||||
}
|
||||
|
||||
// KeyAuthValidator defines a function to validate KeyAuth credentials.
|
||||
type KeyAuthValidator func(auth string, c echo.Context) (bool, error)
|
||||
|
||||
// KeyAuthErrorHandler defines a function which is executed for an invalid key.
|
||||
type KeyAuthErrorHandler func(err error, c echo.Context) error
|
||||
|
||||
// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
|
||||
type ErrKeyAuthMissing struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
|
||||
var DefaultKeyAuthConfig = KeyAuthConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
KeyLookup: "header:" + echo.HeaderAuthorization,
|
||||
AuthScheme: "Bearer",
|
||||
}
|
||||
|
||||
// Error returns errors text
|
||||
func (e *ErrKeyAuthMissing) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap unwraps error
|
||||
func (e *ErrKeyAuthMissing) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// KeyAuth returns an KeyAuth middleware.
|
||||
//
|
||||
// For valid key it calls the next handler.
|
||||
// For invalid key, it sends "401 - Unauthorized" response.
|
||||
// For missing key, it sends "400 - Bad Request" response.
|
||||
func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
|
||||
c := DefaultKeyAuthConfig
|
||||
c.Validator = fn
|
||||
return KeyAuthWithConfig(c)
|
||||
}
|
||||
|
||||
// KeyAuthWithConfig returns an KeyAuth middleware with config.
|
||||
// See `KeyAuth()`.
|
||||
func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultKeyAuthConfig.Skipper
|
||||
}
|
||||
// Defaults
|
||||
if config.AuthScheme == "" {
|
||||
config.AuthScheme = DefaultKeyAuthConfig.AuthScheme
|
||||
}
|
||||
if config.KeyLookup == "" {
|
||||
config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
|
||||
}
|
||||
if config.Validator == nil {
|
||||
panic("echo: key-auth middleware requires a validator function")
|
||||
}
|
||||
|
||||
extractors, cErr := createExtractors(config.KeyLookup, config.AuthScheme)
|
||||
if cErr != nil {
|
||||
panic(cErr)
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
var lastExtractorErr error
|
||||
var lastValidatorErr error
|
||||
for _, extractor := range extractors {
|
||||
keys, err := extractor(c)
|
||||
if err != nil {
|
||||
lastExtractorErr = err
|
||||
continue
|
||||
}
|
||||
for _, key := range keys {
|
||||
valid, err := config.Validator(key, c)
|
||||
if err != nil {
|
||||
lastValidatorErr = err
|
||||
continue
|
||||
}
|
||||
if valid {
|
||||
return next(c)
|
||||
}
|
||||
lastValidatorErr = errors.New("invalid key")
|
||||
}
|
||||
}
|
||||
|
||||
// we are here only when we did not successfully extract and validate any of keys
|
||||
err := lastValidatorErr
|
||||
if err == nil { // prioritize validator errors over extracting errors
|
||||
// ugly part to preserve backwards compatible errors. someone could rely on them
|
||||
if lastExtractorErr == errQueryExtractorValueMissing {
|
||||
err = errors.New("missing key in the query string")
|
||||
} else if lastExtractorErr == errCookieExtractorValueMissing {
|
||||
err = errors.New("missing key in cookies")
|
||||
} else if lastExtractorErr == errFormExtractorValueMissing {
|
||||
err = errors.New("missing key in the form")
|
||||
} else if lastExtractorErr == errHeaderExtractorValueMissing {
|
||||
err = errors.New("missing key in request header")
|
||||
} else if lastExtractorErr == errHeaderExtractorValueInvalid {
|
||||
err = errors.New("invalid key in the request header")
|
||||
} else {
|
||||
err = lastExtractorErr
|
||||
}
|
||||
err = &ErrKeyAuthMissing{Err: err}
|
||||
}
|
||||
|
||||
if config.ErrorHandler != nil {
|
||||
tmpErr := config.ErrorHandler(err, c)
|
||||
if config.ContinueOnIgnoredError && tmpErr == nil {
|
||||
return next(c)
|
||||
}
|
||||
return tmpErr
|
||||
}
|
||||
if lastValidatorErr != nil { // prioritize validator errors over extracting errors
|
||||
return &echo.HTTPError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "Unauthorized",
|
||||
Internal: lastValidatorErr,
|
||||
}
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
244
vendor/github.com/labstack/echo/v4/middleware/logger.go
generated
vendored
Normal file
244
vendor/github.com/labstack/echo/v4/middleware/logger.go
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/color"
|
||||
"github.com/valyala/fasttemplate"
|
||||
)
|
||||
|
||||
// LoggerConfig defines the config for Logger middleware.
|
||||
type LoggerConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Tags to construct the logger format.
|
||||
//
|
||||
// - time_unix
|
||||
// - time_unix_milli
|
||||
// - time_unix_micro
|
||||
// - time_unix_nano
|
||||
// - time_rfc3339
|
||||
// - time_rfc3339_nano
|
||||
// - time_custom
|
||||
// - id (Request ID)
|
||||
// - remote_ip
|
||||
// - uri
|
||||
// - host
|
||||
// - method
|
||||
// - path
|
||||
// - route
|
||||
// - protocol
|
||||
// - referer
|
||||
// - user_agent
|
||||
// - status
|
||||
// - error
|
||||
// - latency (In nanoseconds)
|
||||
// - latency_human (Human readable)
|
||||
// - bytes_in (Bytes received)
|
||||
// - bytes_out (Bytes sent)
|
||||
// - header:<NAME>
|
||||
// - query:<NAME>
|
||||
// - form:<NAME>
|
||||
// - custom (see CustomTagFunc field)
|
||||
//
|
||||
// Example "${remote_ip} ${status}"
|
||||
//
|
||||
// Optional. Default value DefaultLoggerConfig.Format.
|
||||
Format string `yaml:"format"`
|
||||
|
||||
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
|
||||
CustomTimeFormat string `yaml:"custom_time_format"`
|
||||
|
||||
// CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf.
|
||||
// Make sure that outputted text creates valid JSON string with other logged tags.
|
||||
// Optional.
|
||||
CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error)
|
||||
|
||||
// Output is a writer where logs in JSON format are written.
|
||||
// Optional. Default value os.Stdout.
|
||||
Output io.Writer
|
||||
|
||||
template *fasttemplate.Template
|
||||
colorer *color.Color
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
// DefaultLoggerConfig is the default Logger middleware config.
|
||||
var DefaultLoggerConfig = LoggerConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
|
||||
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
|
||||
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
|
||||
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
|
||||
CustomTimeFormat: "2006-01-02 15:04:05.00000",
|
||||
colorer: color.New(),
|
||||
}
|
||||
|
||||
// Logger returns a middleware that logs HTTP requests.
|
||||
func Logger() echo.MiddlewareFunc {
|
||||
return LoggerWithConfig(DefaultLoggerConfig)
|
||||
}
|
||||
|
||||
// LoggerWithConfig returns a Logger middleware with config.
|
||||
// See: `Logger()`.
|
||||
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultLoggerConfig.Skipper
|
||||
}
|
||||
if config.Format == "" {
|
||||
config.Format = DefaultLoggerConfig.Format
|
||||
}
|
||||
if config.Output == nil {
|
||||
config.Output = DefaultLoggerConfig.Output
|
||||
}
|
||||
|
||||
config.template = fasttemplate.New(config.Format, "${", "}")
|
||||
config.colorer = color.New()
|
||||
config.colorer.SetOutput(config.Output)
|
||||
config.pool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bytes.NewBuffer(make([]byte, 256))
|
||||
},
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
start := time.Now()
|
||||
if err = next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
stop := time.Now()
|
||||
buf := config.pool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer config.pool.Put(buf)
|
||||
|
||||
if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
|
||||
switch tag {
|
||||
case "custom":
|
||||
if config.CustomTagFunc == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return config.CustomTagFunc(c, buf)
|
||||
case "time_unix":
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
case "time_unix_milli":
|
||||
// go 1.17 or later, it supports time#UnixMilli()
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000000, 10))
|
||||
case "time_unix_micro":
|
||||
// go 1.17 or later, it supports time#UnixMicro()
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000, 10))
|
||||
case "time_unix_nano":
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
case "time_rfc3339":
|
||||
return buf.WriteString(time.Now().Format(time.RFC3339))
|
||||
case "time_rfc3339_nano":
|
||||
return buf.WriteString(time.Now().Format(time.RFC3339Nano))
|
||||
case "time_custom":
|
||||
return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
|
||||
case "id":
|
||||
id := req.Header.Get(echo.HeaderXRequestID)
|
||||
if id == "" {
|
||||
id = res.Header().Get(echo.HeaderXRequestID)
|
||||
}
|
||||
return buf.WriteString(id)
|
||||
case "remote_ip":
|
||||
return buf.WriteString(c.RealIP())
|
||||
case "host":
|
||||
return buf.WriteString(req.Host)
|
||||
case "uri":
|
||||
return buf.WriteString(req.RequestURI)
|
||||
case "method":
|
||||
return buf.WriteString(req.Method)
|
||||
case "path":
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
return buf.WriteString(p)
|
||||
case "route":
|
||||
return buf.WriteString(c.Path())
|
||||
case "protocol":
|
||||
return buf.WriteString(req.Proto)
|
||||
case "referer":
|
||||
return buf.WriteString(req.Referer())
|
||||
case "user_agent":
|
||||
return buf.WriteString(req.UserAgent())
|
||||
case "status":
|
||||
n := res.Status
|
||||
s := config.colorer.Green(n)
|
||||
switch {
|
||||
case n >= 500:
|
||||
s = config.colorer.Red(n)
|
||||
case n >= 400:
|
||||
s = config.colorer.Yellow(n)
|
||||
case n >= 300:
|
||||
s = config.colorer.Cyan(n)
|
||||
}
|
||||
return buf.WriteString(s)
|
||||
case "error":
|
||||
if err != nil {
|
||||
// Error may contain invalid JSON e.g. `"`
|
||||
b, _ := json.Marshal(err.Error())
|
||||
b = b[1 : len(b)-1]
|
||||
return buf.Write(b)
|
||||
}
|
||||
case "latency":
|
||||
l := stop.Sub(start)
|
||||
return buf.WriteString(strconv.FormatInt(int64(l), 10))
|
||||
case "latency_human":
|
||||
return buf.WriteString(stop.Sub(start).String())
|
||||
case "bytes_in":
|
||||
cl := req.Header.Get(echo.HeaderContentLength)
|
||||
if cl == "" {
|
||||
cl = "0"
|
||||
}
|
||||
return buf.WriteString(cl)
|
||||
case "bytes_out":
|
||||
return buf.WriteString(strconv.FormatInt(res.Size, 10))
|
||||
default:
|
||||
switch {
|
||||
case strings.HasPrefix(tag, "header:"):
|
||||
return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
|
||||
case strings.HasPrefix(tag, "query:"):
|
||||
return buf.Write([]byte(c.QueryParam(tag[6:])))
|
||||
case strings.HasPrefix(tag, "form:"):
|
||||
return buf.Write([]byte(c.FormValue(tag[5:])))
|
||||
case strings.HasPrefix(tag, "cookie:"):
|
||||
cookie, err := c.Cookie(tag[7:])
|
||||
if err == nil {
|
||||
return buf.Write([]byte(cookie.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if config.Output == nil {
|
||||
_, err = c.Logger().Output().Write(buf.Bytes())
|
||||
return
|
||||
}
|
||||
_, err = config.Output.Write(buf.Bytes())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
91
vendor/github.com/labstack/echo/v4/middleware/method_override.go
generated
vendored
Normal file
91
vendor/github.com/labstack/echo/v4/middleware/method_override.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// MethodOverrideConfig defines the config for MethodOverride middleware.
|
||||
type MethodOverrideConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Getter is a function that gets overridden method from the request.
|
||||
// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
|
||||
Getter MethodOverrideGetter
|
||||
}
|
||||
|
||||
// MethodOverrideGetter is a function that gets overridden method from the request
|
||||
type MethodOverrideGetter func(echo.Context) string
|
||||
|
||||
// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
|
||||
var DefaultMethodOverrideConfig = MethodOverrideConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
|
||||
}
|
||||
|
||||
// MethodOverride returns a MethodOverride middleware.
|
||||
// MethodOverride middleware checks for the overridden method from the request and
|
||||
// uses it instead of the original method.
|
||||
//
|
||||
// For security reasons, only `POST` method can be overridden.
|
||||
func MethodOverride() echo.MiddlewareFunc {
|
||||
return MethodOverrideWithConfig(DefaultMethodOverrideConfig)
|
||||
}
|
||||
|
||||
// MethodOverrideWithConfig returns a MethodOverride middleware with config.
|
||||
// See: `MethodOverride()`.
|
||||
func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultMethodOverrideConfig.Skipper
|
||||
}
|
||||
if config.Getter == nil {
|
||||
config.Getter = DefaultMethodOverrideConfig.Getter
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
if req.Method == http.MethodPost {
|
||||
m := config.Getter(c)
|
||||
if m != "" {
|
||||
req.Method = m
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from
|
||||
// the request header.
|
||||
func MethodFromHeader(header string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.Request().Header.Get(header)
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the
|
||||
// form parameter.
|
||||
func MethodFromForm(param string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.FormValue(param)
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from
|
||||
// the query parameter.
|
||||
func MethodFromQuery(param string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.QueryParam(param)
|
||||
}
|
||||
}
|
||||
90
vendor/github.com/labstack/echo/v4/middleware/middleware.go
generated
vendored
Normal file
90
vendor/github.com/labstack/echo/v4/middleware/middleware.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Skipper defines a function to skip middleware. Returning true skips processing
|
||||
// the middleware.
|
||||
type Skipper func(c echo.Context) bool
|
||||
|
||||
// BeforeFunc defines a function which is executed just before the middleware.
|
||||
type BeforeFunc func(c echo.Context)
|
||||
|
||||
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
|
||||
groups := pattern.FindAllStringSubmatch(input, -1)
|
||||
if groups == nil {
|
||||
return nil
|
||||
}
|
||||
values := groups[0][1:]
|
||||
replace := make([]string, 2*len(values))
|
||||
for i, v := range values {
|
||||
j := 2 * i
|
||||
replace[j] = "$" + strconv.Itoa(i+1)
|
||||
replace[j+1] = v
|
||||
}
|
||||
return strings.NewReplacer(replace...)
|
||||
}
|
||||
|
||||
func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string {
|
||||
// Initialize
|
||||
rulesRegex := map[*regexp.Regexp]string{}
|
||||
for k, v := range rewrite {
|
||||
k = regexp.QuoteMeta(k)
|
||||
k = strings.ReplaceAll(k, `\*`, "(.*?)")
|
||||
if strings.HasPrefix(k, `\^`) {
|
||||
k = strings.ReplaceAll(k, `\^`, "^")
|
||||
}
|
||||
k = k + "$"
|
||||
rulesRegex[regexp.MustCompile(k)] = v
|
||||
}
|
||||
return rulesRegex
|
||||
}
|
||||
|
||||
func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error {
|
||||
if len(rewriteRegex) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Depending on how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
|
||||
// We only want to use path part for rewriting and therefore trim prefix if it exists
|
||||
rawURI := req.RequestURI
|
||||
if rawURI != "" && rawURI[0] != '/' {
|
||||
prefix := ""
|
||||
if req.URL.Scheme != "" {
|
||||
prefix = req.URL.Scheme + "://"
|
||||
}
|
||||
if req.URL.Host != "" {
|
||||
prefix += req.URL.Host // host or host:port
|
||||
}
|
||||
if prefix != "" {
|
||||
rawURI = strings.TrimPrefix(rawURI, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range rewriteRegex {
|
||||
if replacer := captureTokens(k, rawURI); replacer != nil {
|
||||
url, err := req.URL.Parse(replacer.Replace(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.URL = url
|
||||
|
||||
return nil // rewrite only once
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultSkipper returns false which processes the middleware.
|
||||
func DefaultSkipper(echo.Context) bool {
|
||||
return false
|
||||
}
|
||||
421
vendor/github.com/labstack/echo/v4/middleware/proxy.go
generated
vendored
Normal file
421
vendor/github.com/labstack/echo/v4/middleware/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// TODO: Handle TLS proxy
|
||||
|
||||
// ProxyConfig defines the config for Proxy middleware.
|
||||
type ProxyConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Balancer defines a load balancing technique.
|
||||
// Required.
|
||||
Balancer ProxyBalancer
|
||||
|
||||
// RetryCount defines the number of times a failed proxied request should be retried
|
||||
// using the next available ProxyTarget. Defaults to 0, meaning requests are never retried.
|
||||
RetryCount int
|
||||
|
||||
// RetryFilter defines a function used to determine if a failed request to a
|
||||
// ProxyTarget should be retried. The RetryFilter will only be called when the number
|
||||
// of previous retries is less than RetryCount. If the function returns true, the
|
||||
// request will be retried. The provided error indicates the reason for the request
|
||||
// failure. When the ProxyTarget is unavailable, the error will be an instance of
|
||||
// echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error
|
||||
// will indicate an internal error in the Proxy middleware. When a RetryFilter is not
|
||||
// specified, all requests that fail with http.StatusBadGateway will be retried. A custom
|
||||
// RetryFilter can be provided to only retry specific requests. Note that RetryFilter is
|
||||
// only called when the request to the target fails, or an internal error in the Proxy
|
||||
// middleware has occurred. Successful requests that return a non-200 response code cannot
|
||||
// be retried.
|
||||
RetryFilter func(c echo.Context, e error) bool
|
||||
|
||||
// ErrorHandler defines a function which can be used to return custom errors from
|
||||
// the Proxy middleware. ErrorHandler is only invoked when there has been
|
||||
// either an internal error in the Proxy middleware or the ProxyTarget is
|
||||
// unavailable. Due to the way requests are proxied, ErrorHandler is not invoked
|
||||
// when a ProxyTarget returns a non-200 response. In these cases, the response
|
||||
// is already written so errors cannot be modified. ErrorHandler is only
|
||||
// invoked after all retry attempts have been exhausted.
|
||||
ErrorHandler func(c echo.Context, err error) error
|
||||
|
||||
// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
|
||||
// retrieved by index e.g. $1, $2 and so on.
|
||||
// Examples:
|
||||
// "/old": "/new",
|
||||
// "/api/*": "/$1",
|
||||
// "/js/*": "/public/javascripts/$1",
|
||||
// "/users/*/orders/*": "/user/$1/order/$2",
|
||||
Rewrite map[string]string
|
||||
|
||||
// RegexRewrite defines rewrite rules using regexp.Rexexp with captures
|
||||
// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
|
||||
// Example:
|
||||
// "^/old/[0.9]+/": "/new",
|
||||
// "^/api/.+?/(.*)": "/v2/$1",
|
||||
RegexRewrite map[*regexp.Regexp]string
|
||||
|
||||
// Context key to store selected ProxyTarget into context.
|
||||
// Optional. Default value "target".
|
||||
ContextKey string
|
||||
|
||||
// To customize the transport to remote.
|
||||
// Examples: If custom TLS certificates are required.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// ModifyResponse defines function to modify response from ProxyTarget.
|
||||
ModifyResponse func(*http.Response) error
|
||||
}
|
||||
|
||||
// ProxyTarget defines the upstream target.
|
||||
type ProxyTarget struct {
|
||||
Name string
|
||||
URL *url.URL
|
||||
Meta echo.Map
|
||||
}
|
||||
|
||||
// ProxyBalancer defines an interface to implement a load balancing technique.
|
||||
type ProxyBalancer interface {
|
||||
AddTarget(*ProxyTarget) bool
|
||||
RemoveTarget(string) bool
|
||||
Next(echo.Context) *ProxyTarget
|
||||
}
|
||||
|
||||
// TargetProvider defines an interface that gives the opportunity for balancer
|
||||
// to return custom errors when selecting target.
|
||||
type TargetProvider interface {
|
||||
NextTarget(echo.Context) (*ProxyTarget, error)
|
||||
}
|
||||
|
||||
type commonBalancer struct {
|
||||
targets []*ProxyTarget
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// RandomBalancer implements a random load balancing technique.
|
||||
type randomBalancer struct {
|
||||
commonBalancer
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
// RoundRobinBalancer implements a round-robin load balancing technique.
|
||||
type roundRobinBalancer struct {
|
||||
commonBalancer
|
||||
// tracking the index on `targets` slice for the next `*ProxyTarget` to be used
|
||||
i int
|
||||
}
|
||||
|
||||
// DefaultProxyConfig is the default Proxy middleware config.
|
||||
var DefaultProxyConfig = ProxyConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
ContextKey: "target",
|
||||
}
|
||||
|
||||
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
in, _, err := c.Response().Hijack()
|
||||
if err != nil {
|
||||
c.Set("_error", fmt.Errorf("proxy raw, hijack error=%w, url=%s", err, t.URL))
|
||||
return
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := net.Dial("tcp", t.URL.Host)
|
||||
if err != nil {
|
||||
c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", err, t.URL)))
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write header
|
||||
err = r.Write(out)
|
||||
if err != nil {
|
||||
c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", err, t.URL)))
|
||||
return
|
||||
}
|
||||
|
||||
errCh := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err = io.Copy(dst, src)
|
||||
errCh <- err
|
||||
}
|
||||
|
||||
go cp(out, in)
|
||||
go cp(in, out)
|
||||
err = <-errCh
|
||||
if err != nil && err != io.EOF {
|
||||
c.Set("_error", fmt.Errorf("proxy raw, copy body error=%w, url=%s", err, t.URL))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NewRandomBalancer returns a random proxy balancer.
|
||||
func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
|
||||
b := randomBalancer{}
|
||||
b.targets = targets
|
||||
b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
|
||||
return &b
|
||||
}
|
||||
|
||||
// NewRoundRobinBalancer returns a round-robin proxy balancer.
|
||||
func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
|
||||
b := roundRobinBalancer{}
|
||||
b.targets = targets
|
||||
return &b
|
||||
}
|
||||
|
||||
// AddTarget adds an upstream target to the list and returns `true`.
|
||||
//
|
||||
// However, if a target with the same name already exists then the operation is aborted returning `false`.
|
||||
func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
for _, t := range b.targets {
|
||||
if t.Name == target.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
b.targets = append(b.targets, target)
|
||||
return true
|
||||
}
|
||||
|
||||
// RemoveTarget removes an upstream target from the list by name.
|
||||
//
|
||||
// Returns `true` on success, `false` if no target with the name is found.
|
||||
func (b *commonBalancer) RemoveTarget(name string) bool {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
for i, t := range b.targets {
|
||||
if t.Name == name {
|
||||
b.targets = append(b.targets[:i], b.targets[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Next randomly returns an upstream target.
|
||||
//
|
||||
// Note: `nil` is returned in case upstream target list is empty.
|
||||
func (b *randomBalancer) Next(c echo.Context) *ProxyTarget {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
if len(b.targets) == 0 {
|
||||
return nil
|
||||
} else if len(b.targets) == 1 {
|
||||
return b.targets[0]
|
||||
}
|
||||
return b.targets[b.random.Intn(len(b.targets))]
|
||||
}
|
||||
|
||||
// Next returns an upstream target using round-robin technique. In the case
|
||||
// where a previously failed request is being retried, the round-robin
|
||||
// balancer will attempt to use the next target relative to the original
|
||||
// request. If the list of targets held by the balancer is modified while a
|
||||
// failed request is being retried, it is possible that the balancer will
|
||||
// return the original failed target.
|
||||
//
|
||||
// Note: `nil` is returned in case upstream target list is empty.
|
||||
func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
if len(b.targets) == 0 {
|
||||
return nil
|
||||
} else if len(b.targets) == 1 {
|
||||
return b.targets[0]
|
||||
}
|
||||
|
||||
var i int
|
||||
const lastIdxKey = "_round_robin_last_index"
|
||||
// This request is a retry, start from the index of the previous
|
||||
// target to ensure we don't attempt to retry the request with
|
||||
// the same failed target
|
||||
if c.Get(lastIdxKey) != nil {
|
||||
i = c.Get(lastIdxKey).(int)
|
||||
i++
|
||||
if i >= len(b.targets) {
|
||||
i = 0
|
||||
}
|
||||
} else {
|
||||
// This is a first time request, use the global index
|
||||
if b.i >= len(b.targets) {
|
||||
b.i = 0
|
||||
}
|
||||
i = b.i
|
||||
b.i++
|
||||
}
|
||||
|
||||
c.Set(lastIdxKey, i)
|
||||
return b.targets[i]
|
||||
}
|
||||
|
||||
// Proxy returns a Proxy middleware.
|
||||
//
|
||||
// Proxy middleware forwards the request to upstream server using a configured load balancing technique.
|
||||
func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
|
||||
c := DefaultProxyConfig
|
||||
c.Balancer = balancer
|
||||
return ProxyWithConfig(c)
|
||||
}
|
||||
|
||||
// ProxyWithConfig returns a Proxy middleware with config.
|
||||
// See: `Proxy()`
|
||||
func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
|
||||
if config.Balancer == nil {
|
||||
panic("echo: proxy middleware requires balancer")
|
||||
}
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultProxyConfig.Skipper
|
||||
}
|
||||
if config.RetryFilter == nil {
|
||||
config.RetryFilter = func(c echo.Context, e error) bool {
|
||||
if httpErr, ok := e.(*echo.HTTPError); ok {
|
||||
return httpErr.Code == http.StatusBadGateway
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
if config.ErrorHandler == nil {
|
||||
config.ErrorHandler = func(c echo.Context, err error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
provider, isTargetProvider := config.Balancer.(TargetProvider)
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
if err := rewriteURL(config.RegexRewrite, req); err != nil {
|
||||
return config.ErrorHandler(c, err)
|
||||
}
|
||||
|
||||
// Fix header
|
||||
// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.
|
||||
// However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor.
|
||||
if req.Header.Get(echo.HeaderXRealIP) == "" || c.Echo().IPExtractor != nil {
|
||||
req.Header.Set(echo.HeaderXRealIP, c.RealIP())
|
||||
}
|
||||
if req.Header.Get(echo.HeaderXForwardedProto) == "" {
|
||||
req.Header.Set(echo.HeaderXForwardedProto, c.Scheme())
|
||||
}
|
||||
if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy.
|
||||
req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
|
||||
}
|
||||
|
||||
retries := config.RetryCount
|
||||
for {
|
||||
var tgt *ProxyTarget
|
||||
var err error
|
||||
if isTargetProvider {
|
||||
tgt, err = provider.NextTarget(c)
|
||||
if err != nil {
|
||||
return config.ErrorHandler(c, err)
|
||||
}
|
||||
} else {
|
||||
tgt = config.Balancer.Next(c)
|
||||
}
|
||||
|
||||
c.Set(config.ContextKey, tgt)
|
||||
|
||||
//If retrying a failed request, clear any previous errors from
|
||||
//context here so that balancers have the option to check for
|
||||
//errors that occurred using previous target
|
||||
if retries < config.RetryCount {
|
||||
c.Set("_error", nil)
|
||||
}
|
||||
|
||||
// This is needed for ProxyConfig.ModifyResponse and/or ProxyConfig.Transport to be able to process the Request
|
||||
// that Balancer may have replaced with c.SetRequest.
|
||||
req = c.Request()
|
||||
|
||||
// Proxy
|
||||
switch {
|
||||
case c.IsWebSocket():
|
||||
proxyRaw(tgt, c).ServeHTTP(res, req)
|
||||
default: // even SSE requests
|
||||
proxyHTTP(tgt, c, config).ServeHTTP(res, req)
|
||||
}
|
||||
|
||||
err, hasError := c.Get("_error").(error)
|
||||
if !hasError {
|
||||
return nil
|
||||
}
|
||||
|
||||
retry := retries > 0 && config.RetryFilter(c, err)
|
||||
if !retry {
|
||||
return config.ErrorHandler(c, err)
|
||||
}
|
||||
|
||||
retries--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatusCodeContextCanceled is a custom HTTP status code for situations
|
||||
// where a client unexpectedly closed the connection to the server.
|
||||
// As there is no standard error code for "client closed connection", but
|
||||
// various well-known HTTP clients and server implement this HTTP code we use
|
||||
// 499 too instead of the more problematic 5xx, which does not allow to detect this situation
|
||||
const StatusCodeContextCanceled = 499
|
||||
|
||||
func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
|
||||
proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
|
||||
proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
|
||||
desc := tgt.URL.String()
|
||||
if tgt.Name != "" {
|
||||
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
|
||||
}
|
||||
// If the client canceled the request (usually by closing the connection), we can report a
|
||||
// client error (4xx) instead of a server error (5xx) to correctly identify the situation.
|
||||
// The Go standard library (at of late 2020) wraps the exported, standard
|
||||
// context.Canceled error with unexported garbage value requiring a substring check, see
|
||||
// https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430
|
||||
if err == context.Canceled || strings.Contains(err.Error(), "operation was canceled") {
|
||||
httpError := echo.NewHTTPError(StatusCodeContextCanceled, fmt.Sprintf("client closed connection: %v", err))
|
||||
httpError.Internal = err
|
||||
c.Set("_error", httpError)
|
||||
} else {
|
||||
httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err))
|
||||
httpError.Internal = err
|
||||
c.Set("_error", httpError)
|
||||
}
|
||||
}
|
||||
proxy.Transport = config.Transport
|
||||
proxy.ModifyResponse = config.ModifyResponse
|
||||
return proxy
|
||||
}
|
||||
267
vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
generated
vendored
Normal file
267
vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// RateLimiterStore is the interface to be implemented by custom stores.
|
||||
type RateLimiterStore interface {
|
||||
// Stores for the rate limiter have to implement the Allow method
|
||||
Allow(identifier string) (bool, error)
|
||||
}
|
||||
|
||||
// RateLimiterConfig defines the configuration for the rate limiter
|
||||
type 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
|
||||
type Extractor func(context echo.Context) (string, error)
|
||||
|
||||
// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
|
||||
var ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
|
||||
|
||||
// ErrExtractorError denotes an error raised when extractor function is unsuccessful
|
||||
var 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
|
||||
type RateLimiterMemoryStore struct {
|
||||
visitors map[string]*Visitor
|
||||
mutex sync.Mutex
|
||||
rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
|
||||
|
||||
burst int
|
||||
expiresIn time.Duration
|
||||
lastCleanup time.Time
|
||||
|
||||
timeNow func() time.Time
|
||||
}
|
||||
|
||||
// Visitor signifies a unique user's limiter details
|
||||
type Visitor struct {
|
||||
*rate.Limiter
|
||||
lastSeen time.Time
|
||||
}
|
||||
|
||||
/*
|
||||
NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
|
||||
the provided rate (as req/s).
|
||||
for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
|
||||
|
||||
Burst and ExpiresIn will be set to default values.
|
||||
|
||||
Note that if the provided rate is a float number and Burst is zero, Burst will be treated as the rounded down value of the rate.
|
||||
|
||||
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 rounded down value of
|
||||
the configured rate if not provided or set to 0.
|
||||
|
||||
The build-in memory store is usually capable for modest loads. For higher loads other
|
||||
store implementations should be considered.
|
||||
|
||||
Characteristics:
|
||||
* Concurrency above 100 parallel requests may causes measurable lock contention
|
||||
* A high number of different IP addresses (above 16000) may be impacted by the internally used Go map
|
||||
* A high number of requests from a single IP address may cause lock contention
|
||||
|
||||
Example:
|
||||
|
||||
limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig(
|
||||
middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minute},
|
||||
)
|
||||
*/
|
||||
func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore) {
|
||||
store = &RateLimiterMemoryStore{}
|
||||
|
||||
store.rate = config.Rate
|
||||
store.burst = config.Burst
|
||||
store.expiresIn = config.ExpiresIn
|
||||
if config.ExpiresIn == 0 {
|
||||
store.expiresIn = DefaultRateLimiterMemoryStoreConfig.ExpiresIn
|
||||
}
|
||||
if config.Burst == 0 {
|
||||
store.burst = int(config.Rate)
|
||||
}
|
||||
store.visitors = make(map[string]*Visitor)
|
||||
store.timeNow = time.Now
|
||||
store.lastCleanup = store.timeNow()
|
||||
return
|
||||
}
|
||||
|
||||
// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore
|
||||
type RateLimiterMemoryStoreConfig struct {
|
||||
Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
|
||||
Burst int // Burst is maximum number of requests to pass at the same moment. It 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
|
||||
}
|
||||
now := store.timeNow()
|
||||
limiter.lastSeen = now
|
||||
if now.Sub(store.lastCleanup) > store.expiresIn {
|
||||
store.cleanupStaleVisitors()
|
||||
}
|
||||
store.mutex.Unlock()
|
||||
return limiter.AllowN(store.timeNow(), 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 store.timeNow().Sub(visitor.lastSeen) > store.expiresIn {
|
||||
delete(store.visitors, id)
|
||||
}
|
||||
}
|
||||
store.lastCleanup = store.timeNow()
|
||||
}
|
||||
133
vendor/github.com/labstack/echo/v4/middleware/recover.go
generated
vendored
Normal file
133
vendor/github.com/labstack/echo/v4/middleware/recover.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
// LogErrorFunc defines a function for custom logging in the middleware.
|
||||
type LogErrorFunc func(c echo.Context, err error, stack []byte) error
|
||||
|
||||
// RecoverConfig defines the config for Recover middleware.
|
||||
type RecoverConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Size of the stack to be printed.
|
||||
// Optional. Default value 4KB.
|
||||
StackSize int `yaml:"stack_size"`
|
||||
|
||||
// DisableStackAll disables formatting stack traces of all other goroutines
|
||||
// into buffer after the trace for the current goroutine.
|
||||
// Optional. Default value false.
|
||||
DisableStackAll bool `yaml:"disable_stack_all"`
|
||||
|
||||
// DisablePrintStack disables printing stack trace.
|
||||
// Optional. Default value as false.
|
||||
DisablePrintStack bool `yaml:"disable_print_stack"`
|
||||
|
||||
// LogLevel is log level to printing stack trace.
|
||||
// Optional. Default value 0 (Print).
|
||||
LogLevel log.Lvl
|
||||
|
||||
// LogErrorFunc defines a function for custom logging in the middleware.
|
||||
// If it's set you don't need to provide LogLevel for config.
|
||||
// If this function returns nil, the centralized HTTPErrorHandler will not be called.
|
||||
LogErrorFunc LogErrorFunc
|
||||
|
||||
// DisableErrorHandler disables the call to centralized HTTPErrorHandler.
|
||||
// The recovered error is then passed back to upstream middleware, instead of swallowing the error.
|
||||
// Optional. Default value false.
|
||||
DisableErrorHandler bool `yaml:"disable_error_handler"`
|
||||
}
|
||||
|
||||
// DefaultRecoverConfig is the default Recover middleware config.
|
||||
var DefaultRecoverConfig = RecoverConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
StackSize: 4 << 10, // 4 KB
|
||||
DisableStackAll: false,
|
||||
DisablePrintStack: false,
|
||||
LogLevel: 0,
|
||||
LogErrorFunc: nil,
|
||||
DisableErrorHandler: false,
|
||||
}
|
||||
|
||||
// Recover returns a middleware which recovers from panics anywhere in the chain
|
||||
// and handles the control to the centralized HTTPErrorHandler.
|
||||
func Recover() echo.MiddlewareFunc {
|
||||
return RecoverWithConfig(DefaultRecoverConfig)
|
||||
}
|
||||
|
||||
// RecoverWithConfig returns a Recover middleware with config.
|
||||
// See: `Recover()`.
|
||||
func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultRecoverConfig.Skipper
|
||||
}
|
||||
if config.StackSize == 0 {
|
||||
config.StackSize = DefaultRecoverConfig.StackSize
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (returnErr error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r == http.ErrAbortHandler {
|
||||
panic(r)
|
||||
}
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
var stack []byte
|
||||
var length int
|
||||
|
||||
if !config.DisablePrintStack {
|
||||
stack = make([]byte, config.StackSize)
|
||||
length = runtime.Stack(stack, !config.DisableStackAll)
|
||||
stack = stack[:length]
|
||||
}
|
||||
|
||||
if config.LogErrorFunc != nil {
|
||||
err = config.LogErrorFunc(c, err, stack)
|
||||
} else if !config.DisablePrintStack {
|
||||
msg := fmt.Sprintf("[PANIC RECOVER] %v %s\n", err, stack[:length])
|
||||
switch config.LogLevel {
|
||||
case log.DEBUG:
|
||||
c.Logger().Debug(msg)
|
||||
case log.INFO:
|
||||
c.Logger().Info(msg)
|
||||
case log.WARN:
|
||||
c.Logger().Warn(msg)
|
||||
case log.ERROR:
|
||||
c.Logger().Error(msg)
|
||||
case log.OFF:
|
||||
// None.
|
||||
default:
|
||||
c.Logger().Print(msg)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && !config.DisableErrorHandler {
|
||||
c.Error(err)
|
||||
} else {
|
||||
returnErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
155
vendor/github.com/labstack/echo/v4/middleware/redirect.go
generated
vendored
Normal file
155
vendor/github.com/labstack/echo/v4/middleware/redirect.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// RedirectConfig defines the config for Redirect middleware.
|
||||
type RedirectConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional. Default value http.StatusMovedPermanently.
|
||||
Code int `yaml:"code"`
|
||||
}
|
||||
|
||||
// redirectLogic represents a function that given a scheme, host and uri
|
||||
// can both: 1) determine if redirect is needed (will set ok accordingly) and
|
||||
// 2) return the appropriate redirect url.
|
||||
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
|
||||
|
||||
const www = "www."
|
||||
|
||||
// DefaultRedirectConfig is the default Redirect middleware config.
|
||||
var DefaultRedirectConfig = RedirectConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Code: http.StatusMovedPermanently,
|
||||
}
|
||||
|
||||
// HTTPSRedirect redirects http requests to https.
|
||||
// For example, http://labstack.com will be redirect to https://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSRedirect())`
|
||||
func HTTPSRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSRedirect()`.
|
||||
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (bool, string) {
|
||||
if scheme != "https" {
|
||||
return true, "https://" + host + uri
|
||||
}
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
// HTTPSWWWRedirect redirects http requests to https www.
|
||||
// For example, http://labstack.com will be redirect to https://www.labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSWWWRedirect())`
|
||||
func HTTPSWWWRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSWWWRedirect()`.
|
||||
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (bool, string) {
|
||||
if scheme != "https" && !strings.HasPrefix(host, www) {
|
||||
return true, "https://www." + host + uri
|
||||
}
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
// HTTPSNonWWWRedirect redirects http requests to https non www.
|
||||
// For example, http://www.labstack.com will be redirect to https://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSNonWWWRedirect())`
|
||||
func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSNonWWWRedirect()`.
|
||||
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if scheme != "https" {
|
||||
host = strings.TrimPrefix(host, www)
|
||||
return true, "https://" + host + uri
|
||||
}
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
// WWWRedirect redirects non www requests to www.
|
||||
// For example, http://labstack.com will be redirect to http://www.labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(WWWRedirect())`
|
||||
func WWWRedirect() echo.MiddlewareFunc {
|
||||
return WWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `WWWRedirect()`.
|
||||
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (bool, string) {
|
||||
if !strings.HasPrefix(host, www) {
|
||||
return true, scheme + "://www." + host + uri
|
||||
}
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
// NonWWWRedirect redirects www requests to non www.
|
||||
// For example, http://www.labstack.com will be redirect to http://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(NonWWWRedirect())`
|
||||
func NonWWWRedirect() echo.MiddlewareFunc {
|
||||
return NonWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `NonWWWRedirect()`.
|
||||
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (bool, string) {
|
||||
if strings.HasPrefix(host, www) {
|
||||
return true, scheme + "://" + host[4:] + uri
|
||||
}
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultRedirectConfig.Skipper
|
||||
}
|
||||
if config.Code == 0 {
|
||||
config.Code = DefaultRedirectConfig.Code
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req, scheme := c.Request(), c.Scheme()
|
||||
host := req.Host
|
||||
if ok, url := cb(scheme, host, req.RequestURI); ok {
|
||||
return c.Redirect(config.Code, url)
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
75
vendor/github.com/labstack/echo/v4/middleware/request_id.go
generated
vendored
Normal file
75
vendor/github.com/labstack/echo/v4/middleware/request_id.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// RequestIDConfig defines the config for RequestID middleware.
|
||||
type RequestIDConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Generator defines a function to generate an ID.
|
||||
// Optional. Defaults to generator for random string of length 32.
|
||||
Generator func() string
|
||||
|
||||
// RequestIDHandler defines a function which is executed for a request id.
|
||||
RequestIDHandler func(echo.Context, string)
|
||||
|
||||
// TargetHeader defines what header to look for to populate the id
|
||||
TargetHeader string
|
||||
}
|
||||
|
||||
// DefaultRequestIDConfig is the default RequestID middleware config.
|
||||
var DefaultRequestIDConfig = RequestIDConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Generator: generator,
|
||||
TargetHeader: echo.HeaderXRequestID,
|
||||
}
|
||||
|
||||
// RequestID returns a X-Request-ID middleware.
|
||||
func RequestID() echo.MiddlewareFunc {
|
||||
return RequestIDWithConfig(DefaultRequestIDConfig)
|
||||
}
|
||||
|
||||
// RequestIDWithConfig returns a X-Request-ID middleware with config.
|
||||
func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultRequestIDConfig.Skipper
|
||||
}
|
||||
if config.Generator == nil {
|
||||
config.Generator = generator
|
||||
}
|
||||
if config.TargetHeader == "" {
|
||||
config.TargetHeader = echo.HeaderXRequestID
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
rid := req.Header.Get(config.TargetHeader)
|
||||
if rid == "" {
|
||||
rid = config.Generator()
|
||||
}
|
||||
res.Header().Set(config.TargetHeader, rid)
|
||||
if config.RequestIDHandler != nil {
|
||||
config.RequestIDHandler(c, rid)
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generator() string {
|
||||
return randomString(32)
|
||||
}
|
||||
391
vendor/github.com/labstack/echo/v4/middleware/request_logger.go
generated
vendored
Normal file
391
vendor/github.com/labstack/echo/v4/middleware/request_logger.go
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Example for `slog` https://pkg.go.dev/log/slog
|
||||
// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
// LogStatus: true,
|
||||
// LogURI: true,
|
||||
// LogError: true,
|
||||
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
// if v.Error == nil {
|
||||
// logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST",
|
||||
// slog.String("uri", v.URI),
|
||||
// slog.Int("status", v.Status),
|
||||
// )
|
||||
// } else {
|
||||
// logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR",
|
||||
// slog.String("uri", v.URI),
|
||||
// slog.Int("status", v.Status),
|
||||
// slog.String("err", v.Error.Error()),
|
||||
// )
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// }))
|
||||
//
|
||||
// Example for `fmt.Printf`
|
||||
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
// LogStatus: true,
|
||||
// LogURI: true,
|
||||
// LogError: true,
|
||||
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
// if v.Error == nil {
|
||||
// fmt.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status)
|
||||
// } else {
|
||||
// fmt.Printf("REQUEST_ERROR: uri: %v, status: %v, err: %v\n", v.URI, v.Status, v.Error)
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// }))
|
||||
//
|
||||
// Example for Zerolog (https://github.com/rs/zerolog)
|
||||
// logger := zerolog.New(os.Stdout)
|
||||
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
// LogURI: true,
|
||||
// LogStatus: true,
|
||||
// LogError: true,
|
||||
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
// if v.Error == nil {
|
||||
// logger.Info().
|
||||
// Str("URI", v.URI).
|
||||
// Int("status", v.Status).
|
||||
// Msg("request")
|
||||
// } else {
|
||||
// logger.Error().
|
||||
// Err(v.Error).
|
||||
// Str("URI", v.URI).
|
||||
// Int("status", v.Status).
|
||||
// Msg("request error")
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// }))
|
||||
//
|
||||
// Example for Zap (https://github.com/uber-go/zap)
|
||||
// logger, _ := zap.NewProduction()
|
||||
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
// LogURI: true,
|
||||
// LogStatus: true,
|
||||
// LogError: true,
|
||||
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
// if v.Error == nil {
|
||||
// logger.Info("request",
|
||||
// zap.String("URI", v.URI),
|
||||
// zap.Int("status", v.Status),
|
||||
// )
|
||||
// } else {
|
||||
// logger.Error("request error",
|
||||
// zap.String("URI", v.URI),
|
||||
// zap.Int("status", v.Status),
|
||||
// zap.Error(v.Error),
|
||||
// )
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// }))
|
||||
//
|
||||
// Example for Logrus (https://github.com/sirupsen/logrus)
|
||||
// log := logrus.New()
|
||||
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
// LogURI: true,
|
||||
// LogStatus: true,
|
||||
// LogError: true,
|
||||
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
// if v.Error == nil {
|
||||
// log.WithFields(logrus.Fields{
|
||||
// "URI": v.URI,
|
||||
// "status": v.Status,
|
||||
// }).Info("request")
|
||||
// } else {
|
||||
// log.WithFields(logrus.Fields{
|
||||
// "URI": v.URI,
|
||||
// "status": v.Status,
|
||||
// "error": v.Error,
|
||||
// }).Error("request error")
|
||||
// }
|
||||
// return nil
|
||||
// },
|
||||
// }))
|
||||
|
||||
// RequestLoggerConfig is configuration for Request Logger middleware.
|
||||
type RequestLoggerConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// BeforeNextFunc defines a function that is called before next middleware or handler is called in chain.
|
||||
BeforeNextFunc func(c echo.Context)
|
||||
// LogValuesFunc defines a function that is called with values extracted by logger from request/response.
|
||||
// Mandatory.
|
||||
LogValuesFunc func(c echo.Context, v RequestLoggerValues) error
|
||||
|
||||
// HandleError instructs logger to call global error handler when next middleware/handler returns an error.
|
||||
// This is useful when you have custom error handler that can decide to use different status codes.
|
||||
//
|
||||
// A side-effect of calling global error handler is that now Response has been committed and sent to the client
|
||||
// and middlewares up in chain can not change Response status code or response body.
|
||||
HandleError bool
|
||||
|
||||
// LogLatency instructs logger to record duration it took to execute rest of the handler chain (next(c) call).
|
||||
LogLatency bool
|
||||
// LogProtocol instructs logger to extract request protocol (i.e. `HTTP/1.1` or `HTTP/2`)
|
||||
LogProtocol bool
|
||||
// LogRemoteIP instructs logger to extract request remote IP. See `echo.Context.RealIP()` for implementation details.
|
||||
LogRemoteIP bool
|
||||
// LogHost instructs logger to extract request host value (i.e. `example.com`)
|
||||
LogHost bool
|
||||
// LogMethod instructs logger to extract request method value (i.e. `GET` etc)
|
||||
LogMethod bool
|
||||
// LogURI instructs logger to extract request URI (i.e. `/list?lang=en&page=1`)
|
||||
LogURI bool
|
||||
// LogURIPath instructs logger to extract request URI path part (i.e. `/list`)
|
||||
LogURIPath bool
|
||||
// LogRoutePath instructs logger to extract route path part to which request was matched to (i.e. `/user/:id`)
|
||||
LogRoutePath bool
|
||||
// LogRequestID instructs logger to extract request ID from request `X-Request-ID` header or response if request did not have value.
|
||||
LogRequestID bool
|
||||
// LogReferer instructs logger to extract request referer values.
|
||||
LogReferer bool
|
||||
// LogUserAgent instructs logger to extract request user agent values.
|
||||
LogUserAgent bool
|
||||
// LogStatus instructs logger to extract response status code. If handler chain returns an echo.HTTPError,
|
||||
// the status code is extracted from the echo.HTTPError returned
|
||||
LogStatus bool
|
||||
// LogError instructs logger to extract error returned from executed handler chain.
|
||||
LogError bool
|
||||
// LogContentLength instructs logger to extract content length header value. Note: this value could be different from
|
||||
// actual request body size as it could be spoofed etc.
|
||||
LogContentLength bool
|
||||
// LogResponseSize instructs logger to extract response content length value. Note: when used with Gzip middleware
|
||||
// this value may not be always correct.
|
||||
LogResponseSize bool
|
||||
// LogHeaders instructs logger to extract given list of headers from request. Note: request can contain more than
|
||||
// one header with same value so slice of values is been logger for each given header.
|
||||
//
|
||||
// Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header
|
||||
// names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
LogHeaders []string
|
||||
// LogQueryParams instructs logger to extract given list of query parameters from request URI. Note: request can
|
||||
// contain more than one query parameter with same name so slice of values is been logger for each given query param name.
|
||||
LogQueryParams []string
|
||||
// LogFormValues instructs logger to extract given list of form values from request body+URI. Note: request can
|
||||
// contain more than one form value with same name so slice of values is been logger for each given form value name.
|
||||
LogFormValues []string
|
||||
|
||||
timeNow func() time.Time
|
||||
}
|
||||
|
||||
// RequestLoggerValues contains extracted values from logger.
|
||||
type RequestLoggerValues struct {
|
||||
// StartTime is time recorded before next middleware/handler is executed.
|
||||
StartTime time.Time
|
||||
// Latency is duration it took to execute rest of the handler chain (next(c) call).
|
||||
Latency time.Duration
|
||||
// Protocol is request protocol (i.e. `HTTP/1.1` or `HTTP/2`)
|
||||
Protocol string
|
||||
// RemoteIP is request remote IP. See `echo.Context.RealIP()` for implementation details.
|
||||
RemoteIP string
|
||||
// Host is request host value (i.e. `example.com`)
|
||||
Host string
|
||||
// Method is request method value (i.e. `GET` etc)
|
||||
Method string
|
||||
// URI is request URI (i.e. `/list?lang=en&page=1`)
|
||||
URI string
|
||||
// URIPath is request URI path part (i.e. `/list`)
|
||||
URIPath string
|
||||
// RoutePath is route path part to which request was matched to (i.e. `/user/:id`)
|
||||
RoutePath string
|
||||
// RequestID is request ID from request `X-Request-ID` header or response if request did not have value.
|
||||
RequestID string
|
||||
// Referer is request referer values.
|
||||
Referer string
|
||||
// UserAgent is request user agent values.
|
||||
UserAgent string
|
||||
// Status is response status code. Then handler returns an echo.HTTPError then code from there.
|
||||
Status int
|
||||
// Error is error returned from executed handler chain.
|
||||
Error error
|
||||
// ContentLength is content length header value. Note: this value could be different from actual request body size
|
||||
// as it could be spoofed etc.
|
||||
ContentLength string
|
||||
// ResponseSize is response content length value. Note: when used with Gzip middleware this value may not be always correct.
|
||||
ResponseSize int64
|
||||
// Headers are list of headers from request. Note: request can contain more than one header with same value so slice
|
||||
// of values is been logger for each given header.
|
||||
// Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header
|
||||
// names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
Headers map[string][]string
|
||||
// QueryParams are list of query parameters from request URI. Note: request can contain more than one query parameter
|
||||
// with same name so slice of values is been logger for each given query param name.
|
||||
QueryParams map[string][]string
|
||||
// FormValues are list of form values from request body+URI. Note: request can contain more than one form value with
|
||||
// same name so slice of values is been logger for each given form value name.
|
||||
FormValues map[string][]string
|
||||
}
|
||||
|
||||
// RequestLoggerWithConfig returns a RequestLogger middleware with config.
|
||||
func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc {
|
||||
mw, err := config.ToMiddleware()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mw
|
||||
}
|
||||
|
||||
// ToMiddleware converts RequestLoggerConfig into middleware or returns an error for invalid configuration.
|
||||
func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultSkipper
|
||||
}
|
||||
now := time.Now
|
||||
if config.timeNow != nil {
|
||||
now = config.timeNow
|
||||
}
|
||||
|
||||
if config.LogValuesFunc == nil {
|
||||
return nil, errors.New("missing LogValuesFunc callback function for request logger middleware")
|
||||
}
|
||||
|
||||
logHeaders := len(config.LogHeaders) > 0
|
||||
headers := append([]string(nil), config.LogHeaders...)
|
||||
for i, v := range headers {
|
||||
headers[i] = http.CanonicalHeaderKey(v)
|
||||
}
|
||||
|
||||
logQueryParams := len(config.LogQueryParams) > 0
|
||||
logFormValues := len(config.LogFormValues) > 0
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
start := now()
|
||||
|
||||
if config.BeforeNextFunc != nil {
|
||||
config.BeforeNextFunc(c)
|
||||
}
|
||||
err := next(c)
|
||||
if err != nil && config.HandleError {
|
||||
c.Error(err)
|
||||
}
|
||||
|
||||
v := RequestLoggerValues{
|
||||
StartTime: start,
|
||||
}
|
||||
if config.LogLatency {
|
||||
v.Latency = now().Sub(start)
|
||||
}
|
||||
if config.LogProtocol {
|
||||
v.Protocol = req.Proto
|
||||
}
|
||||
if config.LogRemoteIP {
|
||||
v.RemoteIP = c.RealIP()
|
||||
}
|
||||
if config.LogHost {
|
||||
v.Host = req.Host
|
||||
}
|
||||
if config.LogMethod {
|
||||
v.Method = req.Method
|
||||
}
|
||||
if config.LogURI {
|
||||
v.URI = req.RequestURI
|
||||
}
|
||||
if config.LogURIPath {
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
v.URIPath = p
|
||||
}
|
||||
if config.LogRoutePath {
|
||||
v.RoutePath = c.Path()
|
||||
}
|
||||
if config.LogRequestID {
|
||||
id := req.Header.Get(echo.HeaderXRequestID)
|
||||
if id == "" {
|
||||
id = res.Header().Get(echo.HeaderXRequestID)
|
||||
}
|
||||
v.RequestID = id
|
||||
}
|
||||
if config.LogReferer {
|
||||
v.Referer = req.Referer()
|
||||
}
|
||||
if config.LogUserAgent {
|
||||
v.UserAgent = req.UserAgent()
|
||||
}
|
||||
if config.LogStatus {
|
||||
v.Status = res.Status
|
||||
if err != nil && !config.HandleError {
|
||||
// this block should not be executed in case of HandleError=true as the global error handler will decide
|
||||
// the status code. In that case status code could be different from what err contains.
|
||||
var httpErr *echo.HTTPError
|
||||
if errors.As(err, &httpErr) {
|
||||
v.Status = httpErr.Code
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.LogError && err != nil {
|
||||
v.Error = err
|
||||
}
|
||||
if config.LogContentLength {
|
||||
v.ContentLength = req.Header.Get(echo.HeaderContentLength)
|
||||
}
|
||||
if config.LogResponseSize {
|
||||
v.ResponseSize = res.Size
|
||||
}
|
||||
if logHeaders {
|
||||
v.Headers = map[string][]string{}
|
||||
for _, header := range headers {
|
||||
if values, ok := req.Header[header]; ok {
|
||||
v.Headers[header] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
if logQueryParams {
|
||||
queryParams := c.QueryParams()
|
||||
v.QueryParams = map[string][]string{}
|
||||
for _, param := range config.LogQueryParams {
|
||||
if values, ok := queryParams[param]; ok {
|
||||
v.QueryParams[param] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
if logFormValues {
|
||||
v.FormValues = map[string][]string{}
|
||||
for _, formValue := range config.LogFormValues {
|
||||
if values, ok := req.Form[formValue]; ok {
|
||||
v.FormValues[formValue] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errOnLog := config.LogValuesFunc(c, v); errOnLog != nil {
|
||||
return errOnLog
|
||||
}
|
||||
|
||||
// in case of HandleError=true we are returning the error that we already have handled with global error handler
|
||||
// this is deliberate as this error could be useful for upstream middlewares and default global error handler
|
||||
// will ignore that error when it bubbles up in middleware chain.
|
||||
return err
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
80
vendor/github.com/labstack/echo/v4/middleware/rewrite.go
generated
vendored
Normal file
80
vendor/github.com/labstack/echo/v4/middleware/rewrite.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// RewriteConfig defines the config for Rewrite middleware.
|
||||
type RewriteConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Rules defines the URL path rewrite rules. The values captured in asterisk can be
|
||||
// retrieved by index e.g. $1, $2 and so on.
|
||||
// Example:
|
||||
// "/old": "/new",
|
||||
// "/api/*": "/$1",
|
||||
// "/js/*": "/public/javascripts/$1",
|
||||
// "/users/*/orders/*": "/user/$1/order/$2",
|
||||
// Required.
|
||||
Rules map[string]string `yaml:"rules"`
|
||||
|
||||
// RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
|
||||
// Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
|
||||
// Example:
|
||||
// "^/old/[0.9]+/": "/new",
|
||||
// "^/api/.+?/(.*)": "/v2/$1",
|
||||
RegexRules map[*regexp.Regexp]string `yaml:"-"`
|
||||
}
|
||||
|
||||
// DefaultRewriteConfig is the default Rewrite middleware config.
|
||||
var DefaultRewriteConfig = RewriteConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
|
||||
// Rewrite returns a Rewrite middleware.
|
||||
//
|
||||
// Rewrite middleware rewrites the URL path based on the provided rules.
|
||||
func Rewrite(rules map[string]string) echo.MiddlewareFunc {
|
||||
c := DefaultRewriteConfig
|
||||
c.Rules = rules
|
||||
return RewriteWithConfig(c)
|
||||
}
|
||||
|
||||
// RewriteWithConfig returns a Rewrite middleware with config.
|
||||
// See: `Rewrite()`.
|
||||
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Rules == nil && config.RegexRules == nil {
|
||||
panic("echo: rewrite middleware requires url path rewrite rules or regex rules")
|
||||
}
|
||||
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||
}
|
||||
|
||||
if config.RegexRules == nil {
|
||||
config.RegexRules = make(map[*regexp.Regexp]string)
|
||||
}
|
||||
for k, v := range rewriteRulesRegex(config.Rules) {
|
||||
config.RegexRules[k] = v
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if err := rewriteURL(config.RegexRules, c.Request()); err != nil {
|
||||
return err
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
144
vendor/github.com/labstack/echo/v4/middleware/secure.go
generated
vendored
Normal file
144
vendor/github.com/labstack/echo/v4/middleware/secure.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// SecureConfig defines the config for Secure middleware.
|
||||
type SecureConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
||||
// by setting the `X-XSS-Protection` header.
|
||||
// Optional. Default value "1; mode=block".
|
||||
XSSProtection string `yaml:"xss_protection"`
|
||||
|
||||
// ContentTypeNosniff provides protection against overriding Content-Type
|
||||
// header by setting the `X-Content-Type-Options` header.
|
||||
// Optional. Default value "nosniff".
|
||||
ContentTypeNosniff string `yaml:"content_type_nosniff"`
|
||||
|
||||
// XFrameOptions can be used to indicate whether or not a browser should
|
||||
// be allowed to render a page in a <frame>, <iframe> or <object> .
|
||||
// Sites can use this to avoid clickjacking attacks, by ensuring that their
|
||||
// content is not embedded into other sites.provides protection against
|
||||
// clickjacking.
|
||||
// Optional. Default value "SAMEORIGIN".
|
||||
// Possible values:
|
||||
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
|
||||
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
|
||||
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
|
||||
XFrameOptions string `yaml:"x_frame_options"`
|
||||
|
||||
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
|
||||
// long (in seconds) browsers should remember that this site is only to
|
||||
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
|
||||
// man-in-the-middle (MITM) attacks.
|
||||
// Optional. Default value 0.
|
||||
HSTSMaxAge int `yaml:"hsts_max_age"`
|
||||
|
||||
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
|
||||
// header, excluding all subdomains from security policy. It has no effect
|
||||
// unless HSTSMaxAge is set to a non-zero value.
|
||||
// Optional. Default value false.
|
||||
HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
|
||||
|
||||
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
|
||||
// security against cross-site scripting (XSS), clickjacking and other code
|
||||
// injection attacks resulting from execution of malicious content in the
|
||||
// trusted web page context.
|
||||
// Optional. Default value "".
|
||||
ContentSecurityPolicy string `yaml:"content_security_policy"`
|
||||
|
||||
// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
|
||||
// of the `Content-Security-Policy` header. This allows iterative updates of the
|
||||
// content security policy by only reporting the violations that would
|
||||
// have occurred instead of blocking the resource.
|
||||
// Optional. Default value false.
|
||||
CSPReportOnly bool `yaml:"csp_report_only"`
|
||||
|
||||
// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
|
||||
// header, which enables the domain to be included in the HSTS preload list
|
||||
// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
|
||||
// Optional. Default value false.
|
||||
HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
|
||||
|
||||
// ReferrerPolicy sets the `Referrer-Policy` header providing security against
|
||||
// leaking potentially sensitive request paths to third parties.
|
||||
// Optional. Default value "".
|
||||
ReferrerPolicy string `yaml:"referrer_policy"`
|
||||
}
|
||||
|
||||
// DefaultSecureConfig is the default Secure middleware config.
|
||||
var DefaultSecureConfig = SecureConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
XSSProtection: "1; mode=block",
|
||||
ContentTypeNosniff: "nosniff",
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
HSTSPreloadEnabled: false,
|
||||
}
|
||||
|
||||
// Secure returns a Secure middleware.
|
||||
// Secure middleware provides protection against cross-site scripting (XSS) attack,
|
||||
// content type sniffing, clickjacking, insecure connection and other code injection
|
||||
// attacks.
|
||||
func Secure() echo.MiddlewareFunc {
|
||||
return SecureWithConfig(DefaultSecureConfig)
|
||||
}
|
||||
|
||||
// SecureWithConfig returns a Secure middleware with config.
|
||||
// See: `Secure()`.
|
||||
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultSecureConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
|
||||
if config.XSSProtection != "" {
|
||||
res.Header().Set(echo.HeaderXXSSProtection, config.XSSProtection)
|
||||
}
|
||||
if config.ContentTypeNosniff != "" {
|
||||
res.Header().Set(echo.HeaderXContentTypeOptions, config.ContentTypeNosniff)
|
||||
}
|
||||
if config.XFrameOptions != "" {
|
||||
res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
|
||||
}
|
||||
if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
|
||||
subdomains := ""
|
||||
if !config.HSTSExcludeSubdomains {
|
||||
subdomains = "; includeSubdomains"
|
||||
}
|
||||
if config.HSTSPreloadEnabled {
|
||||
subdomains = fmt.Sprintf("%s; preload", subdomains)
|
||||
}
|
||||
res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
|
||||
}
|
||||
if config.ContentSecurityPolicy != "" {
|
||||
if config.CSPReportOnly {
|
||||
res.Header().Set(echo.HeaderContentSecurityPolicyReportOnly, config.ContentSecurityPolicy)
|
||||
} else {
|
||||
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
|
||||
}
|
||||
}
|
||||
if config.ReferrerPolicy != "" {
|
||||
res.Header().Set(echo.HeaderReferrerPolicy, config.ReferrerPolicy)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
129
vendor/github.com/labstack/echo/v4/middleware/slash.go
generated
vendored
Normal file
129
vendor/github.com/labstack/echo/v4/middleware/slash.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// TrailingSlashConfig defines the config for TrailingSlash middleware.
|
||||
type TrailingSlashConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional, but when provided the request is redirected using this code.
|
||||
RedirectCode int `yaml:"redirect_code"`
|
||||
}
|
||||
|
||||
// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
|
||||
var DefaultTrailingSlashConfig = TrailingSlashConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
|
||||
// AddTrailingSlash returns a root level (before router) middleware which adds a
|
||||
// trailing slash to the request `URL#Path`.
|
||||
//
|
||||
// Usage `Echo#Pre(AddTrailingSlash())`
|
||||
func AddTrailingSlash() echo.MiddlewareFunc {
|
||||
return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig)
|
||||
}
|
||||
|
||||
// AddTrailingSlashWithConfig returns an AddTrailingSlash middleware with config.
|
||||
// See `AddTrailingSlash()`.
|
||||
func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTrailingSlashConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
uri := path
|
||||
if qs != "" {
|
||||
uri += "?" + qs
|
||||
}
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, sanitizeURI(uri))
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTrailingSlash returns a root level (before router) middleware which removes
|
||||
// a trailing slash from the request URI.
|
||||
//
|
||||
// Usage `Echo#Pre(RemoveTrailingSlash())`
|
||||
func RemoveTrailingSlash() echo.MiddlewareFunc {
|
||||
return RemoveTrailingSlashWithConfig(TrailingSlashConfig{})
|
||||
}
|
||||
|
||||
// RemoveTrailingSlashWithConfig returns a RemoveTrailingSlash middleware with config.
|
||||
// See `RemoveTrailingSlash()`.
|
||||
func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTrailingSlashConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
l := len(path) - 1
|
||||
if l > 0 && strings.HasSuffix(path, "/") {
|
||||
path = path[:l]
|
||||
uri := path
|
||||
if qs != "" {
|
||||
uri += "?" + qs
|
||||
}
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, sanitizeURI(uri))
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeURI(uri string) string {
|
||||
// double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
|
||||
// we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
|
||||
if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
|
||||
uri = "/" + strings.TrimLeft(uri, `/\`)
|
||||
}
|
||||
return uri
|
||||
}
|
||||
270
vendor/github.com/labstack/echo/v4/middleware/static.go
generated
vendored
Normal file
270
vendor/github.com/labstack/echo/v4/middleware/static.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/bytes"
|
||||
)
|
||||
|
||||
// StaticConfig defines the config for Static middleware.
|
||||
type StaticConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Root directory from where the static content is served.
|
||||
// Required.
|
||||
Root string `yaml:"root"`
|
||||
|
||||
// Index file for serving a directory.
|
||||
// Optional. Default value "index.html".
|
||||
Index string `yaml:"index"`
|
||||
|
||||
// Enable HTML5 mode by forwarding all not-found requests to root so that
|
||||
// SPA (single-page application) can handle the routing.
|
||||
// Optional. Default value false.
|
||||
HTML5 bool `yaml:"html5"`
|
||||
|
||||
// Enable directory browsing.
|
||||
// Optional. Default value false.
|
||||
Browse bool `yaml:"browse"`
|
||||
|
||||
// Enable ignoring of the base of the URL path.
|
||||
// Example: when assigning a static middleware to a non root path group,
|
||||
// the filesystem path is not doubled
|
||||
// Optional. Default value false.
|
||||
IgnoreBase bool `yaml:"ignoreBase"`
|
||||
|
||||
// Filesystem provides access to the static content.
|
||||
// Optional. Defaults to http.Dir(config.Root)
|
||||
Filesystem http.FileSystem `yaml:"-"`
|
||||
}
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{{ .Name }}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
padding: 48px;
|
||||
}
|
||||
header {
|
||||
padding: 4px 16px;
|
||||
font-size: 24px;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 20px 0 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
li {
|
||||
width: 300px;
|
||||
padding: 16px;
|
||||
}
|
||||
li a {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
li span {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
}
|
||||
li a:hover {
|
||||
opacity: 0.50;
|
||||
}
|
||||
.dir {
|
||||
color: #E91E63;
|
||||
}
|
||||
.file {
|
||||
color: #673AB7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{ .Name }}
|
||||
</header>
|
||||
<ul>
|
||||
{{ range .Files }}
|
||||
<li>
|
||||
{{ if .Dir }}
|
||||
{{ $name := print .Name "/" }}
|
||||
<a class="dir" href="{{ $name }}">{{ $name }}</a>
|
||||
{{ else }}
|
||||
<a class="file" href="{{ .Name }}">{{ .Name }}</a>
|
||||
<span>{{ .Size }}</span>
|
||||
{{ end }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// DefaultStaticConfig is the default Static middleware config.
|
||||
var DefaultStaticConfig = StaticConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Index: "index.html",
|
||||
}
|
||||
|
||||
// Static returns a Static middleware to serves static content from the provided
|
||||
// root directory.
|
||||
func Static(root string) echo.MiddlewareFunc {
|
||||
c := DefaultStaticConfig
|
||||
c.Root = root
|
||||
return StaticWithConfig(c)
|
||||
}
|
||||
|
||||
// StaticWithConfig returns a Static middleware with config.
|
||||
// See `Static()`.
|
||||
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Root == "" {
|
||||
config.Root = "." // For security we want to restrict to CWD.
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultStaticConfig.Skipper
|
||||
}
|
||||
if config.Index == "" {
|
||||
config.Index = DefaultStaticConfig.Index
|
||||
}
|
||||
if config.Filesystem == nil {
|
||||
config.Filesystem = http.Dir(config.Root)
|
||||
config.Root = "."
|
||||
}
|
||||
|
||||
// Index template
|
||||
t, tErr := template.New("index").Parse(html)
|
||||
if tErr != nil {
|
||||
panic(fmt.Errorf("echo: %w", tErr))
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
p := c.Request().URL.Path
|
||||
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
|
||||
p = c.Param("*")
|
||||
}
|
||||
p, err = url.PathUnescape(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := path.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
|
||||
|
||||
if config.IgnoreBase {
|
||||
routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
|
||||
baseURLPath := path.Base(p)
|
||||
if baseURLPath == routePath {
|
||||
i := strings.LastIndex(name, routePath)
|
||||
name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
|
||||
}
|
||||
}
|
||||
|
||||
file, err := config.Filesystem.Open(name)
|
||||
if err != nil {
|
||||
if !isIgnorableOpenFileError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// file with that path did not exist, so we continue down in middleware/handler chain, hoping that we end up in
|
||||
// handler that is meant to handle this request
|
||||
if err = next(c); err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var he *echo.HTTPError
|
||||
if !(errors.As(err, &he) && config.HTML5 && he.Code == http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err = config.Filesystem.Open(path.Join(config.Root, config.Index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
index, err := config.Filesystem.Open(path.Join(name, config.Index))
|
||||
if err != nil {
|
||||
if config.Browse {
|
||||
return listDir(t, name, file, c.Response())
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
|
||||
defer index.Close()
|
||||
|
||||
info, err = index.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return serveFile(c, index, info)
|
||||
}
|
||||
|
||||
return serveFile(c, file, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
|
||||
http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listDir(t *template.Template, name string, dir http.File, res *echo.Response) (err error) {
|
||||
files, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create directory index
|
||||
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||
data := struct {
|
||||
Name string
|
||||
Files []interface{}
|
||||
}{
|
||||
Name: name,
|
||||
}
|
||||
for _, f := range files {
|
||||
data.Files = append(data.Files, struct {
|
||||
Name string
|
||||
Dir bool
|
||||
Size string
|
||||
}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
|
||||
}
|
||||
return t.Execute(res, data)
|
||||
}
|
||||
15
vendor/github.com/labstack/echo/v4/middleware/static_other.go
generated
vendored
Normal file
15
vendor/github.com/labstack/echo/v4/middleware/static_other.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// We ignore these errors as there could be handler that matches request path.
|
||||
func isIgnorableOpenFileError(err error) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
26
vendor/github.com/labstack/echo/v4/middleware/static_windows.go
generated
vendored
Normal file
26
vendor/github.com/labstack/echo/v4/middleware/static_windows.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// We ignore these errors as there could be handler that matches request path.
|
||||
//
|
||||
// As of Go 1.20 filepath.Clean has different behaviour on OS related filesystems so we need to use path.Clean
|
||||
// on Windows which has some caveats. The Open methods might return different errors than earlier versions and
|
||||
// as of 1.20 path checks are more strict on the provided path and considers [UNC](https://en.wikipedia.org/wiki/Path_(computing)#UNC)
|
||||
// paths with missing host etc parts as invalid. Previously it would result you `fs.ErrNotExist`.
|
||||
//
|
||||
// For 1.20@Windows we need to treat those errors the same as `fs.ErrNotExists` so we can continue handling
|
||||
// errors in the middleware/handler chain. Otherwise we might end up with status 500 instead of finding a route
|
||||
// or return 404 not found.
|
||||
func isIgnorableOpenFileError(err error) bool {
|
||||
if os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
errTxt := err.Error()
|
||||
return errTxt == "http: invalid or unsafe file path" || errTxt == "invalid path"
|
||||
}
|
||||
221
vendor/github.com/labstack/echo/v4/middleware/timeout.go
generated
vendored
Normal file
221
vendor/github.com/labstack/echo/v4/middleware/timeout.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
// WARNING: Timeout middleware causes more problems than it solves.
|
||||
// WARNING: This middleware should be first middleware as it messes with request Writer and could cause data race if
|
||||
// it is in other position
|
||||
//
|
||||
// Depending on out requirements you could be better of setting timeout to context and
|
||||
// check its deadline from handler.
|
||||
//
|
||||
// For example: create middleware to set timeout to context
|
||||
// func RequestTimeout(timeout time.Duration) echo.MiddlewareFunc {
|
||||
// return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
// return func(c echo.Context) error {
|
||||
// timeoutCtx, cancel := context.WithTimeout(c.Request().Context(), timeout)
|
||||
// c.SetRequest(c.Request().WithContext(timeoutCtx))
|
||||
// defer cancel()
|
||||
// return next(c)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
// Create handler that checks for context deadline and runs actual task in separate coroutine
|
||||
// Note: separate coroutine may not be even if you do not want to process continue executing and
|
||||
// just want to stop long-running handler to stop and you are using "context aware" methods (ala db queries with ctx)
|
||||
// e.GET("/", func(c echo.Context) error {
|
||||
//
|
||||
// doneCh := make(chan error)
|
||||
// go func(ctx context.Context) {
|
||||
// doneCh <- myPossiblyLongRunningBackgroundTaskWithCtx(ctx)
|
||||
// }(c.Request().Context())
|
||||
//
|
||||
// select { // wait for task to finish or context to timeout/cancelled
|
||||
// case err := <-doneCh:
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return c.String(http.StatusOK, "OK")
|
||||
// case <-c.Request().Context().Done():
|
||||
// if c.Request().Context().Err() == context.DeadlineExceeded {
|
||||
// return c.String(http.StatusServiceUnavailable, "timeout")
|
||||
// }
|
||||
// return c.Request().Context().Err()
|
||||
// }
|
||||
//
|
||||
// })
|
||||
//
|
||||
|
||||
// TimeoutConfig defines the config for Timeout middleware.
|
||||
type 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
|
||||
}
|
||||
|
||||
// DefaultTimeoutConfig is the default Timeout middleware config.
|
||||
var DefaultTimeoutConfig = TimeoutConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Timeout: 0,
|
||||
ErrorMessage: "",
|
||||
}
|
||||
|
||||
// Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
|
||||
// call runs for longer than its time limit. NB: timeout does not stop handler execution.
|
||||
func Timeout() echo.MiddlewareFunc {
|
||||
return TimeoutWithConfig(DefaultTimeoutConfig)
|
||||
}
|
||||
|
||||
// TimeoutWithConfig returns a Timeout middleware with config or panics on invalid configuration.
|
||||
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
|
||||
mw, err := config.ToMiddleware()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mw
|
||||
}
|
||||
|
||||
// ToMiddleware converts Config to middleware or returns an error for invalid configuration
|
||||
func (config TimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
handlerWrapper := echoHandlerFuncWrapper{
|
||||
writer: &ignorableWriter{ResponseWriter: c.Response().Writer},
|
||||
ctx: c,
|
||||
handler: next,
|
||||
errChan: errChan,
|
||||
errHandler: config.OnTimeoutRouteErrorHandler,
|
||||
}
|
||||
handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage)
|
||||
handler.ServeHTTP(handlerWrapper.writer, c.Request())
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
type echoHandlerFuncWrapper struct {
|
||||
writer *ignorableWriter
|
||||
ctx echo.Context
|
||||
handler echo.HandlerFunc
|
||||
errHandler func(err error, c echo.Context)
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
// replace echo.Context Request with the one provided by TimeoutHandler to let later middlewares/handler on the chain
|
||||
// handle properly it's cancellation
|
||||
t.ctx.SetRequest(r)
|
||||
|
||||
// replace writer with TimeoutHandler custom one. This will guarantee that
|
||||
// `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
|
||||
originalWriter := t.ctx.Response().Writer
|
||||
t.ctx.Response().Writer = rw
|
||||
|
||||
// in case of panic we restore original writer and call panic again
|
||||
// so it could be handled with global middleware Recover()
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.ctx.Response().Writer = originalWriter
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err := t.handler(t.ctx)
|
||||
if ctxErr := r.Context().Err(); ctxErr == context.DeadlineExceeded {
|
||||
if err != nil && t.errHandler != nil {
|
||||
t.errHandler(err, t.ctx)
|
||||
}
|
||||
return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers
|
||||
}
|
||||
if err != nil {
|
||||
// This is needed as `http.TimeoutHandler` will write status code by itself on error and after that our tries to write
|
||||
// status code will not work anymore as Echo.Response thinks it has been already "committed" and further writes
|
||||
// create errors in log about `superfluous response.WriteHeader call from`
|
||||
t.writer.Ignore(true)
|
||||
t.ctx.Response().Writer = originalWriter // make sure we restore writer before we signal original coroutine about the error
|
||||
// we pass error from handler to middlewares up in handler chain to act on it if needed.
|
||||
t.errChan <- err
|
||||
return
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// ignorableWriter is ResponseWriter implementations that allows us to mark writer to ignore further write calls. This
|
||||
// is handy in cases when you do not have direct control of code being executed (3rd party middleware) but want to make
|
||||
// sure that external code will not be able to write response to the client.
|
||||
// Writer is coroutine safe for writes.
|
||||
type ignorableWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
lock sync.Mutex
|
||||
ignoreWrites bool
|
||||
}
|
||||
|
||||
func (w *ignorableWriter) Ignore(ignore bool) {
|
||||
w.lock.Lock()
|
||||
w.ignoreWrites = ignore
|
||||
w.lock.Unlock()
|
||||
}
|
||||
|
||||
func (w *ignorableWriter) WriteHeader(code int) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
if w.ignoreWrites {
|
||||
return
|
||||
}
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *ignorableWriter) Write(b []byte) (int, error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
if w.ignoreWrites {
|
||||
return len(b), nil
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
103
vendor/github.com/labstack/echo/v4/middleware/util.go
generated
vendored
Normal file
103
vendor/github.com/labstack/echo/v4/middleware/util.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func matchScheme(domain, pattern string) bool {
|
||||
didx := strings.Index(domain, ":")
|
||||
pidx := strings.Index(pattern, ":")
|
||||
return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
|
||||
}
|
||||
|
||||
// matchSubdomain compares authority with wildcard
|
||||
func matchSubdomain(domain, pattern string) bool {
|
||||
if !matchScheme(domain, pattern) {
|
||||
return false
|
||||
}
|
||||
didx := strings.Index(domain, "://")
|
||||
pidx := strings.Index(pattern, "://")
|
||||
if didx == -1 || pidx == -1 {
|
||||
return false
|
||||
}
|
||||
domAuth := domain[didx+3:]
|
||||
// to avoid long loop by invalid long domain
|
||||
if len(domAuth) > 253 {
|
||||
return false
|
||||
}
|
||||
patAuth := pattern[pidx+3:]
|
||||
|
||||
domComp := strings.Split(domAuth, ".")
|
||||
patComp := strings.Split(patAuth, ".")
|
||||
for i := len(domComp)/2 - 1; i >= 0; i-- {
|
||||
opp := len(domComp) - 1 - i
|
||||
domComp[i], domComp[opp] = domComp[opp], domComp[i]
|
||||
}
|
||||
for i := len(patComp)/2 - 1; i >= 0; i-- {
|
||||
opp := len(patComp) - 1 - i
|
||||
patComp[i], patComp[opp] = patComp[opp], patComp[i]
|
||||
}
|
||||
|
||||
for i, v := range domComp {
|
||||
if len(patComp) <= i {
|
||||
return false
|
||||
}
|
||||
p := patComp[i]
|
||||
if p == "*" {
|
||||
return true
|
||||
}
|
||||
if p != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// https://tip.golang.org/doc/go1.19#:~:text=Read%20no%20longer%20buffers%20random%20data%20obtained%20from%20the%20operating%20system%20between%20calls
|
||||
var randomReaderPool = sync.Pool{New: func() interface{} {
|
||||
return bufio.NewReader(rand.Reader)
|
||||
}}
|
||||
|
||||
const randomStringCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
const randomStringCharsetLen = 52 // len(randomStringCharset)
|
||||
const randomStringMaxByte = 255 - (256 % randomStringCharsetLen)
|
||||
|
||||
func randomString(length uint8) string {
|
||||
reader := randomReaderPool.Get().(*bufio.Reader)
|
||||
defer randomReaderPool.Put(reader)
|
||||
|
||||
b := make([]byte, length)
|
||||
r := make([]byte, length+(length/4)) // perf: avoid read from rand.Reader many times
|
||||
var i uint8 = 0
|
||||
|
||||
// security note:
|
||||
// we can't just simply do b[i]=randomStringCharset[rb%len(randomStringCharset)],
|
||||
// len(len(randomStringCharset)) is 52, and rb is [0, 255], 256 = 52 * 4 + 48.
|
||||
// make the first 48 characters more possibly to be generated then others.
|
||||
// So we have to skip bytes when rb > randomStringMaxByte
|
||||
|
||||
for {
|
||||
_, err := io.ReadFull(reader, r)
|
||||
if err != nil {
|
||||
panic("unexpected error happened when reading from bufio.NewReader(crypto/rand.Reader)")
|
||||
}
|
||||
for _, rb := range r {
|
||||
if rb > randomStringMaxByte {
|
||||
// Skip this number to avoid bias.
|
||||
continue
|
||||
}
|
||||
b[i] = randomStringCharset[rb%randomStringCharsetLen]
|
||||
i++
|
||||
if i == length {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/labstack/echo/v4/renderer.go
generated
vendored
Normal file
29
vendor/github.com/labstack/echo/v4/renderer.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package echo
|
||||
|
||||
import "io"
|
||||
|
||||
// Renderer is the interface that wraps the Render function.
|
||||
type Renderer interface {
|
||||
Render(io.Writer, string, interface{}, Context) error
|
||||
}
|
||||
|
||||
// TemplateRenderer is helper to ease creating renderers for `html/template` and `text/template` packages.
|
||||
// Example usage:
|
||||
//
|
||||
// e.Renderer = &echo.TemplateRenderer{
|
||||
// Template: template.Must(template.ParseGlob("templates/*.html")),
|
||||
// }
|
||||
//
|
||||
// e.Renderer = &echo.TemplateRenderer{
|
||||
// Template: template.Must(template.New("hello").Parse("Hello, {{.}}!")),
|
||||
// }
|
||||
type TemplateRenderer struct {
|
||||
Template interface {
|
||||
ExecuteTemplate(wr io.Writer, name string, data any) error
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders the template with given data.
|
||||
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c Context) error {
|
||||
return t.Template.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
116
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
Normal file
116
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Response wraps an http.ResponseWriter and implements its interface to be used
|
||||
// by an HTTP handler to construct an HTTP response.
|
||||
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||
type Response struct {
|
||||
Writer http.ResponseWriter
|
||||
echo *Echo
|
||||
beforeFuncs []func()
|
||||
afterFuncs []func()
|
||||
Status int
|
||||
Size int64
|
||||
Committed bool
|
||||
}
|
||||
|
||||
// NewResponse creates a new instance of Response.
|
||||
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
|
||||
return &Response{Writer: w, echo: e}
|
||||
}
|
||||
|
||||
// Header returns the header map for the writer that will be sent by
|
||||
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
|
||||
// no effect unless the modified headers were declared as trailers by setting
|
||||
// the "Trailer" header before the call to WriteHeader (see example)
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||
func (r *Response) Header() http.Header {
|
||||
return r.Writer.Header()
|
||||
}
|
||||
|
||||
// Before registers a function which is called just before the response is written.
|
||||
func (r *Response) Before(fn func()) {
|
||||
r.beforeFuncs = append(r.beforeFuncs, fn)
|
||||
}
|
||||
|
||||
// After registers a function which is called just after the response is written.
|
||||
// If the `Content-Length` is unknown, none of the after function is executed.
|
||||
func (r *Response) After(fn func()) {
|
||||
r.afterFuncs = append(r.afterFuncs, fn)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
|
||||
// not called explicitly, the first call to Write will trigger an implicit
|
||||
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
|
||||
// used to send error codes.
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.Committed {
|
||||
r.echo.Logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.Status = code
|
||||
for _, fn := range r.beforeFuncs {
|
||||
fn()
|
||||
}
|
||||
r.Writer.WriteHeader(r.Status)
|
||||
r.Committed = true
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
if !r.Committed {
|
||||
if r.Status == 0 {
|
||||
r.Status = http.StatusOK
|
||||
}
|
||||
r.WriteHeader(r.Status)
|
||||
}
|
||||
n, err = r.Writer.Write(b)
|
||||
r.Size += int64(n)
|
||||
for _, fn := range r.afterFuncs {
|
||||
fn()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
|
||||
// buffered data to the client.
|
||||
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
|
||||
func (r *Response) Flush() {
|
||||
err := http.NewResponseController(r.Writer).Flush()
|
||||
if err != nil && errors.Is(err, http.ErrNotSupported) {
|
||||
panic(errors.New("response writer flushing is not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
|
||||
// take over the connection.
|
||||
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
|
||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return http.NewResponseController(r.Writer).Hijack()
|
||||
}
|
||||
|
||||
// Unwrap returns the original http.ResponseWriter.
|
||||
// ResponseController can be used to access the original http.ResponseWriter.
|
||||
// See [https://go.dev/blog/go1.20]
|
||||
func (r *Response) Unwrap() http.ResponseWriter {
|
||||
return r.Writer
|
||||
}
|
||||
|
||||
func (r *Response) reset(w http.ResponseWriter) {
|
||||
r.beforeFuncs = nil
|
||||
r.afterFuncs = nil
|
||||
r.Writer = w
|
||||
r.Size = 0
|
||||
r.Status = http.StatusOK
|
||||
r.Committed = false
|
||||
}
|
||||
760
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
Normal file
760
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
Normal file
@@ -0,0 +1,760 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
|
||||
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Router is the registry of all registered routes for an `Echo` instance for
|
||||
// request matching and URL path parameter parsing.
|
||||
type Router struct {
|
||||
tree *node
|
||||
routes map[string]*Route
|
||||
echo *Echo
|
||||
}
|
||||
|
||||
type node struct {
|
||||
methods *routeMethods
|
||||
parent *node
|
||||
paramChild *node
|
||||
anyChild *node
|
||||
// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
|
||||
notFoundHandler *routeMethod
|
||||
prefix string
|
||||
originalPath string
|
||||
staticChildren children
|
||||
paramsCount int
|
||||
label byte
|
||||
kind kind
|
||||
// isLeaf indicates that node does not have child routes
|
||||
isLeaf bool
|
||||
// isHandler indicates that node has at least one handler registered to it
|
||||
isHandler bool
|
||||
}
|
||||
|
||||
type kind uint8
|
||||
type children []*node
|
||||
|
||||
type routeMethod struct {
|
||||
handler HandlerFunc
|
||||
ppath string
|
||||
pnames []string
|
||||
}
|
||||
|
||||
type routeMethods struct {
|
||||
connect *routeMethod
|
||||
delete *routeMethod
|
||||
get *routeMethod
|
||||
head *routeMethod
|
||||
options *routeMethod
|
||||
patch *routeMethod
|
||||
post *routeMethod
|
||||
propfind *routeMethod
|
||||
put *routeMethod
|
||||
trace *routeMethod
|
||||
report *routeMethod
|
||||
anyOther map[string]*routeMethod
|
||||
allowHeader string
|
||||
}
|
||||
|
||||
const (
|
||||
staticKind kind = iota
|
||||
paramKind
|
||||
anyKind
|
||||
|
||||
paramLabel = byte(':')
|
||||
anyLabel = byte('*')
|
||||
)
|
||||
|
||||
func (m *routeMethods) isHandler() bool {
|
||||
return m.connect != nil ||
|
||||
m.delete != nil ||
|
||||
m.get != nil ||
|
||||
m.head != nil ||
|
||||
m.options != nil ||
|
||||
m.patch != nil ||
|
||||
m.post != nil ||
|
||||
m.propfind != nil ||
|
||||
m.put != nil ||
|
||||
m.trace != nil ||
|
||||
m.report != nil ||
|
||||
len(m.anyOther) != 0
|
||||
// RouteNotFound/404 is not considered as a handler
|
||||
}
|
||||
|
||||
func (m *routeMethods) updateAllowHeader() {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(http.MethodOptions)
|
||||
|
||||
if m.connect != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodConnect)
|
||||
}
|
||||
if m.delete != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodDelete)
|
||||
}
|
||||
if m.get != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodGet)
|
||||
}
|
||||
if m.head != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodHead)
|
||||
}
|
||||
if m.patch != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodPatch)
|
||||
}
|
||||
if m.post != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodPost)
|
||||
}
|
||||
if m.propfind != nil {
|
||||
buf.WriteString(", PROPFIND")
|
||||
}
|
||||
if m.put != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodPut)
|
||||
}
|
||||
if m.trace != nil {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(http.MethodTrace)
|
||||
}
|
||||
if m.report != nil {
|
||||
buf.WriteString(", REPORT")
|
||||
}
|
||||
for method := range m.anyOther { // for simplicity, we use map and therefore order is not deterministic here
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(method)
|
||||
}
|
||||
m.allowHeader = buf.String()
|
||||
}
|
||||
|
||||
// NewRouter returns a new Router instance.
|
||||
func NewRouter(e *Echo) *Router {
|
||||
return &Router{
|
||||
tree: &node{
|
||||
methods: new(routeMethods),
|
||||
},
|
||||
routes: map[string]*Route{},
|
||||
echo: e,
|
||||
}
|
||||
}
|
||||
|
||||
// Routes returns the registered routes.
|
||||
func (r *Router) Routes() []*Route {
|
||||
routes := make([]*Route, 0, len(r.routes))
|
||||
for _, v := range r.routes {
|
||||
routes = append(routes, v)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Reverse generates a URL from route name and provided parameters.
|
||||
func (r *Router) Reverse(name string, params ...interface{}) string {
|
||||
uri := new(bytes.Buffer)
|
||||
ln := len(params)
|
||||
n := 0
|
||||
for _, route := range r.routes {
|
||||
if route.Name == name {
|
||||
for i, l := 0, len(route.Path); i < l; i++ {
|
||||
hasBackslash := route.Path[i] == '\\'
|
||||
if hasBackslash && i+1 < l && route.Path[i+1] == ':' {
|
||||
i++ // backslash before colon escapes that colon. in that case skip backslash
|
||||
}
|
||||
if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) {
|
||||
// in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path
|
||||
for ; i < l && route.Path[i] != '/'; i++ {
|
||||
}
|
||||
uri.WriteString(fmt.Sprintf("%v", params[n]))
|
||||
n++
|
||||
}
|
||||
if i < l {
|
||||
uri.WriteByte(route.Path[i])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return uri.String()
|
||||
}
|
||||
|
||||
func normalizePathSlash(path string) string {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
} else if path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
|
||||
path = normalizePathSlash(path)
|
||||
r.insert(method, path, h)
|
||||
|
||||
route := &Route{
|
||||
Method: method,
|
||||
Path: path,
|
||||
Name: name,
|
||||
}
|
||||
r.routes[method+path] = route
|
||||
return route
|
||||
}
|
||||
|
||||
// Add registers a new route for method and path with matching handler.
|
||||
func (r *Router) Add(method, path string, h HandlerFunc) {
|
||||
r.insert(method, normalizePathSlash(path), h)
|
||||
}
|
||||
|
||||
func (r *Router) insert(method, path string, h HandlerFunc) {
|
||||
path = normalizePathSlash(path)
|
||||
pnames := []string{} // Param names
|
||||
ppath := path // Pristine path
|
||||
|
||||
if h == nil && r.echo.Logger != nil {
|
||||
// FIXME: in future we should return error
|
||||
r.echo.Logger.Errorf("Adding route without handler function: %v:%v", method, path)
|
||||
}
|
||||
|
||||
for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
|
||||
if path[i] == ':' {
|
||||
if i > 0 && path[i-1] == '\\' {
|
||||
path = path[:i-1] + path[i:]
|
||||
i--
|
||||
lcpIndex--
|
||||
continue
|
||||
}
|
||||
j := i + 1
|
||||
|
||||
r.insertNode(method, path[:i], staticKind, routeMethod{})
|
||||
for ; i < lcpIndex && path[i] != '/'; i++ {
|
||||
}
|
||||
|
||||
pnames = append(pnames, path[j:i])
|
||||
path = path[:j] + path[i:]
|
||||
i, lcpIndex = j, len(path)
|
||||
|
||||
if i == lcpIndex {
|
||||
// path node is last fragment of route path. ie. `/users/:id`
|
||||
r.insertNode(method, path[:i], paramKind, routeMethod{ppath: ppath, pnames: pnames, handler: h})
|
||||
} else {
|
||||
r.insertNode(method, path[:i], paramKind, routeMethod{})
|
||||
}
|
||||
} else if path[i] == '*' {
|
||||
r.insertNode(method, path[:i], staticKind, routeMethod{})
|
||||
pnames = append(pnames, "*")
|
||||
r.insertNode(method, path[:i+1], anyKind, routeMethod{ppath: ppath, pnames: pnames, handler: h})
|
||||
}
|
||||
}
|
||||
|
||||
r.insertNode(method, path, staticKind, routeMethod{ppath: ppath, pnames: pnames, handler: h})
|
||||
}
|
||||
|
||||
func (r *Router) insertNode(method, path string, t kind, rm routeMethod) {
|
||||
// Adjust max param
|
||||
paramLen := len(rm.pnames)
|
||||
if *r.echo.maxParam < paramLen {
|
||||
*r.echo.maxParam = paramLen
|
||||
}
|
||||
|
||||
currentNode := r.tree // Current node as root
|
||||
if currentNode == nil {
|
||||
panic("echo: invalid method")
|
||||
}
|
||||
search := path
|
||||
|
||||
for {
|
||||
searchLen := len(search)
|
||||
prefixLen := len(currentNode.prefix)
|
||||
lcpLen := 0
|
||||
|
||||
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
|
||||
max := prefixLen
|
||||
if searchLen < max {
|
||||
max = searchLen
|
||||
}
|
||||
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
|
||||
}
|
||||
|
||||
if lcpLen == 0 {
|
||||
// At root node
|
||||
currentNode.label = search[0]
|
||||
currentNode.prefix = search
|
||||
if rm.handler != nil {
|
||||
currentNode.kind = t
|
||||
currentNode.addMethod(method, &rm)
|
||||
currentNode.paramsCount = len(rm.pnames)
|
||||
currentNode.originalPath = rm.ppath
|
||||
}
|
||||
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
|
||||
} else if lcpLen < prefixLen {
|
||||
// Split node into two before we insert new node.
|
||||
// This happens when we are inserting path that is submatch of any existing inserted paths.
|
||||
// For example, we have node `/test` and now are about to insert `/te/*`. In that case
|
||||
// 1. overlapping part is `/te` that is used as parent node
|
||||
// 2. `st` is part from existing node that is not matching - it gets its own node (child to `/te`)
|
||||
// 3. `/*` is the new part we are about to insert (child to `/te`)
|
||||
n := newNode(
|
||||
currentNode.kind,
|
||||
currentNode.prefix[lcpLen:],
|
||||
currentNode,
|
||||
currentNode.staticChildren,
|
||||
currentNode.originalPath,
|
||||
currentNode.methods,
|
||||
currentNode.paramsCount,
|
||||
currentNode.paramChild,
|
||||
currentNode.anyChild,
|
||||
currentNode.notFoundHandler,
|
||||
)
|
||||
// Update parent path for all children to new node
|
||||
for _, child := range currentNode.staticChildren {
|
||||
child.parent = n
|
||||
}
|
||||
if currentNode.paramChild != nil {
|
||||
currentNode.paramChild.parent = n
|
||||
}
|
||||
if currentNode.anyChild != nil {
|
||||
currentNode.anyChild.parent = n
|
||||
}
|
||||
|
||||
// Reset parent node
|
||||
currentNode.kind = staticKind
|
||||
currentNode.label = currentNode.prefix[0]
|
||||
currentNode.prefix = currentNode.prefix[:lcpLen]
|
||||
currentNode.staticChildren = nil
|
||||
currentNode.originalPath = ""
|
||||
currentNode.methods = new(routeMethods)
|
||||
currentNode.paramsCount = 0
|
||||
currentNode.paramChild = nil
|
||||
currentNode.anyChild = nil
|
||||
currentNode.isLeaf = false
|
||||
currentNode.isHandler = false
|
||||
currentNode.notFoundHandler = nil
|
||||
|
||||
// Only Static children could reach here
|
||||
currentNode.addStaticChild(n)
|
||||
|
||||
if lcpLen == searchLen {
|
||||
// At parent node
|
||||
currentNode.kind = t
|
||||
if rm.handler != nil {
|
||||
currentNode.addMethod(method, &rm)
|
||||
currentNode.paramsCount = len(rm.pnames)
|
||||
currentNode.originalPath = rm.ppath
|
||||
}
|
||||
} else {
|
||||
// Create child node
|
||||
n = newNode(t, search[lcpLen:], currentNode, nil, "", new(routeMethods), 0, nil, nil, nil)
|
||||
if rm.handler != nil {
|
||||
n.addMethod(method, &rm)
|
||||
n.paramsCount = len(rm.pnames)
|
||||
n.originalPath = rm.ppath
|
||||
}
|
||||
// Only Static children could reach here
|
||||
currentNode.addStaticChild(n)
|
||||
}
|
||||
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
|
||||
} else if lcpLen < searchLen {
|
||||
search = search[lcpLen:]
|
||||
c := currentNode.findChildWithLabel(search[0])
|
||||
if c != nil {
|
||||
// Go deeper
|
||||
currentNode = c
|
||||
continue
|
||||
}
|
||||
// Create child node
|
||||
n := newNode(t, search, currentNode, nil, rm.ppath, new(routeMethods), 0, nil, nil, nil)
|
||||
if rm.handler != nil {
|
||||
n.addMethod(method, &rm)
|
||||
n.paramsCount = len(rm.pnames)
|
||||
}
|
||||
|
||||
switch t {
|
||||
case staticKind:
|
||||
currentNode.addStaticChild(n)
|
||||
case paramKind:
|
||||
currentNode.paramChild = n
|
||||
case anyKind:
|
||||
currentNode.anyChild = n
|
||||
}
|
||||
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
|
||||
} else {
|
||||
// Node already exists
|
||||
if rm.handler != nil {
|
||||
currentNode.addMethod(method, &rm)
|
||||
currentNode.paramsCount = len(rm.pnames)
|
||||
currentNode.originalPath = rm.ppath
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func newNode(
|
||||
t kind,
|
||||
pre string,
|
||||
p *node,
|
||||
sc children,
|
||||
originalPath string,
|
||||
methods *routeMethods,
|
||||
paramsCount int,
|
||||
paramChildren,
|
||||
anyChildren *node,
|
||||
notFoundHandler *routeMethod,
|
||||
) *node {
|
||||
return &node{
|
||||
kind: t,
|
||||
label: pre[0],
|
||||
prefix: pre,
|
||||
parent: p,
|
||||
staticChildren: sc,
|
||||
originalPath: originalPath,
|
||||
methods: methods,
|
||||
paramsCount: paramsCount,
|
||||
paramChild: paramChildren,
|
||||
anyChild: anyChildren,
|
||||
isLeaf: sc == nil && paramChildren == nil && anyChildren == nil,
|
||||
isHandler: methods.isHandler(),
|
||||
notFoundHandler: notFoundHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) addStaticChild(c *node) {
|
||||
n.staticChildren = append(n.staticChildren, c)
|
||||
}
|
||||
|
||||
func (n *node) findStaticChild(l byte) *node {
|
||||
for _, c := range n.staticChildren {
|
||||
if c.label == l {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildWithLabel(l byte) *node {
|
||||
if c := n.findStaticChild(l); c != nil {
|
||||
return c
|
||||
}
|
||||
if l == paramLabel {
|
||||
return n.paramChild
|
||||
}
|
||||
if l == anyLabel {
|
||||
return n.anyChild
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) addMethod(method string, h *routeMethod) {
|
||||
switch method {
|
||||
case http.MethodConnect:
|
||||
n.methods.connect = h
|
||||
case http.MethodDelete:
|
||||
n.methods.delete = h
|
||||
case http.MethodGet:
|
||||
n.methods.get = h
|
||||
case http.MethodHead:
|
||||
n.methods.head = h
|
||||
case http.MethodOptions:
|
||||
n.methods.options = h
|
||||
case http.MethodPatch:
|
||||
n.methods.patch = h
|
||||
case http.MethodPost:
|
||||
n.methods.post = h
|
||||
case PROPFIND:
|
||||
n.methods.propfind = h
|
||||
case http.MethodPut:
|
||||
n.methods.put = h
|
||||
case http.MethodTrace:
|
||||
n.methods.trace = h
|
||||
case REPORT:
|
||||
n.methods.report = h
|
||||
case RouteNotFound:
|
||||
n.notFoundHandler = h
|
||||
return // RouteNotFound/404 is not considered as a handler so no further logic needs to be executed
|
||||
default:
|
||||
if n.methods.anyOther == nil {
|
||||
n.methods.anyOther = make(map[string]*routeMethod)
|
||||
}
|
||||
if h.handler == nil {
|
||||
delete(n.methods.anyOther, method)
|
||||
} else {
|
||||
n.methods.anyOther[method] = h
|
||||
}
|
||||
}
|
||||
|
||||
n.methods.updateAllowHeader()
|
||||
n.isHandler = true
|
||||
}
|
||||
|
||||
func (n *node) findMethod(method string) *routeMethod {
|
||||
switch method {
|
||||
case http.MethodConnect:
|
||||
return n.methods.connect
|
||||
case http.MethodDelete:
|
||||
return n.methods.delete
|
||||
case http.MethodGet:
|
||||
return n.methods.get
|
||||
case http.MethodHead:
|
||||
return n.methods.head
|
||||
case http.MethodOptions:
|
||||
return n.methods.options
|
||||
case http.MethodPatch:
|
||||
return n.methods.patch
|
||||
case http.MethodPost:
|
||||
return n.methods.post
|
||||
case PROPFIND:
|
||||
return n.methods.propfind
|
||||
case http.MethodPut:
|
||||
return n.methods.put
|
||||
case http.MethodTrace:
|
||||
return n.methods.trace
|
||||
case REPORT:
|
||||
return n.methods.report
|
||||
default: // RouteNotFound/404 is not considered as a handler
|
||||
return n.methods.anyOther[method]
|
||||
}
|
||||
}
|
||||
|
||||
func optionsMethodHandler(allowMethods string) func(c Context) error {
|
||||
return func(c Context) error {
|
||||
// Note: we are not handling most of the CORS headers here. CORS is handled by CORS middleware
|
||||
// 'OPTIONS' method RFC: https://httpwg.org/specs/rfc7231.html#OPTIONS
|
||||
// 'Allow' header RFC: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
|
||||
c.Response().Header().Add(HeaderAllow, allowMethods)
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// Find lookup a handler registered for method and path. It also parses URL for path
|
||||
// parameters and load them into context.
|
||||
//
|
||||
// For performance:
|
||||
//
|
||||
// - Get context from `Echo#AcquireContext()`
|
||||
// - Reset it `Context#Reset()`
|
||||
// - Return it `Echo#ReleaseContext()`.
|
||||
func (r *Router) Find(method, path string, c Context) {
|
||||
ctx := c.(*context)
|
||||
currentNode := r.tree // Current node as root
|
||||
|
||||
var (
|
||||
previousBestMatchNode *node
|
||||
matchedRouteMethod *routeMethod
|
||||
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
|
||||
// and search value gets shorter and shorter.
|
||||
search = path
|
||||
searchIndex = 0
|
||||
paramIndex int // Param counter
|
||||
paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
|
||||
)
|
||||
|
||||
// Backtracking is needed when a dead end (leaf node) is reached in the router tree.
|
||||
// To backtrack the current node will be changed to the parent node and the next kind for the
|
||||
// router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
|
||||
// For example if there is no static node match we should check parent next sibling by kind (param).
|
||||
// Backtracking itself does not check if there is a next sibling, this is done by the router logic.
|
||||
backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
|
||||
previous := currentNode
|
||||
currentNode = previous.parent
|
||||
valid = currentNode != nil
|
||||
|
||||
// Next node type by priority
|
||||
if previous.kind == anyKind {
|
||||
nextNodeKind = staticKind
|
||||
} else {
|
||||
nextNodeKind = previous.kind + 1
|
||||
}
|
||||
|
||||
if fromKind == staticKind {
|
||||
// when backtracking is done from static kind block we did not change search so nothing to restore
|
||||
return
|
||||
}
|
||||
|
||||
// restore search to value it was before we move to current node we are backtracking from.
|
||||
if previous.kind == staticKind {
|
||||
searchIndex -= len(previous.prefix)
|
||||
} else {
|
||||
paramIndex--
|
||||
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
|
||||
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
|
||||
searchIndex -= len(paramValues[paramIndex])
|
||||
paramValues[paramIndex] = ""
|
||||
}
|
||||
search = path[searchIndex:]
|
||||
return
|
||||
}
|
||||
|
||||
// Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
|
||||
// Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
|
||||
// Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
|
||||
// Search order/priority is: static > param > any.
|
||||
//
|
||||
// Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
|
||||
// and hoping to (goto statement) next block by priority to check if it is the match.
|
||||
for {
|
||||
prefixLen := 0 // Prefix length
|
||||
lcpLen := 0 // LCP (longest common prefix) length
|
||||
|
||||
if currentNode.kind == staticKind {
|
||||
searchLen := len(search)
|
||||
prefixLen = len(currentNode.prefix)
|
||||
|
||||
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
|
||||
max := prefixLen
|
||||
if searchLen < max {
|
||||
max = searchLen
|
||||
}
|
||||
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
|
||||
}
|
||||
}
|
||||
|
||||
if lcpLen != prefixLen {
|
||||
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
|
||||
nk, ok := backtrackToNextNodeKind(staticKind)
|
||||
if !ok {
|
||||
return // No other possibilities on the decision path, handler will be whatever context is reset to.
|
||||
} else if nk == paramKind {
|
||||
goto Param
|
||||
// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
|
||||
//} else if nk == anyKind {
|
||||
// goto Any
|
||||
} else {
|
||||
// Not found (this should never be possible for static node we are looking currently)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The full prefix has matched, remove the prefix from the remaining search
|
||||
search = search[lcpLen:]
|
||||
searchIndex = searchIndex + lcpLen
|
||||
|
||||
// Finish routing if is no request path remaining to search
|
||||
if search == "" {
|
||||
// in case of node that is handler we have exact method type match or something for 405 to use
|
||||
if currentNode.isHandler {
|
||||
// check if current node has handler registered for http method we are looking for. we store currentNode as
|
||||
// best matching in case we do no find no more routes matching this path+method
|
||||
if previousBestMatchNode == nil {
|
||||
previousBestMatchNode = currentNode
|
||||
}
|
||||
if h := currentNode.findMethod(method); h != nil {
|
||||
matchedRouteMethod = h
|
||||
break
|
||||
}
|
||||
} else if currentNode.notFoundHandler != nil {
|
||||
matchedRouteMethod = currentNode.notFoundHandler
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Static node
|
||||
if search != "" {
|
||||
if child := currentNode.findStaticChild(search[0]); child != nil {
|
||||
currentNode = child
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Param:
|
||||
// Param node
|
||||
if child := currentNode.paramChild; search != "" && child != nil {
|
||||
currentNode = child
|
||||
i := 0
|
||||
l := len(search)
|
||||
if currentNode.isLeaf {
|
||||
// when param node does not have any children (path param is last piece of route path) then param node should
|
||||
// act similarly to any node - consider all remaining search as match
|
||||
i = l
|
||||
} else {
|
||||
for ; i < l && search[i] != '/'; i++ {
|
||||
}
|
||||
}
|
||||
|
||||
paramValues[paramIndex] = search[:i]
|
||||
paramIndex++
|
||||
search = search[i:]
|
||||
searchIndex = searchIndex + i
|
||||
continue
|
||||
}
|
||||
|
||||
Any:
|
||||
// Any node
|
||||
if child := currentNode.anyChild; child != nil {
|
||||
// If any node is found, use remaining path for paramValues
|
||||
currentNode = child
|
||||
paramValues[currentNode.paramsCount-1] = search
|
||||
|
||||
// update indexes/search in case we need to backtrack when no handler match is found
|
||||
paramIndex++
|
||||
searchIndex += +len(search)
|
||||
search = ""
|
||||
|
||||
if h := currentNode.findMethod(method); h != nil {
|
||||
matchedRouteMethod = h
|
||||
break
|
||||
}
|
||||
// we store currentNode as best matching in case we do not find more routes matching this path+method. Needed for 405
|
||||
if previousBestMatchNode == nil {
|
||||
previousBestMatchNode = currentNode
|
||||
}
|
||||
if currentNode.notFoundHandler != nil {
|
||||
matchedRouteMethod = currentNode.notFoundHandler
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Let's backtrack to the first possible alternative node of the decision path
|
||||
nk, ok := backtrackToNextNodeKind(anyKind)
|
||||
if !ok {
|
||||
break // No other possibilities on the decision path
|
||||
} else if nk == paramKind {
|
||||
goto Param
|
||||
} else if nk == anyKind {
|
||||
goto Any
|
||||
} else {
|
||||
// Not found
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if currentNode == nil && previousBestMatchNode == nil {
|
||||
return // nothing matched at all
|
||||
}
|
||||
|
||||
// matchedHandler could be method+path handler that we matched or notFoundHandler from node with matching path
|
||||
// user provided not found (404) handler has priority over generic method not found (405) handler or global 404 handler
|
||||
var rPath string
|
||||
var rPNames []string
|
||||
if matchedRouteMethod != nil {
|
||||
rPath = matchedRouteMethod.ppath
|
||||
rPNames = matchedRouteMethod.pnames
|
||||
ctx.handler = matchedRouteMethod.handler
|
||||
} else {
|
||||
// use previous match as basis. although we have no matching handler we have path match.
|
||||
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
|
||||
currentNode = previousBestMatchNode
|
||||
|
||||
rPath = currentNode.originalPath
|
||||
rPNames = nil // no params here
|
||||
ctx.handler = NotFoundHandler
|
||||
if currentNode.notFoundHandler != nil {
|
||||
rPath = currentNode.notFoundHandler.ppath
|
||||
rPNames = currentNode.notFoundHandler.pnames
|
||||
ctx.handler = currentNode.notFoundHandler.handler
|
||||
} else if currentNode.isHandler {
|
||||
ctx.Set(ContextKeyHeaderAllow, currentNode.methods.allowHeader)
|
||||
ctx.handler = MethodNotAllowedHandler
|
||||
if method == http.MethodOptions {
|
||||
ctx.handler = optionsMethodHandler(currentNode.methods.allowHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.path = rPath
|
||||
ctx.pnames = rPNames
|
||||
}
|
||||
Reference in New Issue
Block a user