diff --git a/go.mod b/go.mod index 053f3d858..149d3267b 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/deepmap/oapi-codegen v1.3.12 github.com/getkin/kin-openapi v0.13.0 github.com/gobwas/glob v0.2.3 - github.com/golang-jwt/jwt v3.2.0+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.5 github.com/google/uuid v1.3.0 @@ -28,7 +28,7 @@ require ( github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c github.com/julienschmidt/httprouter v1.3.0 github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b - github.com/labstack/echo/v4 v4.1.11 + github.com/labstack/echo/v4 v4.5.0 github.com/openshift-online/ocm-sdk-go v0.1.198 github.com/prometheus/client_golang v1.10.0 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 60ebbb454..f9f9d49fc 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-jwt/jwt v3.2.0+incompatible h1:cy0jZQ1aewnxirUHoalEYhE2zxzE7JqR9YQPWhEKzXc= -github.com/golang-jwt/jwt v3.2.0+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -419,8 +419,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -438,8 +439,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -600,8 +602,9 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vmware/govmomi v0.23.0 h1:DC97v1FdSr3cPfq3eBKD5C1O4JtYxo+NTcbGTKe2k48= github.com/vmware/govmomi v0.23.0/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= @@ -730,6 +733,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -807,6 +811,8 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -825,6 +831,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/vendor/github.com/golang-jwt/jwt/.gitignore b/vendor/github.com/golang-jwt/jwt/.gitignore index 80bed650e..09573e016 100644 --- a/vendor/github.com/golang-jwt/jwt/.gitignore +++ b/vendor/github.com/golang-jwt/jwt/.gitignore @@ -1,4 +1,4 @@ .DS_Store bin - +.idea/ diff --git a/vendor/github.com/golang-jwt/jwt/.travis.yml b/vendor/github.com/golang-jwt/jwt/.travis.yml deleted file mode 100644 index 1027f56cd..000000000 --- a/vendor/github.com/golang-jwt/jwt/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: go - -script: - - go vet ./... - - go test -v ./... - -go: - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - 1.7 - - tip diff --git a/vendor/github.com/golang-jwt/jwt/LICENSE b/vendor/github.com/golang-jwt/jwt/LICENSE index df83a9c2f..35dbc2520 100644 --- a/vendor/github.com/golang-jwt/jwt/LICENSE +++ b/vendor/github.com/golang-jwt/jwt/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2012 Dave Grijalva +Copyright (c) 2021 golang-jwt maintainers 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: diff --git a/vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md b/vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md index 7fc1f793c..c4efbd2a8 100644 --- a/vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md +++ b/vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md @@ -1,97 +1,22 @@ -## Migration Guide from v2 -> v3 +## Migration Guide (v3.2.1) -Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code. +Starting from [v3.2.1](https://github.com/golang-jwt/jwt/releases/tag/v3.2.1]), the import path has changed from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt`. Future releases will be using the `github.com/golang-jwt/jwt` import path and continue the existing versioning scheme of `v3.x.x+incompatible`. Backwards-compatible patches and fixes will be done on the `v3` release branch, where as new build-breaking features will be developed in a `v4` release, possibly including a SIV-style import path. -### `Token.Claims` is now an interface type +### go.mod replacement -The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`. +In a first step, the easiest way is to use `go mod edit` to issue a replacement. -`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property. - -The old example for parsing a token looked like this.. - -```go - if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { - fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) - } +``` +go mod edit -replace github.com/dgrijalva/jwt-go=github.com/golang-jwt/jwt@v3.2.1+incompatible +go mod tidy ``` -is now directly mapped to... +This will still keep the old import path in your code but replace it with the new package and also introduce a new indirect dependency to `github.com/golang-jwt/jwt`. Try to compile your project; it should still work. -```go - if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { - claims := token.Claims.(jwt.MapClaims) - fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) - } -``` +### Cleanup -`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type. +If your code still consistently builds, you can replace all occurences of `github.com/dgrijalva/jwt-go` with `github.com/golang-jwt/jwt`, either manually or by using tools such as `sed`. Finally, the `replace` directive in the `go.mod` file can be removed. -```go - type MyCustomClaims struct { - User string - *StandardClaims - } - - if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil { - claims := token.Claims.(*MyCustomClaims) - fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt) - } -``` +## Older releases (before v3.2.0) -### `ParseFromRequest` has been moved - -To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`. - -`Extractors` do the work of picking the token string out of a request. The interface is simple and composable. - -This simple parsing example: - -```go - if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil { - fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) - } -``` - -is directly mapped to: - -```go - if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil { - claims := token.Claims.(jwt.MapClaims) - fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) - } -``` - -There are several concrete `Extractor` types provided for your convenience: - -* `HeaderExtractor` will search a list of headers until one contains content. -* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content. -* `MultiExtractor` will try a list of `Extractors` in order until one returns content. -* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token. -* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument -* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header - - -### RSA signing methods no longer accept `[]byte` keys - -Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse. - -To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types. - -```go - func keyLookupFunc(*Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - - // Look up key - key, err := lookupPublicKey(token.Header["kid"]) - if err != nil { - return nil, err - } - - // Unpack key from PEM encoded PKCS8 - return jwt.ParseRSAPublicKeyFromPEM(key) - } -``` +The original migration guide for older releases can be found at https://github.com/dgrijalva/jwt-go/blob/master/MIGRATION_GUIDE.md. \ No newline at end of file diff --git a/vendor/github.com/golang-jwt/jwt/README.md b/vendor/github.com/golang-jwt/jwt/README.md index d358d881b..9b653e46b 100644 --- a/vendor/github.com/golang-jwt/jwt/README.md +++ b/vendor/github.com/golang-jwt/jwt/README.md @@ -1,25 +1,34 @@ # jwt-go -[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) -[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go) +[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt) -A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) +A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519). -**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3. +**IMPORT PATH CHANGE:** Starting from [v3.2.1](https://github.com/golang-jwt/jwt/releases/tag/v3.2.1), the import path has changed from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt`. After the original author of the library suggested migrating the maintenance of `jwt-go`, a dedicated team of open source maintainers decided to clone the existing library into this repository. See [dgrijalva/jwt-go#462](https://github.com/dgrijalva/jwt-go/issues/462) for a detailed discussion on this topic. -**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail. +Future releases will be using the `github.com/golang-jwt/jwt` import path and continue the existing versioning scheme of `v3.x.x+incompatible`. Backwards-compatible patches and fixes will be done on the `v3` release branch, where as new build-breaking features will be developed in a `v4` release, possibly including a SIV-style import path. -**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. +**SECURITY NOTICE:** Some older versions of Go have a security issue in the crypto/elliptic. Recommendation is to upgrade to at least 1.15 See issue [dgrijalva/jwt-go#216](https://github.com/dgrijalva/jwt-go/issues/216) for more detail. + +**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. + +### Supported Go versions + +Our support of Go versions is aligned with Go's [version release policy](https://golang.org/doc/devel/release#policy). +So we will support a major version of Go until there are two newer major releases. +We no longer support building jwt-go with unsupported Go versions, as these contain security vulnerabilities +which will not be fixed. ## What the heck is a JWT? JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens. -In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. +In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](https://datatracker.ietf.org/doc/html/rfc4648) encoded. The last part is the signature, encoded the same way. The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used. -The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own. +The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) for information about reserved keys and the proper way to add your own. ## What's in the box? @@ -27,31 +36,31 @@ This library supports the parsing and verification as well as the generation and ## Examples -See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage: +See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage: -* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac) -* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac) -* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples) +* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac) +* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac) +* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples) ## Extensions This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. -Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go +Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go ## Compliance -This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: +This library was last reviewed to comply with [RTF 7519](https://datatracker.ietf.org/doc/html/rfc7519) dated May 2015 with a few notable differences: -* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. +* In order to protect against accidental use of [Unsecured JWTs](https://datatracker.ietf.org/doc/html/rfc7519#section-6), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. ## Project Status & Versioning This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). -This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). +This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `main`. Periodically, versions will be tagged from `main`. You can find all the releases on [the project releases page](https://github.com/golang-jwt/jwt/releases). -While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning. +While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/golang-jwt/jwt.v3`. It will do the right thing WRT semantic versioning. **BREAKING CHANGES:*** * Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. @@ -79,9 +88,9 @@ Asymmetric signing methods, such as RSA, use different keys for signing and veri Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: -* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation -* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation -* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation +* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation +* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation +* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation ### JWT and OAuth @@ -93,8 +102,12 @@ Without going too far down the rabbit hole, here's a description of the interact * OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. * Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. +### Troubleshooting + +This library uses descriptive error messages whenever possible. If you are not getting the expected result, have a look at the errors. The most common place people get stuck is providing the correct type of key to the parser. See the above section on signing methods and key types. + ## More -Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). +Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt). The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. diff --git a/vendor/github.com/golang-jwt/jwt/VERSION_HISTORY.md b/vendor/github.com/golang-jwt/jwt/VERSION_HISTORY.md index 637029831..637f2ba61 100644 --- a/vendor/github.com/golang-jwt/jwt/VERSION_HISTORY.md +++ b/vendor/github.com/golang-jwt/jwt/VERSION_HISTORY.md @@ -1,5 +1,18 @@ ## `jwt-go` Version History +#### 3.2.2 + +* Starting from this release, we are adopting the policy to support the most 2 recent versions of Go currently available. By the time of this release, this is Go 1.15 and 1.16 ([#28](https://github.com/golang-jwt/jwt/pull/28)). +* Fixed a potential issue that could occur when the verification of `exp`, `iat` or `nbf` was not required and contained invalid contents, i.e. non-numeric/date. Thanks for @thaJeztah for making us aware of that and @giorgos-f3 for originally reporting it to the formtech fork ([#40](https://github.com/golang-jwt/jwt/pull/40)). +* Added support for EdDSA / ED25519 ([#36](https://github.com/golang-jwt/jwt/pull/36)). +* Optimized allocations ([#33](https://github.com/golang-jwt/jwt/pull/33)). + +#### 3.2.1 + +* **Import Path Change**: See MIGRATION_GUIDE.md for tips on updating your code + * Changed the import path from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt` +* Fixed type confusing issue between `string` and `[]string` in `VerifyAudience` ([#12](https://github.com/golang-jwt/jwt/pull/12)). This fixes CVE-2020-26160 + #### 3.2.0 * Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation @@ -115,4 +128,4 @@ It is likely the only integration change required here will be to change `func(t * First versioned release * API stabilized * Supports creating, signing, parsing, and validating JWT tokens -* Supports RS256 and HS256 signing methods \ No newline at end of file +* Supports RS256 and HS256 signing methods diff --git a/vendor/github.com/golang-jwt/jwt/claims.go b/vendor/github.com/golang-jwt/jwt/claims.go index f0228f02e..f1dba3cb9 100644 --- a/vendor/github.com/golang-jwt/jwt/claims.go +++ b/vendor/github.com/golang-jwt/jwt/claims.go @@ -35,18 +35,18 @@ func (c StandardClaims) Valid() error { // The claims below are optional, by default, so if they are set to the // default value in Go, let's not fail the verification for them. - if c.VerifyExpiresAt(now, false) == false { + if !c.VerifyExpiresAt(now, false) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired } - if c.VerifyIssuedAt(now, false) == false { + if !c.VerifyIssuedAt(now, false) { vErr.Inner = fmt.Errorf("Token used before issued") vErr.Errors |= ValidationErrorIssuedAt } - if c.VerifyNotBefore(now, false) == false { + if !c.VerifyNotBefore(now, false) { vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } @@ -61,7 +61,7 @@ func (c StandardClaims) Valid() error { // Compares the aud claim against cmp. // If required is false, this method will return true if the value matches or is unset func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { - return verifyAud(c.Audience, cmp, req) + return verifyAud([]string{c.Audience}, cmp, req) } // Compares the exp claim against cmp. @@ -90,15 +90,27 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { // ----- helpers -func verifyAud(aud string, cmp string, required bool) bool { - if aud == "" { +func verifyAud(aud []string, cmp string, required bool) bool { + if len(aud) == 0 { return !required } - if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { - return true - } else { - return false + // use a var here to keep constant time compare when looping over a number of claims + result := false + + var stringClaims string + for _, a := range aud { + if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 { + result = true + } + stringClaims = stringClaims + a } + + // case where "" is sent in one or many aud claims + if len(stringClaims) == 0 { + return !required + } + + return result } func verifyExp(exp int64, now int64, required bool) bool { diff --git a/vendor/github.com/golang-jwt/jwt/ecdsa.go b/vendor/github.com/golang-jwt/jwt/ecdsa.go index f97738124..15e23435d 100644 --- a/vendor/github.com/golang-jwt/jwt/ecdsa.go +++ b/vendor/github.com/golang-jwt/jwt/ecdsa.go @@ -88,11 +88,11 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa hasher.Write([]byte(signingString)) // Verify the signature - if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { + if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus { return nil - } else { - return ErrECDSAVerification } + + return ErrECDSAVerification } // Implements the Sign method from SigningMethod @@ -128,18 +128,12 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string keyBytes += 1 } - // We serialize the outpus (r and s) into big-endian byte arrays and pad - // them with zeros on the left to make sure the sizes work out. Both arrays - // must be keyBytes long, and the output must be 2*keyBytes long. - rBytes := r.Bytes() - rBytesPadded := make([]byte, keyBytes) - copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) - - sBytes := s.Bytes() - sBytesPadded := make([]byte, keyBytes) - copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) - - out := append(rBytesPadded, sBytesPadded...) + // We serialize the outputs (r and s) into big-endian byte arrays + // padded with zeros on the left to make sure the sizes work out. + // Output must be 2*keyBytes long. + out := make([]byte, 2*keyBytes) + r.FillBytes(out[0:keyBytes]) // r is assigned to the first half of output. + s.FillBytes(out[keyBytes:]) // s is assigned to the second half of output. return EncodeSegment(out), nil } else { diff --git a/vendor/github.com/golang-jwt/jwt/ecdsa_utils.go b/vendor/github.com/golang-jwt/jwt/ecdsa_utils.go index d19624b72..db9f4be7d 100644 --- a/vendor/github.com/golang-jwt/jwt/ecdsa_utils.go +++ b/vendor/github.com/golang-jwt/jwt/ecdsa_utils.go @@ -25,7 +25,9 @@ func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { // Parse the key var parsedKey interface{} if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { - return nil, err + if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { + return nil, err + } } var pkey *ecdsa.PrivateKey diff --git a/vendor/github.com/golang-jwt/jwt/ed25519.go b/vendor/github.com/golang-jwt/jwt/ed25519.go new file mode 100644 index 000000000..a2f8ddbe9 --- /dev/null +++ b/vendor/github.com/golang-jwt/jwt/ed25519.go @@ -0,0 +1,81 @@ +package jwt + +import ( + "errors" + + "crypto/ed25519" +) + +var ( + ErrEd25519Verification = errors.New("ed25519: verification error") +) + +// Implements the EdDSA family +// Expects ed25519.PrivateKey for signing and ed25519.PublicKey for verification +type SigningMethodEd25519 struct{} + +// Specific instance for EdDSA +var ( + SigningMethodEdDSA *SigningMethodEd25519 +) + +func init() { + SigningMethodEdDSA = &SigningMethodEd25519{} + RegisterSigningMethod(SigningMethodEdDSA.Alg(), func() SigningMethod { + return SigningMethodEdDSA + }) +} + +func (m *SigningMethodEd25519) Alg() string { + return "EdDSA" +} + +// Implements the Verify method from SigningMethod +// For this verify method, key must be an ed25519.PublicKey +func (m *SigningMethodEd25519) Verify(signingString, signature string, key interface{}) error { + var err error + var ed25519Key ed25519.PublicKey + var ok bool + + if ed25519Key, ok = key.(ed25519.PublicKey); !ok { + return ErrInvalidKeyType + } + + if len(ed25519Key) != ed25519.PublicKeySize { + return ErrInvalidKey + } + + // Decode the signature + var sig []byte + if sig, err = DecodeSegment(signature); err != nil { + return err + } + + // Verify the signature + if !ed25519.Verify(ed25519Key, []byte(signingString), sig) { + return ErrEd25519Verification + } + + return nil +} + +// Implements the Sign method from SigningMethod +// For this signing method, key must be an ed25519.PrivateKey +func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (string, error) { + var ed25519Key ed25519.PrivateKey + var ok bool + + if ed25519Key, ok = key.(ed25519.PrivateKey); !ok { + return "", ErrInvalidKeyType + } + + // ed25519.Sign panics if private key not equal to ed25519.PrivateKeySize + // this allows to avoid recover usage + if len(ed25519Key) != ed25519.PrivateKeySize { + return "", ErrInvalidKey + } + + // Sign the string and return the encoded result + sig := ed25519.Sign(ed25519Key, []byte(signingString)) + return EncodeSegment(sig), nil +} diff --git a/vendor/github.com/golang-jwt/jwt/ed25519_utils.go b/vendor/github.com/golang-jwt/jwt/ed25519_utils.go new file mode 100644 index 000000000..c6357275e --- /dev/null +++ b/vendor/github.com/golang-jwt/jwt/ed25519_utils.go @@ -0,0 +1,64 @@ +package jwt + +import ( + "crypto" + "crypto/ed25519" + "crypto/x509" + "encoding/pem" + "errors" +) + +var ( + ErrNotEdPrivateKey = errors.New("Key is not a valid Ed25519 private key") + ErrNotEdPublicKey = errors.New("Key is not a valid Ed25519 public key") +) + +// Parse PEM-encoded Edwards curve private key +func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { + return nil, err + } + + var pkey ed25519.PrivateKey + var ok bool + if pkey, ok = parsedKey.(ed25519.PrivateKey); !ok { + return nil, ErrNotEdPrivateKey + } + + return pkey, nil +} + +// Parse PEM-encoded Edwards curve public key +func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + return nil, err + } + + var pkey ed25519.PublicKey + var ok bool + if pkey, ok = parsedKey.(ed25519.PublicKey); !ok { + return nil, ErrNotEdPublicKey + } + + return pkey, nil +} diff --git a/vendor/github.com/golang-jwt/jwt/map_claims.go b/vendor/github.com/golang-jwt/jwt/map_claims.go index 291213c46..72c79f92e 100644 --- a/vendor/github.com/golang-jwt/jwt/map_claims.go +++ b/vendor/github.com/golang-jwt/jwt/map_claims.go @@ -10,37 +10,59 @@ import ( // This is the default claims type if you don't supply one type MapClaims map[string]interface{} -// Compares the aud claim against cmp. +// VerifyAudience Compares the aud claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyAudience(cmp string, req bool) bool { - aud, _ := m["aud"].(string) + var aud []string + switch v := m["aud"].(type) { + case string: + aud = append(aud, v) + case []string: + aud = v + case []interface{}: + for _, a := range v { + vs, ok := a.(string) + if !ok { + return false + } + aud = append(aud, vs) + } + } return verifyAud(aud, cmp, req) } // Compares the exp claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { - switch exp := m["exp"].(type) { + exp, ok := m["exp"] + if !ok { + return !req + } + switch expType := exp.(type) { case float64: - return verifyExp(int64(exp), cmp, req) + return verifyExp(int64(expType), cmp, req) case json.Number: - v, _ := exp.Int64() + v, _ := expType.Int64() return verifyExp(v, cmp, req) } - return req == false + return false } // Compares the iat claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { - switch iat := m["iat"].(type) { + iat, ok := m["iat"] + if !ok { + return !req + } + switch iatType := iat.(type) { case float64: - return verifyIat(int64(iat), cmp, req) + return verifyIat(int64(iatType), cmp, req) case json.Number: - v, _ := iat.Int64() + v, _ := iatType.Int64() return verifyIat(v, cmp, req) } - return req == false + return false } // Compares the iss claim against cmp. @@ -53,14 +75,18 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // Compares the nbf claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { - switch nbf := m["nbf"].(type) { + nbf, ok := m["nbf"] + if !ok { + return !req + } + switch nbfType := nbf.(type) { case float64: - return verifyNbf(int64(nbf), cmp, req) + return verifyNbf(int64(nbfType), cmp, req) case json.Number: - v, _ := nbf.Int64() + v, _ := nbfType.Int64() return verifyNbf(v, cmp, req) } - return req == false + return false } // Validates time based claims "exp, iat, nbf". @@ -71,17 +97,17 @@ func (m MapClaims) Valid() error { vErr := new(ValidationError) now := TimeFunc().Unix() - if m.VerifyExpiresAt(now, false) == false { + if !m.VerifyExpiresAt(now, false) { vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } - if m.VerifyIssuedAt(now, false) == false { + if !m.VerifyIssuedAt(now, false) { vErr.Inner = errors.New("Token used before issued") vErr.Errors |= ValidationErrorIssuedAt } - if m.VerifyNotBefore(now, false) == false { + if !m.VerifyNotBefore(now, false) { vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } diff --git a/vendor/github.com/golang-jwt/jwt/rsa_pss.go b/vendor/github.com/golang-jwt/jwt/rsa_pss.go index 10ee9db8a..c01470864 100644 --- a/vendor/github.com/golang-jwt/jwt/rsa_pss.go +++ b/vendor/github.com/golang-jwt/jwt/rsa_pss.go @@ -12,9 +12,14 @@ import ( type SigningMethodRSAPSS struct { *SigningMethodRSA Options *rsa.PSSOptions + // VerifyOptions is optional. If set overrides Options for rsa.VerifyPPS. + // Used to accept tokens signed with rsa.PSSSaltLengthAuto, what doesn't follow + // https://tools.ietf.org/html/rfc7518#section-3.5 but was used previously. + // See https://github.com/dgrijalva/jwt-go/issues/285#issuecomment-437451244 for details. + VerifyOptions *rsa.PSSOptions } -// Specific instances for RS/PS and company +// Specific instances for RS/PS and company. var ( SigningMethodPS256 *SigningMethodRSAPSS SigningMethodPS384 *SigningMethodRSAPSS @@ -24,13 +29,15 @@ var ( func init() { // PS256 SigningMethodPS256 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ + SigningMethodRSA: &SigningMethodRSA{ Name: "PS256", Hash: crypto.SHA256, }, - &rsa.PSSOptions{ + Options: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }, + VerifyOptions: &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA256, }, } RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { @@ -39,13 +46,15 @@ func init() { // PS384 SigningMethodPS384 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ + SigningMethodRSA: &SigningMethodRSA{ Name: "PS384", Hash: crypto.SHA384, }, - &rsa.PSSOptions{ + Options: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }, + VerifyOptions: &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA384, }, } RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { @@ -54,13 +63,15 @@ func init() { // PS512 SigningMethodPS512 = &SigningMethodRSAPSS{ - &SigningMethodRSA{ + SigningMethodRSA: &SigningMethodRSA{ Name: "PS512", Hash: crypto.SHA512, }, - &rsa.PSSOptions{ + Options: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }, + VerifyOptions: &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, - Hash: crypto.SHA512, }, } RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { @@ -94,7 +105,12 @@ func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interf hasher := m.Hash.New() hasher.Write([]byte(signingString)) - return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) + opts := m.Options + if m.VerifyOptions != nil { + opts = m.VerifyOptions + } + + return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, opts) } // Implements the Sign method from SigningMethod diff --git a/vendor/github.com/golang-jwt/jwt/rsa_utils.go b/vendor/github.com/golang-jwt/jwt/rsa_utils.go index a5ababf95..14c78c292 100644 --- a/vendor/github.com/golang-jwt/jwt/rsa_utils.go +++ b/vendor/github.com/golang-jwt/jwt/rsa_utils.go @@ -8,7 +8,7 @@ import ( ) var ( - ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") + ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be a PEM encoded PKCS1 or PKCS8 key") ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key") ) diff --git a/vendor/github.com/golang-jwt/jwt/token.go b/vendor/github.com/golang-jwt/jwt/token.go index d637e0867..6b30ced12 100644 --- a/vendor/github.com/golang-jwt/jwt/token.go +++ b/vendor/github.com/golang-jwt/jwt/token.go @@ -65,7 +65,7 @@ func (t *Token) SignedString(key interface{}) (string, error) { func (t *Token) SigningString() (string, error) { var err error parts := make([]string, 2) - for i, _ := range parts { + for i := range parts { var jsonValue []byte if i == 0 { if jsonValue, err = json.Marshal(t.Header); err != nil { @@ -95,14 +95,10 @@ func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token // Encode JWT specific base64url encoding with padding stripped func EncodeSegment(seg []byte) string { - return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") + return base64.RawURLEncoding.EncodeToString(seg) } // Decode JWT specific base64url encoding with padding stripped func DecodeSegment(seg string) ([]byte, error) { - if l := len(seg) % 4; l > 0 { - seg += strings.Repeat("=", 4-l) - } - - return base64.URLEncoding.DecodeString(seg) + return base64.RawURLEncoding.DecodeString(seg) } diff --git a/vendor/github.com/labstack/echo/v4/.gitignore b/vendor/github.com/labstack/echo/v4/.gitignore index dd74acca4..dbadf3bd0 100644 --- a/vendor/github.com/labstack/echo/v4/.gitignore +++ b/vendor/github.com/labstack/echo/v4/.gitignore @@ -5,3 +5,4 @@ vendor .idea *.iml *.out +.vscode diff --git a/vendor/github.com/labstack/echo/v4/.travis.yml b/vendor/github.com/labstack/echo/v4/.travis.yml index 3897356dd..67d45ad78 100644 --- a/vendor/github.com/labstack/echo/v4/.travis.yml +++ b/vendor/github.com/labstack/echo/v4/.travis.yml @@ -1,7 +1,11 @@ +arch: + - amd64 + - ppc64le + language: go go: - - 1.11.x - - 1.12.x + - 1.14.x + - 1.15.x - tip env: - GO111MODULE=on diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md new file mode 100644 index 000000000..02eb36fd4 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md @@ -0,0 +1,167 @@ +# Changelog + +## 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 diff --git a/vendor/github.com/labstack/echo/v4/LICENSE b/vendor/github.com/labstack/echo/v4/LICENSE index b5b006b4e..c46d0105f 100644 --- a/vendor/github.com/labstack/echo/v4/LICENSE +++ b/vendor/github.com/labstack/echo/v4/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 LabStack +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 diff --git a/vendor/github.com/labstack/echo/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile index dfcb6c02b..48061f7e2 100644 --- a/vendor/github.com/labstack/echo/v4/Makefile +++ b/vendor/github.com/labstack/echo/v4/Makefile @@ -1,3 +1,34 @@ +PKG := "github.com/labstack/echo" +PKG_LIST := $(shell go list ${PKG}/...) + tag: @git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'` @git tag|grep -v ^v + +.DEFAULT_GOAL := check +check: lint vet race ## Check project + +init: + @go get -u golang.org/x/lint/golint + +lint: ## Lint the files + @golint -set_exit_status ${PKG_LIST} + +vet: ## Vet the files + @go vet ${PKG_LIST} + +test: ## Run tests + @go test -short ${PKG_LIST} + +race: ## Run tests with data race detector + @go test -race ${PKG_LIST} + +benchmark: ## Run benchmarks + @go test -run="-" -bench=".*" ${PKG_LIST} + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +goversion ?= "1.15" +test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15 + @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check" diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md index 0da031225..4dec531a2 100644 --- a/vendor/github.com/labstack/echo/v4/README.md +++ b/vendor/github.com/labstack/echo/v4/README.md @@ -1,12 +1,12 @@ [![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge) -[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4) [![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo) [![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo) [![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo) [![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo) -[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com) +[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions) [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) @@ -17,7 +17,7 @@ Therefore a Go version capable of understanding /vN suffixed imports is required - 1.9.7+ - 1.10.3+ -- 1.11+ +- 1.14+ Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended way of using Echo going forward. @@ -42,14 +42,24 @@ For older versions, please use the latest v3 tag. ## Benchmarks -Date: 2018/03/15
+Date: 2020/11/11
Source: https://github.com/vishr/web-framework-benchmark
Lower is better! - + + + +The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz ## [Guide](https://echo.labstack.com/guide) +### Installation + +```sh +// go get github.com/labstack/echo/{version} +go get github.com/labstack/echo/v4 +``` + ### Example ```go @@ -84,7 +94,7 @@ func hello(c echo.Context) error { ## Help -- [Forum](https://forum.labstack.com) +- [Forum](https://github.com/labstack/echo/discussions) - [Chat](https://gitter.im/labstack/echo) ## Contribute diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go index ef04eaaf3..fdf0524c2 100644 --- a/vendor/github.com/labstack/echo/v4/bind.go +++ b/vendor/github.com/labstack/echo/v4/bind.go @@ -2,7 +2,6 @@ package echo import ( "encoding" - "encoding/json" "encoding/xml" "errors" "fmt" @@ -30,10 +29,8 @@ type ( } ) -// Bind implements the `Binder#Bind` function. -func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { - req := c.Request() - +// BindPathParams binds path params to bindable object +func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error { names := c.ParamNames() values := c.ParamValues() params := map[string][]string{} @@ -43,22 +40,38 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { if err := b.bindData(i, params, "param"); err != nil { return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) } - if err = b.bindData(i, c.QueryParams(), "query"); err != nil { + return nil +} + +// BindQueryParams binds query params to bindable object +func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error { + if err := b.bindData(i, c.QueryParams(), "query"); err != nil { return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) } + return nil +} + +// BindBody binds request body contents to bindable object +// NB: then binding forms take note that this implementation uses standard library form parsing +// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm +// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm +// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm +func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { + req := c.Request() if req.ContentLength == 0 { return } + ctype := req.Header.Get(HeaderContentType) switch { case strings.HasPrefix(ctype, MIMEApplicationJSON): - if err = json.NewDecoder(req.Body).Decode(i); err != nil { - 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) + 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) } - return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) } case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML): if err = xml.NewDecoder(req.Body).Decode(i); err != nil { @@ -80,45 +93,89 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { default: return ErrUnsupportedMediaType } - return + return nil } -func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error { - if ptr == nil || len(data) == 0 { +// BindHeaders binds HTTP headers to a bindable object +func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error { + if err := b.bindData(i, c.Request().Header, "header"); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + return nil +} + +// Bind implements the `Binder#Bind` function. +// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous +// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams. +func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { + if err := b.BindPathParams(c, i); err != nil { + return err + } + // Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH) + // Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that. + // i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding. + // This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body + if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete { + if err = b.BindQueryParams(c, i); err != nil { + return err + } + } + return b.BindBody(c, i) +} + +// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag +func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error { + if destination == nil || len(data) == 0 { return nil } - typ := reflect.TypeOf(ptr).Elem() - val := reflect.ValueOf(ptr).Elem() + typ := reflect.TypeOf(destination).Elem() + val := reflect.ValueOf(destination).Elem() - if m, ok := ptr.(*map[string]interface{}); ok { + // Map + if typ.Kind() == reflect.Map { for k, v := range data { - (*m)[k] = v[0] + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) } return nil } + // !struct if typ.Kind() != reflect.Struct { + if tag == "param" || tag == "query" || tag == "header" { + // incompatible type, data is probably to be found in the body + return nil + } return errors.New("binding element must be a struct") } for i := 0; i < typ.NumField(); i++ { typeField := typ.Field(i) structField := val.Field(i) + if typeField.Anonymous { + if structField.Kind() == reflect.Ptr { + structField = structField.Elem() + } + } if !structField.CanSet() { continue } structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get(tag) + if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" { + // if anonymous struct with query/param/form tags, report an error + return errors.New("query/param/form tags are not allowed with anonymous struct field") + } if inputFieldName == "" { - inputFieldName = typeField.Name - // If tag is nil, we inspect if the field is a struct. - if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct { + // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags). + // structs that implement BindUnmarshaler are binded only when they have explicit tag + if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct { if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil { return err } - continue } + // does not have explicit tag and is not an ordinary struct - so move to next field + continue } inputValue, exists := data[inputFieldName] @@ -127,9 +184,8 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag // 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. - inputFieldName = strings.ToLower(inputFieldName) for k, v := range data { - if strings.ToLower(k) == inputFieldName { + if strings.EqualFold(k, inputFieldName) { inputValue = v exists = true break @@ -219,40 +275,13 @@ func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bo } } -// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler -func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) { - ptr := reflect.New(field.Type()) - if ptr.CanInterface() { - iface := ptr.Interface() - if unmarshaler, ok := iface.(BindUnmarshaler); ok { - return unmarshaler, ok - } - } - return nil, false -} - -// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler -func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) { - ptr := reflect.New(field.Type()) - if ptr.CanInterface() { - iface := ptr.Interface() - if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok { - return unmarshaler, ok - } - } - return nil, false -} - func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { - if unmarshaler, ok := bindUnmarshaler(field); ok { - err := unmarshaler.UnmarshalParam(value) - field.Set(reflect.ValueOf(unmarshaler).Elem()) - return true, err + fieldIValue := field.Addr().Interface() + if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok { + return true, unmarshaler.UnmarshalParam(value) } - if unmarshaler, ok := textUnmarshaler(field); ok { - err := unmarshaler.UnmarshalText([]byte(value)) - field.Set(reflect.ValueOf(unmarshaler).Elem()) - return true, err + if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok { + return true, unmarshaler.UnmarshalText([]byte(value)) } return false, nil diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go new file mode 100644 index 000000000..0900ce8dc --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/binder.go @@ -0,0 +1,1230 @@ +package echo + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +/** + Following functions provide handful of methods for binding to Go native types from request query or path parameters. + * QueryParamsBinder(c) - binds query parameters (source URL) + * PathParamsBinder(c) - binds path parameters (source URL) + * FormFieldBinder(c) - binds form fields (source URL + body) + + Example: + ```go + var length int64 + err := echo.QueryParamsBinder(c).Int64("length", &length).BindError() + ``` + + For every supported type there are following methods: + * ("param", &destination) - if parameter value exists then binds it to given destination of that type i.e Int64(...). + * Must("param", &destination) - parameter value is required to exist, binds it to given destination of that type i.e MustInt64(...). + * s("param", &destination) - (for slices) if parameter values exists then binds it to given destination of that type i.e Int64s(...). + * Musts("param", &destination) - (for slices) parameter value is required to exist, binds it to given destination of that type i.e MustInt64s(...). + + for some slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done + i.e. URL `/api/search?id=1,2,3&id=1` can be bind to `[]int64{1,2,3,1}` + + `FailFast` flags binder to stop binding after first bind error during binder call chain. Enabled by default. + `BindError()` returns first bind error from binder and resets errors in binder. Useful along with `FailFast()` method + to do binding and returns on first problem + `BindErrors()` returns all bind errors from binder and resets errors in binder. + + Types that are supported: + * bool + * float32 + * float64 + * int + * int8 + * int16 + * int32 + * int64 + * uint + * uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{}) + * uint16 + * uint32 + * uint64 + * string + * time + * duration + * BindUnmarshaler() interface + * UnixTime() - converts unix time (integer) to time.Time + * UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time + * CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error` +*/ + +// BindingError represents an error that occurred while binding request data. +type BindingError struct { + // Field is the field name where value binding failed + Field string `json:"field"` + // Values of parameter that failed to bind. + Values []string `json:"-"` + *HTTPError +} + +// NewBindingError creates new instance of binding error +func NewBindingError(sourceParam string, values []string, message interface{}, internalError error) error { + return &BindingError{ + Field: sourceParam, + Values: values, + HTTPError: &HTTPError{ + Code: http.StatusBadRequest, + Message: message, + Internal: internalError, + }, + } +} + +// Error returns error message +func (be *BindingError) Error() string { + return fmt.Sprintf("%s, field=%s", be.HTTPError.Error(), be.Field) +} + +// ValueBinder provides utility methods for binding query or path parameter to various Go built-in types +type ValueBinder struct { + // failFast is flag for binding methods to return without attempting to bind when previous binding already failed + failFast bool + errors []error + + // ValueFunc is used to get single parameter (first) value from request + ValueFunc func(sourceParam string) string + // ValuesFunc is used to get all values for parameter from request. i.e. `/api/search?ids=1&ids=2` + ValuesFunc func(sourceParam string) []string + // ErrorFunc is used to create errors. Allows you to use your own error type, that for example marshals to your specific json response + ErrorFunc func(sourceParam string, values []string, message interface{}, internalError error) error +} + +// QueryParamsBinder creates query parameter value binder +func QueryParamsBinder(c Context) *ValueBinder { + return &ValueBinder{ + failFast: true, + ValueFunc: c.QueryParam, + ValuesFunc: func(sourceParam string) []string { + values, ok := c.QueryParams()[sourceParam] + if !ok { + return nil + } + return values + }, + ErrorFunc: NewBindingError, + } +} + +// PathParamsBinder creates path parameter value binder +func PathParamsBinder(c Context) *ValueBinder { + return &ValueBinder{ + failFast: true, + ValueFunc: c.Param, + ValuesFunc: func(sourceParam string) []string { + // path parameter should not have multiple values so getting values does not make sense but lets not error out here + value := c.Param(sourceParam) + if value == "" { + return nil + } + return []string{value} + }, + ErrorFunc: NewBindingError, + } +} + +// FormFieldBinder creates form field value binder +// For all requests, FormFieldBinder parses the raw query from the URL and uses query params as form fields +// +// For POST, PUT, and PATCH requests, it also reads the request body, parses it +// as a form and uses query params as form fields. Request body parameters take precedence over URL query +// string values in r.Form. +// +// NB: when 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 https://golang.org/pkg/net/http/#Request.ParseForm +func FormFieldBinder(c Context) *ValueBinder { + vb := &ValueBinder{ + failFast: true, + ValueFunc: func(sourceParam string) string { + return c.Request().FormValue(sourceParam) + }, + ErrorFunc: NewBindingError, + } + vb.ValuesFunc = func(sourceParam string) []string { + if c.Request().Form == nil { + // this is same as `Request().FormValue()` does internally + _ = c.Request().ParseMultipartForm(32 << 20) + } + values, ok := c.Request().Form[sourceParam] + if !ok { + return nil + } + return values + } + + return vb +} + +// FailFast set internal flag to indicate if binding methods will return early (without binding) when previous bind failed +// NB: call this method before any other binding methods as it modifies binding methods behaviour +func (b *ValueBinder) FailFast(value bool) *ValueBinder { + b.failFast = value + return b +} + +func (b *ValueBinder) setError(err error) { + if b.errors == nil { + b.errors = []error{err} + return + } + b.errors = append(b.errors, err) +} + +// BindError returns first seen bind error and resets/empties binder errors for further calls +func (b *ValueBinder) BindError() error { + if b.errors == nil { + return nil + } + err := b.errors[0] + b.errors = nil // reset errors so next chain will start from zero + return err +} + +// BindErrors returns all bind errors and resets/empties binder errors for further calls +func (b *ValueBinder) BindErrors() []error { + if b.errors == nil { + return nil + } + errors := b.errors + b.errors = nil // reset errors so next chain will start from zero + return errors +} + +// CustomFunc binds parameter values with Func. Func is called only when parameter values exist. +func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder { + return b.customFunc(sourceParam, customFunc, false) +} + +// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist. +func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder { + return b.customFunc(sourceParam, customFunc, true) +} + +func (b *ValueBinder) customFunc(sourceParam string, customFunc func(values []string) []error, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + if errs := customFunc(values); errs != nil { + b.errors = append(b.errors, errs...) + } + return b +} + +// String binds parameter to string variable +func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + return b + } + *dest = value + return b +} + +// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist +func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + return b + } + *dest = value + return b +} + +// Strings binds parameter values to slice of string +func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValuesFunc(sourceParam) + if value == nil { + return b + } + *dest = value + return b +} + +// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist +func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValuesFunc(sourceParam) + if value == nil { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + return b + } + *dest = value + return b +} + +// BindUnmarshaler binds parameter to destination implementing BindUnmarshaler interface +func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + return b + } + + if err := dest.UnmarshalParam(tmp); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to BindUnmarshaler interface", err)) + } + return b +} + +// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface. +// Returns error when value does not exist +func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + return b + } + + if err := dest.UnmarshalParam(value); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to BindUnmarshaler interface", err)) + } + return b +} + +// BindWithDelimiter binds parameter to destination by suitable conversion function. +// Delimiter is used before conversion to split parameter value to separate values +func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { + return b.bindWithDelimiter(sourceParam, dest, delimiter, false) +} + +// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function. +// Delimiter is used before conversion to split parameter value to separate values +func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { + return b.bindWithDelimiter(sourceParam, dest, delimiter, true) +} + +func (b *ValueBinder) bindWithDelimiter(sourceParam string, dest interface{}, delimiter string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + tmpValues := make([]string, 0, len(values)) + for _, v := range values { + tmpValues = append(tmpValues, strings.Split(v, delimiter)...) + } + + switch d := dest.(type) { + case *[]string: + *d = tmpValues + return b + case *[]bool: + return b.bools(sourceParam, tmpValues, d) + case *[]int64, *[]int32, *[]int16, *[]int8, *[]int: + return b.ints(sourceParam, tmpValues, d) + case *[]uint64, *[]uint32, *[]uint16, *[]uint8, *[]uint: // *[]byte is same as *[]uint8 + return b.uints(sourceParam, tmpValues, d) + case *[]float64, *[]float32: + return b.floats(sourceParam, tmpValues, d) + case *[]time.Duration: + return b.durations(sourceParam, tmpValues, d) + default: + // support only cases when destination is slice + // does not support time.Time as it needs argument (layout) for parsing or BindUnmarshaler + b.setError(b.ErrorFunc(sourceParam, []string{}, "unsupported bind type", nil)) + return b + } +} + +// Int64 binds parameter to int64 variable +func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder { + return b.intValue(sourceParam, dest, 64, false) +} + +// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder { + return b.intValue(sourceParam, dest, 64, true) +} + +// Int32 binds parameter to int32 variable +func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder { + return b.intValue(sourceParam, dest, 32, false) +} + +// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder { + return b.intValue(sourceParam, dest, 32, true) +} + +// Int16 binds parameter to int16 variable +func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder { + return b.intValue(sourceParam, dest, 16, false) +} + +// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder { + return b.intValue(sourceParam, dest, 16, true) +} + +// Int8 binds parameter to int8 variable +func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder { + return b.intValue(sourceParam, dest, 8, false) +} + +// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder { + return b.intValue(sourceParam, dest, 8, true) +} + +// Int binds parameter to int variable +func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder { + return b.intValue(sourceParam, dest, 0, false) +} + +// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist +func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder { + return b.intValue(sourceParam, dest, 0, true) +} + +func (b *ValueBinder) intValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.int(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) int(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseInt(value, 10, bitSize) + if err != nil { + if bitSize == 0 { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to int", err)) + } else { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to int%v", bitSize), err)) + } + return b + } + + switch d := dest.(type) { + case *int64: + *d = n + case *int32: + *d = int32(n) + case *int16: + *d = int16(n) + case *int8: + *d = int8(n) + case *int: + *d = int(n) + } + return b +} + +func (b *ValueBinder) intsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil)) + } + return b + } + return b.ints(sourceParam, values, dest) +} + +func (b *ValueBinder) ints(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]int64: + tmp := make([]int64, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int32: + tmp := make([]int32, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int16: + tmp := make([]int16, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 16) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int8: + tmp := make([]int8, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 8) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int: + tmp := make([]int, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 0) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Int64s binds parameter to slice of int64 +func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int32s binds parameter to slice of int32 +func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int16s binds parameter to slice of int16 +func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int8s binds parameter to slice of int8 +func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Ints binds parameter to slice of int +func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Uint64 binds parameter to uint64 variable +func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder { + return b.uintValue(sourceParam, dest, 64, false) +} + +// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder { + return b.uintValue(sourceParam, dest, 64, true) +} + +// Uint32 binds parameter to uint32 variable +func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder { + return b.uintValue(sourceParam, dest, 32, false) +} + +// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder { + return b.uintValue(sourceParam, dest, 32, true) +} + +// Uint16 binds parameter to uint16 variable +func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder { + return b.uintValue(sourceParam, dest, 16, false) +} + +// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder { + return b.uintValue(sourceParam, dest, 16, true) +} + +// Uint8 binds parameter to uint8 variable +func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, false) +} + +// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, true) +} + +// Byte binds parameter to byte variable +func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, false) +} + +// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist +func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, true) +} + +// Uint binds parameter to uint variable +func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder { + return b.uintValue(sourceParam, dest, 0, false) +} + +// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist +func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder { + return b.uintValue(sourceParam, dest, 0, true) +} + +func (b *ValueBinder) uintValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.uint(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) uint(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseUint(value, 10, bitSize) + if err != nil { + if bitSize == 0 { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to uint", err)) + } else { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to uint%v", bitSize), err)) + } + return b + } + + switch d := dest.(type) { + case *uint64: + *d = n + case *uint32: + *d = uint32(n) + case *uint16: + *d = uint16(n) + case *uint8: // byte is alias to uint8 + *d = uint8(n) + case *uint: + *d = uint(n) + } + return b +} + +func (b *ValueBinder) uintsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil)) + } + return b + } + return b.uints(sourceParam, values, dest) +} + +func (b *ValueBinder) uints(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]uint64: + tmp := make([]uint64, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint32: + tmp := make([]uint32, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint16: + tmp := make([]uint16, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 16) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint8: // byte is alias to uint8 + tmp := make([]uint8, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 8) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint: + tmp := make([]uint, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 0) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Uint64s binds parameter to slice of uint64 +func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint32s binds parameter to slice of uint32 +func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint16s binds parameter to slice of uint16 +func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint8s binds parameter to slice of uint8 +func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uints binds parameter to slice of uint +func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Bool binds parameter to bool variable +func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder { + return b.boolValue(sourceParam, dest, false) +} + +// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist +func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder { + return b.boolValue(sourceParam, dest, true) +} + +func (b *ValueBinder) boolValue(sourceParam string, dest *bool, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.bool(sourceParam, value, dest) +} + +func (b *ValueBinder) bool(sourceParam string, value string, dest *bool) *ValueBinder { + n, err := strconv.ParseBool(value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to bool", err)) + return b + } + + *dest = n + return b +} + +func (b *ValueBinder) boolsValue(sourceParam string, dest *[]bool, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.bools(sourceParam, values, dest) +} + +func (b *ValueBinder) bools(sourceParam string, values []string, dest *[]bool) *ValueBinder { + tmp := make([]bool, len(values)) + for i, v := range values { + b.bool(sourceParam, v, &tmp[i]) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// Bools binds parameter values to slice of bool variables +func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder { + return b.boolsValue(sourceParam, dest, false) +} + +// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist +func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder { + return b.boolsValue(sourceParam, dest, true) +} + +// Float64 binds parameter to float64 variable +func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder { + return b.floatValue(sourceParam, dest, 64, false) +} + +// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist +func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder { + return b.floatValue(sourceParam, dest, 64, true) +} + +// Float32 binds parameter to float32 variable +func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder { + return b.floatValue(sourceParam, dest, 32, false) +} + +// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist +func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder { + return b.floatValue(sourceParam, dest, 32, true) +} + +func (b *ValueBinder) floatValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.float(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) float(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseFloat(value, bitSize) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to float%v", bitSize), err)) + return b + } + + switch d := dest.(type) { + case *float64: + *d = n + case *float32: + *d = float32(n) + } + return b +} + +func (b *ValueBinder) floatsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.floats(sourceParam, values, dest) +} + +func (b *ValueBinder) floats(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]float64: + tmp := make([]float64, len(values)) + for i, v := range values { + b.float(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]float32: + tmp := make([]float32, len(values)) + for i, v := range values { + b.float(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Float64s binds parameter values to slice of float64 variables +func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder { + return b.floatsValue(sourceParam, dest, false) +} + +// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist +func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder { + return b.floatsValue(sourceParam, dest, true) +} + +// Float32s binds parameter values to slice of float32 variables +func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder { + return b.floatsValue(sourceParam, dest, false) +} + +// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist +func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder { + return b.floatsValue(sourceParam, dest, true) +} + +// Time binds parameter to time.Time variable +func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *ValueBinder { + return b.time(sourceParam, dest, layout, false) +} + +// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist +func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder { + return b.time(sourceParam, dest, layout, true) +} + +func (b *ValueBinder) time(sourceParam string, dest *time.Time, layout string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + t, err := time.Parse(layout, value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err)) + return b + } + *dest = t + return b +} + +// Times binds parameter values to slice of time.Time variables +func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string) *ValueBinder { + return b.times(sourceParam, dest, layout, false) +} + +// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist +func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder { + return b.times(sourceParam, dest, layout, true) +} + +func (b *ValueBinder) times(sourceParam string, dest *[]time.Time, layout string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + tmp := make([]time.Time, len(values)) + for i, v := range values { + t, err := time.Parse(layout, v) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Time", err)) + if b.failFast { + return b + } + continue + } + tmp[i] = t + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// Duration binds parameter to time.Duration variable +func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBinder { + return b.duration(sourceParam, dest, false) +} + +// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist +func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder { + return b.duration(sourceParam, dest, true) +} + +func (b *ValueBinder) duration(sourceParam string, dest *time.Duration, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + t, err := time.ParseDuration(value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Duration", err)) + return b + } + *dest = t + return b +} + +// Durations binds parameter values to slice of time.Duration variables +func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *ValueBinder { + return b.durationsValue(sourceParam, dest, false) +} + +// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist +func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder { + return b.durationsValue(sourceParam, dest, true) +} + +func (b *ValueBinder) durationsValue(sourceParam string, dest *[]time.Duration, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.durations(sourceParam, values, dest) +} + +func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]time.Duration) *ValueBinder { + tmp := make([]time.Duration, len(values)) + for i, v := range values { + t, err := time.ParseDuration(v) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Duration", err)) + if b.failFast { + return b + } + continue + } + tmp[i] = t + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// UnixTime binds parameter to time.Time variable (in local Time corresponding to the given Unix time). +// +// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 +// +// Note: +// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, false, false) +} + +// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding +// to the given Unix time). Returns error when value does not exist. +// +// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 +// +// Note: +// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, true, false) +} + +// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision). +// +// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 +// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00 +// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 +// +// Note: +// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. +func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, false, true) +} + +// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding +// to the given Unix time value in nano second precision). Returns error when value does not exist. +// +// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 +// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00 +// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 +// +// Note: +// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. +func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, true, true) +} + +func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + + n, err := strconv.ParseInt(value, 10, 64) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err)) + return b + } + + if isNano { + *dest = time.Unix(0, n) + } else { + *dest = time.Unix(n, 0) + } + return b +} diff --git a/vendor/github.com/labstack/echo/v4/codecov.yml b/vendor/github.com/labstack/echo/v4/codecov.yml new file mode 100644 index 000000000..0fa3a3f18 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + threshold: 1% + patch: + default: + threshold: 1% + +comment: + require_changes: true \ No newline at end of file diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go index e0f4cc00f..91ab6e480 100644 --- a/vendor/github.com/labstack/echo/v4/context.go +++ b/vendor/github.com/labstack/echo/v4/context.go @@ -2,7 +2,6 @@ package echo import ( "bytes" - "encoding/json" "encoding/xml" "fmt" "io" @@ -43,6 +42,7 @@ type ( // 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. @@ -183,6 +183,9 @@ type ( // Logger returns the `Logger` instance. Logger() Logger + // Set the logger + SetLogger(l Logger) + // Echo returns the `Echo` instance. Echo() *Echo @@ -202,6 +205,7 @@ type ( handler HandlerFunc store Map echo *Echo + logger Logger lock sync.RWMutex } ) @@ -241,7 +245,7 @@ func (c *context) IsTLS() bool { func (c *context) IsWebSocket() bool { upgrade := c.request.Header.Get(HeaderUpgrade) - return strings.ToLower(upgrade) == "websocket" + return strings.EqualFold(upgrade, "websocket") } func (c *context) Scheme() string { @@ -266,8 +270,16 @@ func (c *context) Scheme() string { } 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 != "" { - return strings.Split(ip, ", ")[0] + i := strings.IndexAny(ip, ",") + if i > 0 { + return strings.TrimSpace(ip[:i]) + } + return ip } if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { return ip @@ -301,6 +313,19 @@ func (c *context) ParamNames() []string { func (c *context) SetParamNames(names ...string) { c.pnames = names + + l := len(names) + if *c.echo.maxParam < l { + *c.echo.maxParam = l + } + + if len(c.pvalues) < l { + // Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them, + // probably those values will be overriden in a Context#SetParamValues + newPvalues := make([]string, l) + copy(newPvalues, c.pvalues) + c.pvalues = newPvalues + } } func (c *context) ParamValues() []string { @@ -308,7 +333,15 @@ func (c *context) ParamValues() []string { } func (c *context) SetParamValues(values ...string) { - c.pvalues = values + // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times + // It will brake the Router#Find code + limit := len(values) + if limit > *c.echo.maxParam { + limit = *c.echo.maxParam + } + for i := 0; i < limit; i++ { + c.pvalues[i] = values[i] + } } func (c *context) QueryParam(name string) string { @@ -347,8 +380,12 @@ func (c *context) FormParams() (url.Values, error) { } func (c *context) FormFile(name string) (*multipart.FileHeader, error) { - _, fh, err := c.request.FormFile(name) - return fh, err + 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) { @@ -419,17 +456,16 @@ func (c *context) String(code int, s string) (err error) { } func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) { - enc := json.NewEncoder(c.response) - _, pretty := c.QueryParams()["pretty"] - if c.echo.Debug || pretty { - enc.SetIndent("", " ") + 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 = enc.Encode(i); err != nil { + if err = c.echo.JSONSerializer.Serialize(c, i, indent); err != nil { return } if _, err = c.response.Write([]byte(");")); err != nil { @@ -439,13 +475,9 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error } func (c *context) json(code int, i interface{}, indent string) error { - enc := json.NewEncoder(c.response) - if indent != "" { - enc.SetIndent("", indent) - } c.writeContentType(MIMEApplicationJSONCharsetUTF8) c.response.Status = code - return enc.Encode(i) + return c.echo.JSONSerializer.Serialize(c, i, indent) } func (c *context) JSON(code int, i interface{}) (err error) { @@ -597,9 +629,17 @@ func (c *context) SetHandler(h HandlerFunc) { } 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) @@ -608,6 +648,9 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) { c.store = nil c.path = "" c.pnames = nil + c.logger = nil // NOTE: Don't reset because it has to have length c.echo.maxParam at all times - // c.pvalues = nil + for i := 0; i < *c.echo.maxParam; i++ { + c.pvalues[i] = "" + } } diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go index 459c89882..246a62256 100644 --- a/vendor/github.com/labstack/echo/v4/echo.go +++ b/vendor/github.com/labstack/echo/v4/echo.go @@ -48,7 +48,7 @@ import ( "net" "net/http" "net/url" - "path" + "os" "path/filepath" "reflect" "runtime" @@ -59,12 +59,17 @@ import ( "github.com/labstack/gommon/log" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" ) type ( // Echo is the top-level framework instance. Echo struct { common + // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get + // listener address info (on which interface/port was listener binded) without having data races. + startupMutex sync.RWMutex StdLogger *stdLog.Logger colorer *color.Color premiddleware []MiddlewareFunc @@ -85,9 +90,12 @@ type ( HidePort bool HTTPErrorHandler HTTPErrorHandler Binder Binder + JSONSerializer JSONSerializer Validator Validator Renderer Renderer Logger Logger + IPExtractor IPExtractor + ListenerNetwork string } // Route contains a handler and information for matching against requests. @@ -118,6 +126,12 @@ type ( Validate(i interface{}) error } + // JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. + JSONSerializer interface { + Serialize(c Context, i interface{}, indent string) error + Deserialize(c Context, i interface{}) error + } + // Renderer is the interface that wraps the Render function. Renderer interface { Render(io.Writer, string, interface{}, Context) error @@ -227,7 +241,7 @@ const ( const ( // Version of Echo - Version = "4.1.11" + Version = "4.5.0" website = "https://echo.labstack.com" // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo banner = ` @@ -277,6 +291,7 @@ var ( ErrInvalidRedirectCode = errors.New("invalid redirect status code") ErrCookieNotFound = errors.New("cookie not found") ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte") + ErrInvalidListenerNetwork = errors.New("invalid listener network") ) // Error handlers @@ -298,14 +313,16 @@ func New() (e *Echo) { AutoTLSManager: autocert.Manager{ Prompt: autocert.AcceptTOS, }, - Logger: log.New("echo"), - colorer: color.New(), - maxParam: new(int), + Logger: log.New("echo"), + colorer: color.New(), + maxParam: new(int), + ListenerNetwork: "tcp", } e.Server.Handler = e e.TLSServer.Handler = e e.HTTPErrorHandler = e.DefaultHTTPErrorHandler e.Binder = &DefaultBinder{} + e.JSONSerializer = &DefaultJSONSerializer{} e.Logger.SetLevel(log.ERROR) e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0) e.pool.New = func() interface{} { @@ -354,10 +371,16 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { Message: http.StatusText(http.StatusInternalServerError), } } - if e.Debug { - he.Message = err.Error() - } else if m, ok := he.Message.(string); ok { - he.Message = Map{"message": m} + + // Issue #1426 + code := he.Code + message := he.Message + if m, ok := he.Message.(string); ok { + if e.Debug { + message = Map{"message": m, "error": err.Error()} + } else { + message = Map{"message": m} + } } // Send response @@ -365,7 +388,7 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { if c.Request().Method == http.MethodHead { // Issue #608 err = c.NoContent(he.Code) } else { - err = c.JSON(he.Code, he.Message) + err = c.JSON(code, message) } if err != nil { e.Logger.Error(err) @@ -472,11 +495,31 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl if err != nil { return err } - name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security + + name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security + fi, err := os.Stat(name) + if err != nil { + // The access path does not exist + return NotFoundHandler(c) + } + + // If the request is for a directory and does not end with "/" + p = c.Request().URL.Path // path must not be empty. + if fi.IsDir() && p[len(p)-1] != '/' { + // Redirect to ends with "/" + return c.Redirect(http.StatusMovedPermanently, p+"/") + } return c.File(name) } - if prefix == "/" { - return get(prefix+"*", h) + // Handle added routes based on trailing slash: + // /prefix => exact route "/prefix" + any route "/prefix/*" + // /prefix/ => only any route "/prefix/*" + if prefix != "" { + if prefix[len(prefix)-1] == '/' { + // Only add any route for intentional trailing slash + return get(prefix+"*", h) + } + get(prefix, h) } return get(prefix+"/*", h) } @@ -497,11 +540,7 @@ func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware .. name := handlerName(handler) router := e.findRouter(host) router.Add(method, path, func(c Context) error { - h := handler - // Chain middleware - for i := len(middleware) - 1; i >= 0; i-- { - h = middleware[i](h) - } + h := applyMiddleware(handler, middleware...) return h(c) }) r := &Route{ @@ -553,7 +592,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string { for _, r := range e.router.routes { if r.Name == name { for i, l := 0, len(r.Path); i < l; i++ { - if r.Path[i] == ':' && n < ln { + if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln { for ; i < l && r.Path[i] != '/'; i++ { } uri.WriteString(fmt.Sprintf("%v", params[n])) @@ -595,16 +634,15 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Acquire context c := e.pool.Get().(*context) c.Reset(r, w) - h := NotFoundHandler if e.premiddleware == nil { - e.findRouter(r.Host).Find(r.Method, getPath(r), c) + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) h = c.Handler() h = applyMiddleware(h, e.middleware...) } else { h = func(c Context) error { - e.findRouter(r.Host).Find(r.Method, getPath(r), c) + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) h := c.Handler() h = applyMiddleware(h, e.middleware...) return h(c) @@ -623,21 +661,30 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Start starts an HTTP server. func (e *Echo) Start(address string) error { + e.startupMutex.Lock() e.Server.Addr = address - return e.StartServer(e.Server) + if err := e.configureServer(e.Server); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return e.Server.Serve(e.Listener) } // StartTLS starts an HTTPS server. // If `certFile` or `keyFile` is `string` the values are treated as file paths. // If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is. func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) { + e.startupMutex.Lock() var cert []byte if cert, err = filepathOrContent(certFile); err != nil { + e.startupMutex.Unlock() return } var key []byte if key, err = filepathOrContent(keyFile); err != nil { + e.startupMutex.Unlock() return } @@ -645,10 +692,17 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro s.TLSConfig = new(tls.Config) s.TLSConfig.Certificates = make([]tls.Certificate, 1) if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { + e.startupMutex.Unlock() return } - return e.startTLS(address) + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) } func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { @@ -664,24 +718,45 @@ func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { // StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org. func (e *Echo) StartAutoTLS(address string) error { + e.startupMutex.Lock() s := e.TLSServer s.TLSConfig = new(tls.Config) s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto) - return e.startTLS(address) + + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) } -func (e *Echo) startTLS(address string) error { +func (e *Echo) configureTLS(address string) { s := e.TLSServer s.Addr = address if !e.DisableHTTP2 { s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2") } - return e.StartServer(e.TLSServer) } // StartServer starts a custom http server. func (e *Echo) StartServer(s *http.Server) (err error) { + e.startupMutex.Lock() + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + if s.TLSConfig != nil { + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) +} + +func (e *Echo) configureServer(s *http.Server) (err error) { // Setup e.colorer.SetOutput(e.Logger.Output()) s.ErrorLog = e.StdLogger @@ -696,7 +771,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) { if s.TLSConfig == nil { if e.Listener == nil { - e.Listener, err = newListener(s.Addr) + e.Listener, err = newListener(s.Addr, e.ListenerNetwork) if err != nil { return err } @@ -704,10 +779,10 @@ func (e *Echo) StartServer(s *http.Server) (err error) { if !e.HidePort { e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) } - return s.Serve(e.Listener) + return nil } if e.TLSListener == nil { - l, err := newListener(s.Addr) + l, err := newListener(s.Addr, e.ListenerNetwork) if err != nil { return err } @@ -716,12 +791,65 @@ func (e *Echo) StartServer(s *http.Server) (err error) { if !e.HidePort { e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) } - return s.Serve(e.TLSListener) + return nil +} + +// ListenerAddr returns net.Addr for Listener +func (e *Echo) ListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.Listener == nil { + return nil + } + return e.Listener.Addr() +} + +// TLSListenerAddr returns net.Addr for TLSListener +func (e *Echo) TLSListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.TLSListener == nil { + return nil + } + return e.TLSListener.Addr() +} + +// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext). +func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) { + e.startupMutex.Lock() + // Setup + s := e.Server + s.Addr = address + e.colorer.SetOutput(e.Logger.Output()) + s.ErrorLog = e.StdLogger + s.Handler = h2c.NewHandler(e, h2s) + if e.Debug { + e.Logger.SetLevel(log.DEBUG) + } + + if !e.HideBanner { + e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website)) + } + + if e.Listener == nil { + e.Listener, err = newListener(s.Addr, e.ListenerNetwork) + if err != nil { + e.startupMutex.Unlock() + return err + } + } + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) } // Close immediately stops the server. // It internally calls `http.Server#Close()`. func (e *Echo) Close() error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() if err := e.TLSServer.Close(); err != nil { return err } @@ -731,6 +859,8 @@ func (e *Echo) Close() error { // Shutdown stops the server gracefully. // It internally calls `http.Server#Shutdown()`. func (e *Echo) Shutdown(ctx stdContext.Context) error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() if err := e.TLSServer.Shutdown(ctx); err != nil { return err } @@ -748,6 +878,9 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError { // Error makes it compatible with `error` interface. func (he *HTTPError) Error() string { + if he.Internal == nil { + return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) + } return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal) } @@ -757,6 +890,11 @@ func (he *HTTPError) SetInternal(err error) *HTTPError { return he } +// Unwrap satisfies the Go 1.13 error wrapper interface. +func (he *HTTPError) Unwrap() error { + return he.Internal +} + // WrapHandler wraps `http.Handler` into `echo.HandlerFunc`. func WrapHandler(h http.Handler) HandlerFunc { return func(c Context) error { @@ -779,7 +917,11 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc { } } -func getPath(r *http.Request) string { +// GetPath returns RawPath, if it's empty returns Path from URL +// Difference between RawPath and Path is: +// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/. +// * RawPath is an optional field which only gets set if the default encoding is different from Path. +func GetPath(r *http.Request) string { path := r.URL.RawPath if path == "" { path = r.URL.Path @@ -822,14 +964,18 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { return } else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil { return - } else if err = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute); err != nil { - return } + // Ignore error from setting the KeepAlivePeriod as some systems, such as + // OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP + _ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute) return } -func newListener(address string) (*tcpKeepAliveListener, error) { - l, err := net.Listen("tcp", address) +func newListener(address, network string) (*tcpKeepAliveListener, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, ErrInvalidListenerNetwork + } + l, err := net.Listen(network, address) if err != nil { return nil, err } diff --git a/vendor/github.com/labstack/echo/v4/go.mod b/vendor/github.com/labstack/echo/v4/go.mod index f088ba6e4..9cd3529bd 100644 --- a/vendor/github.com/labstack/echo/v4/go.mod +++ b/vendor/github.com/labstack/echo/v4/go.mod @@ -1,11 +1,16 @@ module github.com/labstack/echo/v4 -go 1.12 +go 1.15 require ( - github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/labstack/gommon v0.3.0 + github.com/mattn/go-colorable v0.1.8 // indirect github.com/stretchr/testify v1.4.0 - github.com/valyala/fasttemplate v1.0.1 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + github.com/valyala/fasttemplate v1.2.1 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 ) diff --git a/vendor/github.com/labstack/echo/v4/go.sum b/vendor/github.com/labstack/echo/v4/go.sum index 5aedb2e22..027e96600 100644 --- a/vendor/github.com/labstack/echo/v4/go.sum +++ b/vendor/github.com/labstack/echo/v4/go.sum @@ -1,14 +1,16 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -16,19 +18,30 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go index 5d9582535..426bef9eb 100644 --- a/vendor/github.com/labstack/echo/v4/group.go +++ b/vendor/github.com/labstack/echo/v4/group.go @@ -109,7 +109,7 @@ func (g *Group) Static(prefix, root string) { // File implements `Echo#File()` for sub-routes within the Group. func (g *Group) File(path, file string) { - g.file(g.prefix+path, file, g.GET) + g.file(path, file, g.GET) } // Add implements `Echo#Add()` for sub-routes within the Group. diff --git a/vendor/github.com/labstack/echo/v4/ip.go b/vendor/github.com/labstack/echo/v4/ip.go new file mode 100644 index 000000000..39cb421fd --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/ip.go @@ -0,0 +1,137 @@ +package echo + +import ( + "net" + "net/http" + "strings" +) + +type ipChecker struct { + trustLoopback bool + trustLinkLocal bool + trustPrivateNet bool + trustExtraRanges []*net.IPNet +} + +// TrustOption is config for which IP address to trust +type TrustOption func(*ipChecker) + +// TrustLoopback configures if you trust loopback address (default: true). +func TrustLoopback(v bool) TrustOption { + return func(c *ipChecker) { + c.trustLoopback = v + } +} + +// TrustLinkLocal configures if you trust link-local address (default: true). +func TrustLinkLocal(v bool) TrustOption { + return func(c *ipChecker) { + c.trustLinkLocal = v + } +} + +// TrustPrivateNet configures if you trust private network address (default: true). +func TrustPrivateNet(v bool) TrustOption { + return func(c *ipChecker) { + c.trustPrivateNet = v + } +} + +// TrustIPRange add trustable IP ranges using CIDR notation. +func TrustIPRange(ipRange *net.IPNet) TrustOption { + return func(c *ipChecker) { + c.trustExtraRanges = append(c.trustExtraRanges, ipRange) + } +} + +func newIPChecker(configs []TrustOption) *ipChecker { + checker := &ipChecker{trustLoopback: true, trustLinkLocal: true, trustPrivateNet: true} + for _, configure := range configs { + configure(checker) + } + return checker +} + +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 func(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 := ExtractIPDirect()(req) + realIP := req.Header.Get(HeaderXRealIP) + if realIP != "" { + if ip := net.ParseIP(directIP); ip != nil && checker.trust(ip) { + 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 := ExtractIPDirect()(req) + xffs := req.Header[HeaderXForwardedFor] + if len(xffs) == 0 { + return directIP + } + ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP) + for i := len(ips) - 1; i >= 0; i-- { + ip := net.ParseIP(strings.TrimSpace(ips[i])) + if ip == nil { + // Unable to parse IP; cannot trust entire records + return directIP + } + if !checker.trust(ip) { + return ip.String() + } + } + // All of the IPs are trusted; return first element because it is furthest from server (best effort strategy). + return strings.TrimSpace(ips[0]) + } +} diff --git a/vendor/github.com/labstack/echo/v4/json.go b/vendor/github.com/labstack/echo/v4/json.go new file mode 100644 index 000000000..16b2d0577 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/json.go @@ -0,0 +1,31 @@ +package echo + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// DefaultJSONSerializer implements JSON encoding using encoding/json. +type DefaultJSONSerializer struct{} + +// Serialize converts an interface into a json and writes it to the response. +// You can optionally use the indent parameter to produce pretty JSONs. +func (d DefaultJSONSerializer) Serialize(c Context, i interface{}, indent string) error { + enc := json.NewEncoder(c.Response()) + if indent != "" { + enc.SetIndent("", indent) + } + return enc.Encode(i) +} + +// Deserialize reads a JSON from a request body and converts it into an interface. +func (d DefaultJSONSerializer) Deserialize(c Context, i interface{}) error { + err := json.NewDecoder(c.Request().Body).Decode(i) + if ute, ok := err.(*json.UnmarshalTypeError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err) + } else if se, ok := err.(*json.SyntaxError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err) + } + return err +} diff --git a/vendor/github.com/labstack/echo/v4/response.go b/vendor/github.com/labstack/echo/v4/response.go index ca7405c5d..84f7c9e7e 100644 --- a/vendor/github.com/labstack/echo/v4/response.go +++ b/vendor/github.com/labstack/echo/v4/response.go @@ -56,11 +56,11 @@ func (r *Response) WriteHeader(code int) { r.echo.Logger.Warn("response already committed") return } + r.Status = code for _, fn := range r.beforeFuncs { fn() } - r.Status = code - r.Writer.WriteHeader(code) + r.Writer.WriteHeader(r.Status) r.Committed = true } diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go index 70bf409f9..5b2474b32 100644 --- a/vendor/github.com/labstack/echo/v4/router.go +++ b/vendor/github.com/labstack/echo/v4/router.go @@ -1,6 +1,8 @@ package echo -import "net/http" +import ( + "net/http" +) type ( // Router is the registry of all registered routes for an `Echo` instance for @@ -11,17 +13,23 @@ type ( echo *Echo } node struct { - kind kind - label byte - prefix string - parent *node - children children - ppath string - pnames []string - methodHandler *methodHandler + kind kind + label byte + prefix string + parent *node + staticChildren children + ppath string + pnames []string + methodHandler *methodHandler + paramChild *node + anyChild *node + // isLeaf indicates that node does not have child routes + isLeaf bool + // isHandler indicates that node has at least one handler registered to it + isHandler bool } - kind uint8 - children []*node + kind uint8 + children []*node methodHandler struct { connect HandlerFunc delete HandlerFunc @@ -38,11 +46,28 @@ type ( ) const ( - skind kind = iota - pkind - akind + staticKind kind = iota + paramKind + anyKind + + paramLabel = byte(':') + anyLabel = byte('*') ) +func (m *methodHandler) isHandler() bool { + return m.connect != nil || + m.delete != nil || + m.get != nil || + m.head != nil || + m.options != nil || + m.patch != nil || + m.post != nil || + m.propfind != nil || + m.put != nil || + m.trace != nil || + m.report != nil +} + // NewRouter returns a new Router instance. func NewRouter(e *Echo) *Router { return &Router{ @@ -66,115 +91,157 @@ func (r *Router) Add(method, path string, h HandlerFunc) { pnames := []string{} // Param names ppath := path // Pristine path - for i, l := 0, len(path); i < l; i++ { + 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] == ':' { j := i + 1 - r.insert(method, path[:i], nil, skind, "", nil) - for ; i < l && path[i] != '/'; i++ { + r.insert(method, path[:i], nil, staticKind, "", nil) + for ; i < lcpIndex && path[i] != '/'; i++ { } pnames = append(pnames, path[j:i]) path = path[:j] + path[i:] - i, l = j, len(path) + i, lcpIndex = j, len(path) - if i == l { - r.insert(method, path[:i], h, pkind, ppath, pnames) + if i == lcpIndex { + // path node is last fragment of route path. ie. `/users/:id` + r.insert(method, path[:i], h, paramKind, ppath, pnames) } else { - r.insert(method, path[:i], nil, pkind, "", nil) + r.insert(method, path[:i], nil, paramKind, "", nil) } } else if path[i] == '*' { - r.insert(method, path[:i], nil, skind, "", nil) + r.insert(method, path[:i], nil, staticKind, "", nil) pnames = append(pnames, "*") - r.insert(method, path[:i+1], h, akind, ppath, pnames) + r.insert(method, path[:i+1], h, anyKind, ppath, pnames) } } - r.insert(method, path, h, skind, ppath, pnames) + r.insert(method, path, h, staticKind, ppath, pnames) } func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) { // Adjust max param - l := len(pnames) - if *r.echo.maxParam < l { - *r.echo.maxParam = l + paramLen := len(pnames) + if *r.echo.maxParam < paramLen { + *r.echo.maxParam = paramLen } - cn := r.tree // Current node as root - if cn == nil { + currentNode := r.tree // Current node as root + if currentNode == nil { panic("echo: invalid method") } search := path for { - sl := len(search) - pl := len(cn.prefix) - l := 0 + searchLen := len(search) + prefixLen := len(currentNode.prefix) + lcpLen := 0 - // LCP - max := pl - if sl < max { - max = sl + // LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array) + max := prefixLen + if searchLen < max { + max = searchLen } - for ; l < max && search[l] == cn.prefix[l]; l++ { + for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ { } - if l == 0 { + if lcpLen == 0 { // At root node - cn.label = search[0] - cn.prefix = search + currentNode.label = search[0] + currentNode.prefix = search if h != nil { - cn.kind = t - cn.addHandler(method, h) - cn.ppath = ppath - cn.pnames = pnames + currentNode.kind = t + currentNode.addHandler(method, h) + currentNode.ppath = ppath + currentNode.pnames = pnames } - } else if l < pl { + currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil + } else if lcpLen < prefixLen { // Split node - n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames) + n := newNode( + currentNode.kind, + currentNode.prefix[lcpLen:], + currentNode, + currentNode.staticChildren, + currentNode.methodHandler, + currentNode.ppath, + currentNode.pnames, + currentNode.paramChild, + currentNode.anyChild, + ) + // Update parent path for all children to new node + for _, child := range currentNode.staticChildren { + child.parent = n + } + if currentNode.paramChild != nil { + currentNode.paramChild.parent = n + } + if currentNode.anyChild != nil { + currentNode.anyChild.parent = n + } // Reset parent node - cn.kind = skind - cn.label = cn.prefix[0] - cn.prefix = cn.prefix[:l] - cn.children = nil - cn.methodHandler = new(methodHandler) - cn.ppath = "" - cn.pnames = nil + currentNode.kind = staticKind + currentNode.label = currentNode.prefix[0] + currentNode.prefix = currentNode.prefix[:lcpLen] + currentNode.staticChildren = nil + currentNode.methodHandler = new(methodHandler) + currentNode.ppath = "" + currentNode.pnames = nil + currentNode.paramChild = nil + currentNode.anyChild = nil + currentNode.isLeaf = false + currentNode.isHandler = false - cn.addChild(n) + // Only Static children could reach here + currentNode.addStaticChild(n) - if l == sl { + if lcpLen == searchLen { // At parent node - cn.kind = t - cn.addHandler(method, h) - cn.ppath = ppath - cn.pnames = pnames + currentNode.kind = t + currentNode.addHandler(method, h) + currentNode.ppath = ppath + currentNode.pnames = pnames } else { // Create child node - n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames) + n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil) n.addHandler(method, h) - cn.addChild(n) + // Only Static children could reach here + currentNode.addStaticChild(n) } - } else if l < sl { - search = search[l:] - c := cn.findChildWithLabel(search[0]) + 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 - cn = c + currentNode = c continue } // Create child node - n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames) + n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil) n.addHandler(method, h) - cn.addChild(n) + switch t { + case staticKind: + currentNode.addStaticChild(n) + case paramKind: + currentNode.paramChild = n + case anyKind: + currentNode.anyChild = n + } + currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil } else { // Node already exists if h != nil { - cn.addHandler(method, h) - cn.ppath = ppath - if len(cn.pnames) == 0 { // Issue #729 - cn.pnames = pnames + currentNode.addHandler(method, h) + currentNode.ppath = ppath + if len(currentNode.pnames) == 0 { // Issue #729 + currentNode.pnames = pnames } } } @@ -182,34 +249,29 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string } } -func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node { +func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node { return &node{ - kind: t, - label: pre[0], - prefix: pre, - parent: p, - children: c, - ppath: ppath, - pnames: pnames, - methodHandler: mh, + kind: t, + label: pre[0], + prefix: pre, + parent: p, + staticChildren: sc, + ppath: ppath, + pnames: pnames, + methodHandler: mh, + paramChild: paramChildren, + anyChild: anyChildren, + isLeaf: sc == nil && paramChildren == nil && anyChildren == nil, + isHandler: mh.isHandler(), } } -func (n *node) addChild(c *node) { - n.children = append(n.children, c) +func (n *node) addStaticChild(c *node) { + n.staticChildren = append(n.staticChildren, c) } -func (n *node) findChild(l byte, t kind) *node { - for _, c := range n.children { - if c.label == l && c.kind == t { - return c - } - } - return nil -} - -func (n *node) findChildWithLabel(l byte) *node { - for _, c := range n.children { +func (n *node) findStaticChild(l byte) *node { + for _, c := range n.staticChildren { if c.label == l { return c } @@ -217,12 +279,18 @@ func (n *node) findChildWithLabel(l byte) *node { return nil } -func (n *node) findChildByKind(t kind) *node { - for _, c := range n.children { - if c.kind == t { +func (n *node) findChildWithLabel(l byte) *node { + for _, c := range n.staticChildren { + if c.label == l { return c } } + if l == paramLabel { + return n.paramChild + } + if l == anyLabel { + return n.anyChild + } return nil } @@ -251,6 +319,12 @@ func (n *node) addHandler(method string, h HandlerFunc) { case REPORT: n.methodHandler.report = h } + + if h != nil { + n.isHandler = true + } else { + n.isHandler = n.methodHandler.isHandler() + } } func (n *node) findHandler(method string) HandlerFunc { @@ -302,142 +376,191 @@ func (n *node) checkMethodNotAllowed() HandlerFunc { func (r *Router) Find(method, path string, c Context) { ctx := c.(*context) ctx.path = path - cn := r.tree // Current node as root + currentNode := r.tree // Current node as root var ( - search = path - child *node // Child node - n int // Param counter - nk kind // Next kind - nn *node // Next node - ns string // Next search - pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice + previousBestMatchNode *node + matchedHandler HandlerFunc + // search stores the remaining path to check for match. By each iteration we move from start of path to end of the path + // and search value gets shorter and shorter. + search = path + searchIndex = 0 + paramIndex int // Param counter + paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice ) - // Search order static > param > any - for { - if search == "" { - break - } + // 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 - pl := 0 // Prefix length - l := 0 // LCP length - - if cn.label != ':' { - sl := len(search) - pl = len(cn.prefix) - - // LCP - max := pl - if sl < max { - max = sl - } - for ; l < max && search[l] == cn.prefix[l]; l++ { - } - } - - - if l == pl { - // Continue search - search = search[l:] + // Next node type by priority + if previous.kind == anyKind { + nextNodeKind = staticKind } else { - if nn == nil { // Issue #1348 - return // Not found + 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 } - cn = nn - search = ns - if nk == pkind { - goto Param - } else if nk == akind { - goto Any + for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ { } } - if search == "" { - break + if lcpLen != prefixLen { + // No matching prefix, let's backtrack to the first possible alternative node of the decision path + nk, ok := backtrackToNextNodeKind(staticKind) + if !ok { + return // No other possibilities on the decision path + } else if nk == paramKind { + goto Param + // NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently + //} else if nk == anyKind { + // goto Any + } else { + // Not found (this should never be possible for static node we are looking currently) + break + } + } + + // The full prefix has matched, remove the prefix from the remaining search + search = search[lcpLen:] + searchIndex = searchIndex + lcpLen + + // Finish routing if no remaining search and we are on a node with handler and matching method type + if search == "" && currentNode.isHandler { + // check if current node has handler registered for http method we are looking for. we store currentNode as + // best matching in case we do no find no more routes matching this path+method + if previousBestMatchNode == nil { + previousBestMatchNode = currentNode + } + if h := currentNode.findHandler(method); h != nil { + matchedHandler = h + break + } } // Static node - if child = cn.findChild(search[0], skind); child != nil { - // Save next - if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 - nk = pkind - nn = cn - ns = search - } - cn = child - continue - } - - // Param node - Param: - if child = cn.findChildByKind(pkind); child != nil { - // Issue #378 - if len(pvalues) == n { + if search != "" { + if child := currentNode.findStaticChild(search[0]); child != nil { + currentNode = child continue } + } - // Save next - if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 - nk = akind - nn = cn - ns = search + Param: + // Param node + if child := currentNode.paramChild; search != "" && child != nil { + currentNode = child + i := 0 + l := len(search) + if currentNode.isLeaf { + // when param node does not have any children then param node should act similarly to any node - consider all remaining search as match + i = l + } else { + for ; i < l && search[i] != '/'; i++ { + } } - cn = child - i, l := 0, len(search) - for ; i < l && search[i] != '/'; i++ { - } - pvalues[n] = search[:i] - n++ + paramValues[paramIndex] = search[:i] + paramIndex++ search = search[i:] + searchIndex = searchIndex + i continue } - // Any node Any: - if cn = cn.findChildByKind(akind); cn == nil { - if nn != nil { - cn = nn - nn = cn.parent // Next (Issue #954) - if nn != nil { - nk = nn.kind - } - search = ns - if nk == pkind { - goto Param - } else if nk == akind { - goto Any - } + // Any node + if child := currentNode.anyChild; child != nil { + // If any node is found, use remaining path for paramValues + currentNode = child + paramValues[len(currentNode.pnames)-1] = search + // update indexes/search in case we need to backtrack when no handler match is found + paramIndex++ + searchIndex += +len(search) + search = "" + + // check if current node has handler registered for http method we are looking for. we store currentNode as + // best matching in case we do no find no more routes matching this path+method + if previousBestMatchNode == nil { + previousBestMatchNode = currentNode + } + if h := currentNode.findHandler(method); h != nil { + matchedHandler = h + break } - return // Not found } - pvalues[len(cn.pnames)-1] = search - break - } - ctx.handler = cn.findHandler(method) - ctx.path = cn.ppath - ctx.pnames = cn.pnames - - // NOTE: Slow zone... - if ctx.handler == nil { - ctx.handler = cn.checkMethodNotAllowed() - - // Dig further for any, might have an empty value for *, e.g. - // serving a directory. Issue #207. - if cn = cn.findChildByKind(akind); cn == nil { - return - } - if h := cn.findHandler(method); h != nil { - ctx.handler = h + // 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 { - ctx.handler = cn.checkMethodNotAllowed() + // Not found + break } - ctx.path = cn.ppath - ctx.pnames = cn.pnames - pvalues[len(cn.pnames)-1] = "" } + if currentNode == nil && previousBestMatchNode == nil { + return // nothing matched at all + } + + if matchedHandler != nil { + ctx.handler = matchedHandler + } else { + // use previous match as basis. although we have no matching handler we have path match. + // so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404) + currentNode = previousBestMatchNode + ctx.handler = currentNode.checkMethodNotAllowed() + } + ctx.path = currentNode.ppath + ctx.pnames = currentNode.pnames + return } diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go index b9e936344..41215d7fc 100644 --- a/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -10,6 +10,7 @@ import ( "os" "strconv" "strings" + "sync" "syscall" "unsafe" @@ -27,6 +28,7 @@ const ( backgroundRed = 0x40 backgroundIntensity = 0x80 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) + commonLvbUnderscore = 0x8000 cENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 ) @@ -93,6 +95,7 @@ type Writer struct { oldattr word oldpos coord rest bytes.Buffer + mutex sync.Mutex } // NewColorable returns new instance of Writer which handles escape sequence from File. @@ -432,6 +435,8 @@ func atoiWithDefault(s string, def int) (int, error) { // Write writes data on console func (w *Writer) Write(data []byte) (n int, err error) { + w.mutex.Lock() + defer w.mutex.Unlock() var csbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) @@ -683,14 +688,19 @@ loop: switch { case n == 0 || n == 100: attr = w.oldattr - case 1 <= n && n <= 5: + case n == 4: + attr |= commonLvbUnderscore + case (1 <= n && n <= 3) || n == 5: attr |= foregroundIntensity - case n == 7: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) - case n == 22 || n == 25: - attr |= foregroundIntensity - case n == 27: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case n == 7 || n == 27: + attr = + (attr &^ (foregroundMask | backgroundMask)) | + ((attr & foregroundMask) << 4) | + ((attr & backgroundMask) >> 4) + case n == 22: + attr &^= foregroundIntensity + case n == 24: + attr &^= commonLvbUnderscore case 30 <= n && n <= 37: attr &= backgroundMask if (n-30)&1 != 0 { @@ -709,7 +719,7 @@ loop: n256setup() } attr &= backgroundMask - attr |= n256foreAttr[n256] + attr |= n256foreAttr[n256%len(n256foreAttr)] i += 2 } } else if len(token) == 5 && token[i+1] == "2" { @@ -751,7 +761,7 @@ loop: n256setup() } attr &= foregroundMask - attr |= n256backAttr[n256] + attr |= n256backAttr[n256%len(n256backAttr)] i += 2 } } else if len(token) == 5 && token[i+1] == "2" { diff --git a/vendor/github.com/valyala/fasttemplate/go.mod b/vendor/github.com/valyala/fasttemplate/go.mod index 6015c4b5a..37a50e261 100644 --- a/vendor/github.com/valyala/fasttemplate/go.mod +++ b/vendor/github.com/valyala/fasttemplate/go.mod @@ -1,3 +1,5 @@ module github.com/valyala/fasttemplate +go 1.12 + require github.com/valyala/bytebufferpool v1.0.0 diff --git a/vendor/github.com/valyala/fasttemplate/template.go b/vendor/github.com/valyala/fasttemplate/template.go index 91209201f..186200134 100644 --- a/vendor/github.com/valyala/fasttemplate/template.go +++ b/vendor/github.com/valyala/fasttemplate/template.go @@ -9,8 +9,9 @@ package fasttemplate import ( "bytes" "fmt" - "github.com/valyala/bytebufferpool" "io" + + "github.com/valyala/bytebufferpool" ) // ExecuteFunc calls f on each template tag (placeholder) occurrence. @@ -49,6 +50,9 @@ func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int ni, err = f(w, unsafeBytes2String(s[:n])) nn += int64(ni) + if err != nil { + return nn, err + } s = s[n+len(b):] } ni, err = w.Write(s) @@ -73,6 +77,22 @@ func Execute(template, startTag, endTag string, w io.Writer, m map[string]interf return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } +// ExecuteStd works the same way as Execute, but keeps the unknown placeholders. +// This can be used as a drop-in replacement for strings.Replacer +// +// Substitution map m may contain values with the following types: +// * []byte - the fastest value type +// * string - convenient value type +// * TagFunc - flexible value type +// +// Returns the number of bytes written to w. +// +// This function is optimized for constantly changing templates. +// Use Template.ExecuteStd for frozen templates. +func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) { + return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) }) +} + // ExecuteFuncString calls f on each template tag (placeholder) occurrence // and substitutes it with the data written to TagFunc's w. // @@ -81,19 +101,32 @@ func Execute(template, startTag, endTag string, w io.Writer, m map[string]interf // This function is optimized for constantly changing templates. // Use Template.ExecuteFuncString for frozen templates. func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string { + s, err := ExecuteFuncStringWithErr(template, startTag, endTag, f) + if err != nil { + panic(fmt.Sprintf("unexpected error: %s", err)) + } + return s +} + +// ExecuteFuncStringWithErr is nearly the same as ExecuteFuncString +// but when f returns an error, ExecuteFuncStringWithErr won't panic like ExecuteFuncString +// it just returns an empty string and the error f returned +func ExecuteFuncStringWithErr(template, startTag, endTag string, f TagFunc) (string, error) { tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag)) if tagsCount == 0 { - return template + return template, nil } bb := byteBufferPool.Get() if _, err := ExecuteFunc(template, startTag, endTag, bb, f); err != nil { - panic(fmt.Sprintf("unexpected error: %s", err)) + bb.Reset() + byteBufferPool.Put(bb) + return "", err } s := string(bb.B) bb.Reset() byteBufferPool.Put(bb) - return s + return s, nil } var byteBufferPool bytebufferpool.Pool @@ -112,6 +145,20 @@ func ExecuteString(template, startTag, endTag string, m map[string]interface{}) return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } +// ExecuteStringStd works the same way as ExecuteString, but keeps the unknown placeholders. +// This can be used as a drop-in replacement for strings.Replacer +// +// Substitution map m may contain values with the following types: +// * []byte - the fastest value type +// * string - convenient value type +// * TagFunc - flexible value type +// +// This function is optimized for constantly changing templates. +// Use Template.ExecuteStringStd for frozen templates. +func ExecuteStringStd(template, startTag, endTag string, m map[string]interface{}) string { + return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) }) +} + // Template implements simple template engine, which can be used for fast // tags' (aka placeholders) substitution. type Template struct { @@ -267,6 +314,19 @@ func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } +// ExecuteStd works the same way as Execute, but keeps the unknown placeholders. +// This can be used as a drop-in replacement for strings.Replacer +// +// Substitution map m may contain values with the following types: +// * []byte - the fastest value type +// * string - convenient value type +// * TagFunc - flexible value type +// +// Returns the number of bytes written to w. +func (t *Template) ExecuteStd(w io.Writer, m map[string]interface{}) (int64, error) { + return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) }) +} + // ExecuteFuncString calls f on each template tag (placeholder) occurrence // and substitutes it with the data written to TagFunc's w. // @@ -275,14 +335,31 @@ func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) // This function is optimized for frozen templates. // Use ExecuteFuncString for constantly changing templates. func (t *Template) ExecuteFuncString(f TagFunc) string { + s, err := t.ExecuteFuncStringWithErr(f) + if err != nil { + panic(fmt.Sprintf("unexpected error: %s", err)) + } + return s +} + +// ExecuteFuncStringWithErr calls f on each template tag (placeholder) occurrence +// and substitutes it with the data written to TagFunc's w. +// +// Returns the resulting string. +// +// This function is optimized for frozen templates. +// Use ExecuteFuncString for constantly changing templates. +func (t *Template) ExecuteFuncStringWithErr(f TagFunc) (string, error) { bb := t.byteBufferPool.Get() if _, err := t.ExecuteFunc(bb, f); err != nil { - panic(fmt.Sprintf("unexpected error: %s", err)) + bb.Reset() + t.byteBufferPool.Put(bb) + return "", err } s := string(bb.Bytes()) bb.Reset() t.byteBufferPool.Put(bb) - return s + return s, nil } // ExecuteString substitutes template tags (placeholders) with the corresponding @@ -299,6 +376,20 @@ func (t *Template) ExecuteString(m map[string]interface{}) string { return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } +// ExecuteStringStd works the same way as ExecuteString, but keeps the unknown placeholders. +// This can be used as a drop-in replacement for strings.Replacer +// +// Substitution map m may contain values with the following types: +// * []byte - the fastest value type +// * string - convenient value type +// * TagFunc - flexible value type +// +// This function is optimized for frozen templates. +// Use ExecuteStringStd for constantly changing templates. +func (t *Template) ExecuteStringStd(m map[string]interface{}) string { + return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) }) +} + func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) { v := m[tag] if v == nil { @@ -315,3 +406,32 @@ func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v)) } } + +func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]interface{}) (int, error) { + v, ok := m[tag] + if !ok { + if _, err := w.Write(unsafeString2Bytes(startTag)); err != nil { + return 0, err + } + if _, err := w.Write(unsafeString2Bytes(tag)); err != nil { + return 0, err + } + if _, err := w.Write(unsafeString2Bytes(endTag)); err != nil { + return 0, err + } + return len(startTag) + len(tag) + len(endTag), nil + } + if v == nil { + return 0, nil + } + switch value := v.(type) { + case []byte: + return w.Write(value) + case string: + return w.Write([]byte(value)) + case TagFunc: + return value(w, tag) + default: + panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v)) + } +} diff --git a/vendor/github.com/valyala/fasttemplate/unsafe.go b/vendor/github.com/valyala/fasttemplate/unsafe.go index 0498248fe..1020ca387 100644 --- a/vendor/github.com/valyala/fasttemplate/unsafe.go +++ b/vendor/github.com/valyala/fasttemplate/unsafe.go @@ -11,12 +11,11 @@ func unsafeBytes2String(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } -func unsafeString2Bytes(s string) []byte { +func unsafeString2Bytes(s string) (b []byte) { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) - bh := reflect.SliceHeader{ - Data: sh.Data, - Len: sh.Len, - Cap: sh.Len, - } - return *(*[]byte)(unsafe.Pointer(&bh)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh.Data = sh.Data + bh.Cap = sh.Len + bh.Len = sh.Len + return b } diff --git a/vendor/golang.org/x/net/http2/h2c/h2c.go b/vendor/golang.org/x/net/http2/h2c/h2c.go new file mode 100644 index 000000000..16319b8fd --- /dev/null +++ b/vendor/golang.org/x/net/http2/h2c/h2c.go @@ -0,0 +1,501 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package h2c implements the unencrypted "h2c" form of HTTP/2. +// +// The h2c protocol is the non-TLS version of HTTP/2 which is not available from +// net/http or golang.org/x/net/http2. +package h2c + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "net" + "net/http" + "net/textproto" + "os" + "strings" + + "golang.org/x/net/http/httpguts" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +var ( + http2VerboseLogs bool +) + +func init() { + e := os.Getenv("GODEBUG") + if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") { + http2VerboseLogs = true + } +} + +// h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic +// that should be h2c traffic. There are two ways to begin a h2c connection +// (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this +// works by starting an h2c connection with a string of bytes that is valid +// HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to +// h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to +// h2c. When either of those situations occur we hijack the HTTP/1 connection, +// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. +type h2cHandler struct { + Handler http.Handler + s *http2.Server +} + +// NewHandler returns an http.Handler that wraps h, intercepting any h2c +// traffic. If a request is an h2c connection, it's hijacked and redirected to +// s.ServeConn. Otherwise the returned Handler just forwards requests to h. This +// works because h2c is designed to be parseable as valid HTTP/1, but ignored by +// any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1 +// compatible parts of the Go http library to parse and recognize h2c requests. +// Once a request is recognized as h2c, we hijack the connection and convert it +// to an HTTP/2 connection which is understandable to s.ServeConn. (s.ServeConn +// understands HTTP/2 except for the h2c part of it.) +func NewHandler(h http.Handler, s *http2.Server) http.Handler { + return &h2cHandler{ + Handler: h, + s: s, + } +} + +// ServeHTTP implement the h2c support that is enabled by h2c.GetH2CHandler. +func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Handle h2c with prior knowledge (RFC 7540 Section 3.4) + if r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" { + if http2VerboseLogs { + log.Print("h2c: attempting h2c with prior knowledge.") + } + conn, err := initH2CWithPriorKnowledge(w) + if err != nil { + if http2VerboseLogs { + log.Printf("h2c: error h2c with prior knowledge: %v", err) + } + return + } + defer conn.Close() + + s.s.ServeConn(conn, &http2.ServeConnOpts{ + Context: r.Context(), + Handler: s.Handler, + }) + return + } + // Handle Upgrade to h2c (RFC 7540 Section 3.2) + if conn, err := h2cUpgrade(w, r); err == nil { + defer conn.Close() + + s.s.ServeConn(conn, &http2.ServeConnOpts{ + Context: r.Context(), + Handler: s.Handler, + }) + return + } + + s.Handler.ServeHTTP(w, r) + return +} + +// initH2CWithPriorKnowledge implements creating a h2c connection with prior +// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn. +// All we have to do is look for the client preface that is suppose to be part +// of the body, and reforward the client preface on the net.Conn this function +// creates. +func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) { + hijacker, ok := w.(http.Hijacker) + if !ok { + panic("Hijack not supported.") + } + conn, rw, err := hijacker.Hijack() + if err != nil { + panic(fmt.Sprintf("Hijack failed: %v", err)) + } + + const expectedBody = "SM\r\n\r\n" + + buf := make([]byte, len(expectedBody)) + n, err := io.ReadFull(rw, buf) + if err != nil { + return nil, fmt.Errorf("could not read from the buffer: %s", err) + } + + if string(buf[:n]) == expectedBody { + c := &rwConn{ + Conn: conn, + Reader: io.MultiReader(strings.NewReader(http2.ClientPreface), rw), + BufWriter: rw.Writer, + } + return c, nil + } + + conn.Close() + if http2VerboseLogs { + log.Printf( + "h2c: missing the request body portion of the client preface. Wanted: %v Got: %v", + []byte(expectedBody), + buf[0:n], + ) + } + return nil, errors.New("invalid client preface") +} + +// drainClientPreface reads a single instance of the HTTP/2 client preface from +// the supplied reader. +func drainClientPreface(r io.Reader) error { + var buf bytes.Buffer + prefaceLen := int64(len(http2.ClientPreface)) + n, err := io.CopyN(&buf, r, prefaceLen) + if err != nil { + return err + } + if n != prefaceLen || buf.String() != http2.ClientPreface { + return fmt.Errorf("Client never sent: %s", http2.ClientPreface) + } + return nil +} + +// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2). +func h2cUpgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) { + if !isH2CUpgrade(r.Header) { + return nil, errors.New("non-conforming h2c headers") + } + + // Initial bytes we put into conn to fool http2 server + initBytes, _, err := convertH1ReqToH2(r) + if err != nil { + return nil, err + } + + hijacker, ok := w.(http.Hijacker) + if !ok { + return nil, errors.New("hijack not supported.") + } + conn, rw, err := hijacker.Hijack() + if err != nil { + return nil, fmt.Errorf("hijack failed: %v", err) + } + + rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: h2c\r\n\r\n")) + rw.Flush() + + // A conforming client will now send an H2 client preface which need to drain + // since we already sent this. + if err := drainClientPreface(rw); err != nil { + return nil, err + } + + c := &rwConn{ + Conn: conn, + Reader: io.MultiReader(initBytes, rw), + BufWriter: newSettingsAckSwallowWriter(rw.Writer), + } + return c, nil +} + +// convert the data contained in the HTTP/1 upgrade request into the HTTP/2 +// version in byte form. +func convertH1ReqToH2(r *http.Request) (*bytes.Buffer, []http2.Setting, error) { + h2Bytes := bytes.NewBuffer([]byte((http2.ClientPreface))) + framer := http2.NewFramer(h2Bytes, nil) + settings, err := getH2Settings(r.Header) + if err != nil { + return nil, nil, err + } + + if err := framer.WriteSettings(settings...); err != nil { + return nil, nil, err + } + + headerBytes, err := getH2HeaderBytes(r, getMaxHeaderTableSize(settings)) + if err != nil { + return nil, nil, err + } + + maxFrameSize := int(getMaxFrameSize(settings)) + needOneHeader := len(headerBytes) < maxFrameSize + err = framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: 1, + BlockFragment: headerBytes, + EndHeaders: needOneHeader, + }) + if err != nil { + return nil, nil, err + } + + for i := maxFrameSize; i < len(headerBytes); i += maxFrameSize { + if len(headerBytes)-i > maxFrameSize { + if err := framer.WriteContinuation(1, + false, // endHeaders + headerBytes[i:maxFrameSize]); err != nil { + return nil, nil, err + } + } else { + if err := framer.WriteContinuation(1, + true, // endHeaders + headerBytes[i:]); err != nil { + return nil, nil, err + } + } + } + + return h2Bytes, settings, nil +} + +// getMaxFrameSize returns the SETTINGS_MAX_FRAME_SIZE. If not present default +// value is 16384 as specified by RFC 7540 Section 6.5.2. +func getMaxFrameSize(settings []http2.Setting) uint32 { + for _, setting := range settings { + if setting.ID == http2.SettingMaxFrameSize { + return setting.Val + } + } + return 16384 +} + +// getMaxHeaderTableSize returns the SETTINGS_HEADER_TABLE_SIZE. If not present +// default value is 4096 as specified by RFC 7540 Section 6.5.2. +func getMaxHeaderTableSize(settings []http2.Setting) uint32 { + for _, setting := range settings { + if setting.ID == http2.SettingHeaderTableSize { + return setting.Val + } + } + return 4096 +} + +// bufWriter is a Writer interface that also has a Flush method. +type bufWriter interface { + io.Writer + Flush() error +} + +// rwConn implements net.Conn but overrides Read and Write so that reads and +// writes are forwarded to the provided io.Reader and bufWriter. +type rwConn struct { + net.Conn + io.Reader + BufWriter bufWriter +} + +// Read forwards reads to the underlying Reader. +func (c *rwConn) Read(p []byte) (int, error) { + return c.Reader.Read(p) +} + +// Write forwards writes to the underlying bufWriter and immediately flushes. +func (c *rwConn) Write(p []byte) (int, error) { + n, err := c.BufWriter.Write(p) + if err := c.BufWriter.Flush(); err != nil { + return 0, err + } + return n, err +} + +// settingsAckSwallowWriter is a writer that normally forwards bytes to its +// underlying Writer, but swallows the first SettingsAck frame that it sees. +type settingsAckSwallowWriter struct { + Writer *bufio.Writer + buf []byte + didSwallow bool +} + +// newSettingsAckSwallowWriter returns a new settingsAckSwallowWriter. +func newSettingsAckSwallowWriter(w *bufio.Writer) *settingsAckSwallowWriter { + return &settingsAckSwallowWriter{ + Writer: w, + buf: make([]byte, 0), + didSwallow: false, + } +} + +// Write implements io.Writer interface. Normally forwards bytes to w.Writer, +// except for the first Settings ACK frame that it sees. +func (w *settingsAckSwallowWriter) Write(p []byte) (int, error) { + if !w.didSwallow { + w.buf = append(w.buf, p...) + // Process all the frames we have collected into w.buf + for { + // Append until we get full frame header which is 9 bytes + if len(w.buf) < 9 { + break + } + // Check if we have collected a whole frame. + fh, err := http2.ReadFrameHeader(bytes.NewBuffer(w.buf)) + if err != nil { + // Corrupted frame, fail current Write + return 0, err + } + fSize := fh.Length + 9 + if uint32(len(w.buf)) < fSize { + // Have not collected whole frame. Stop processing buf, and withold on + // forward bytes to w.Writer until we get the full frame. + break + } + + // We have now collected a whole frame. + if fh.Type == http2.FrameSettings && fh.Flags.Has(http2.FlagSettingsAck) { + // If Settings ACK frame, do not forward to underlying writer, remove + // bytes from w.buf, and record that we have swallowed Settings Ack + // frame. + w.didSwallow = true + w.buf = w.buf[fSize:] + continue + } + + // Not settings ack frame. Forward bytes to w.Writer. + if _, err := w.Writer.Write(w.buf[:fSize]); err != nil { + // Couldn't forward bytes. Fail current Write. + return 0, err + } + w.buf = w.buf[fSize:] + } + return len(p), nil + } + return w.Writer.Write(p) +} + +// Flush calls w.Writer.Flush. +func (w *settingsAckSwallowWriter) Flush() error { + return w.Writer.Flush() +} + +// isH2CUpgrade returns true if the header properly request an upgrade to h2c +// as specified by Section 3.2. +func isH2CUpgrade(h http.Header) bool { + return httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") && + httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings") +} + +// getH2Settings returns the []http2.Setting that are encoded in the +// HTTP2-Settings header. +func getH2Settings(h http.Header) ([]http2.Setting, error) { + vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")] + if !ok { + return nil, errors.New("missing HTTP2-Settings header") + } + if len(vals) != 1 { + return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals) + } + settings, err := decodeSettings(vals[0]) + if err != nil { + return nil, fmt.Errorf("Invalid HTTP2-Settings: %q", vals[0]) + } + return settings, nil +} + +// decodeSettings decodes the base64url header value of the HTTP2-Settings +// header. RFC 7540 Section 3.2.1. +func decodeSettings(headerVal string) ([]http2.Setting, error) { + b, err := base64.RawURLEncoding.DecodeString(headerVal) + if err != nil { + return nil, err + } + if len(b)%6 != 0 { + return nil, err + } + settings := make([]http2.Setting, 0) + for i := 0; i < len(b)/6; i++ { + settings = append(settings, http2.Setting{ + ID: http2.SettingID(binary.BigEndian.Uint16(b[i*6 : i*6+2])), + Val: binary.BigEndian.Uint32(b[i*6+2 : i*6+6]), + }) + } + + return settings, nil +} + +// getH2HeaderBytes return the headers in r a []bytes encoded by HPACK. +func getH2HeaderBytes(r *http.Request, maxHeaderTableSize uint32) ([]byte, error) { + headerBytes := bytes.NewBuffer(nil) + hpackEnc := hpack.NewEncoder(headerBytes) + hpackEnc.SetMaxDynamicTableSize(maxHeaderTableSize) + + // Section 8.1.2.3 + err := hpackEnc.WriteField(hpack.HeaderField{ + Name: ":method", + Value: r.Method, + }) + if err != nil { + return nil, err + } + + err = hpackEnc.WriteField(hpack.HeaderField{ + Name: ":scheme", + Value: "http", + }) + if err != nil { + return nil, err + } + + err = hpackEnc.WriteField(hpack.HeaderField{ + Name: ":authority", + Value: r.Host, + }) + if err != nil { + return nil, err + } + + path := r.URL.Path + if r.URL.RawQuery != "" { + path = strings.Join([]string{path, r.URL.RawQuery}, "?") + } + err = hpackEnc.WriteField(hpack.HeaderField{ + Name: ":path", + Value: path, + }) + if err != nil { + return nil, err + } + + // TODO Implement Section 8.3 + + for header, values := range r.Header { + // Skip non h2 headers + if isNonH2Header(header) { + continue + } + for _, v := range values { + err := hpackEnc.WriteField(hpack.HeaderField{ + Name: strings.ToLower(header), + Value: v, + }) + if err != nil { + return nil, err + } + } + } + return headerBytes.Bytes(), nil +} + +// Connection specific headers listed in RFC 7540 Section 8.1.2.2 that are not +// suppose to be transferred to HTTP/2. The Http2-Settings header is skipped +// since already use to create the HTTP/2 SETTINGS frame. +var nonH2Headers = []string{ + "Connection", + "Keep-Alive", + "Proxy-Connection", + "Transfer-Encoding", + "Upgrade", + "Http2-Settings", +} + +// isNonH2Header returns true if header should not be transferred to HTTP/2. +func isNonH2Header(header string) bool { + for _, nonH2h := range nonH2Headers { + if header == nonH2h { + return true + } + } + return false +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7d1d0ef13..3611c6d48 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -145,7 +145,7 @@ github.com/gobwas/glob/syntax/ast github.com/gobwas/glob/syntax/lexer github.com/gobwas/glob/util/runes github.com/gobwas/glob/util/strings -# github.com/golang-jwt/jwt v3.2.0+incompatible +# github.com/golang-jwt/jwt v3.2.2+incompatible ## explicit github.com/golang-jwt/jwt # github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b @@ -231,13 +231,13 @@ github.com/kolo/xmlrpc github.com/kr/pretty # github.com/kr/text v0.1.0 github.com/kr/text -# github.com/labstack/echo/v4 v4.1.11 +# github.com/labstack/echo/v4 v4.5.0 ## explicit github.com/labstack/echo/v4 # github.com/labstack/gommon v0.3.0 github.com/labstack/gommon/color github.com/labstack/gommon/log -# github.com/mattn/go-colorable v0.1.6 +# github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-colorable # github.com/mattn/go-ieproxy v0.0.1 github.com/mattn/go-ieproxy @@ -289,7 +289,7 @@ github.com/ubccr/kerby github.com/ubccr/kerby/khttp # github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/bytebufferpool -# github.com/valyala/fasttemplate v1.1.0 +# github.com/valyala/fasttemplate v1.2.1 github.com/valyala/fasttemplate # github.com/vmware/govmomi v0.23.0 ## explicit @@ -365,6 +365,7 @@ golang.org/x/net/context/ctxhttp golang.org/x/net/http/httpguts golang.org/x/net/http/httpproxy golang.org/x/net/http2 +golang.org/x/net/http2/h2c golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/timeseries