mirror of
https://github.com/christianselig/apollo-backend
synced 2024-11-22 03:37:43 +00:00
vendor thing
This commit is contained in:
parent
d95cc47cb7
commit
60a9e7deae
145 changed files with 93453 additions and 0 deletions
4
vendor/github.com/dgrijalva/jwt-go/.gitignore
generated
vendored
Normal file
4
vendor/github.com/dgrijalva/jwt-go/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.DS_Store
|
||||||
|
bin
|
||||||
|
|
||||||
|
|
13
vendor/github.com/dgrijalva/jwt-go/.travis.yml
generated
vendored
Normal file
13
vendor/github.com/dgrijalva/jwt-go/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet ./...
|
||||||
|
- go test -v ./...
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- tip
|
8
vendor/github.com/dgrijalva/jwt-go/LICENSE
generated
vendored
Normal file
8
vendor/github.com/dgrijalva/jwt-go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Copyright (c) 2012 Dave Grijalva
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
97
vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md
generated
vendored
Normal file
97
vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
## Migration Guide from v2 -> v3
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### `Token.Claims` is now an interface type
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
`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"])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
is now directly mapped to...
|
||||||
|
|
||||||
|
```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"])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
```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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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)
|
||||||
|
}
|
||||||
|
```
|
100
vendor/github.com/dgrijalva/jwt-go/README.md
generated
vendored
Normal file
100
vendor/github.com/dgrijalva/jwt-go/README.md
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## What's in the box?
|
||||||
|
|
||||||
|
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) 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)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## Usage Tips
|
||||||
|
|
||||||
|
### Signing vs Encryption
|
||||||
|
|
||||||
|
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
|
||||||
|
|
||||||
|
* The author of the token was in the possession of the signing secret
|
||||||
|
* The data has not been modified since it was signed
|
||||||
|
|
||||||
|
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
|
||||||
|
|
||||||
|
### Choosing a Signing Method
|
||||||
|
|
||||||
|
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric.
|
||||||
|
|
||||||
|
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
|
||||||
|
|
||||||
|
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
|
||||||
|
|
||||||
|
### Signing Methods and Key Types
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### JWT and OAuth
|
||||||
|
|
||||||
|
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
|
||||||
|
|
||||||
|
Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
|
||||||
|
|
||||||
|
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
|
||||||
|
|
||||||
|
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.
|
118
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
generated
vendored
Normal file
118
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
## `jwt-go` Version History
|
||||||
|
|
||||||
|
#### 3.2.0
|
||||||
|
|
||||||
|
* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation
|
||||||
|
* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate
|
||||||
|
* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before.
|
||||||
|
* Deprecated `ParseFromRequestWithClaims` to simplify API in the future.
|
||||||
|
|
||||||
|
#### 3.1.0
|
||||||
|
|
||||||
|
* Improvements to `jwt` command line tool
|
||||||
|
* Added `SkipClaimsValidation` option to `Parser`
|
||||||
|
* Documentation updates
|
||||||
|
|
||||||
|
#### 3.0.0
|
||||||
|
|
||||||
|
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code
|
||||||
|
* Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods.
|
||||||
|
* `ParseFromRequest` has been moved to `request` subpackage and usage has changed
|
||||||
|
* The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims.
|
||||||
|
* Other Additions and Changes
|
||||||
|
* Added `Claims` interface type to allow users to decode the claims into a custom type
|
||||||
|
* Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into.
|
||||||
|
* Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage
|
||||||
|
* Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims`
|
||||||
|
* Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`.
|
||||||
|
* Added several new, more specific, validation errors to error type bitmask
|
||||||
|
* Moved examples from README to executable example files
|
||||||
|
* Signing method registry is now thread safe
|
||||||
|
* Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser)
|
||||||
|
|
||||||
|
#### 2.7.0
|
||||||
|
|
||||||
|
This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes.
|
||||||
|
|
||||||
|
* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying
|
||||||
|
* Error text for expired tokens includes how long it's been expired
|
||||||
|
* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM`
|
||||||
|
* Documentation updates
|
||||||
|
|
||||||
|
#### 2.6.0
|
||||||
|
|
||||||
|
* Exposed inner error within ValidationError
|
||||||
|
* Fixed validation errors when using UseJSONNumber flag
|
||||||
|
* Added several unit tests
|
||||||
|
|
||||||
|
#### 2.5.0
|
||||||
|
|
||||||
|
* Added support for signing method none. You shouldn't use this. The API tries to make this clear.
|
||||||
|
* Updated/fixed some documentation
|
||||||
|
* Added more helpful error message when trying to parse tokens that begin with `BEARER `
|
||||||
|
|
||||||
|
#### 2.4.0
|
||||||
|
|
||||||
|
* Added new type, Parser, to allow for configuration of various parsing parameters
|
||||||
|
* You can now specify a list of valid signing methods. Anything outside this set will be rejected.
|
||||||
|
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON
|
||||||
|
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go)
|
||||||
|
* Fixed some bugs with ECDSA parsing
|
||||||
|
|
||||||
|
#### 2.3.0
|
||||||
|
|
||||||
|
* Added support for ECDSA signing methods
|
||||||
|
* Added support for RSA PSS signing methods (requires go v1.4)
|
||||||
|
|
||||||
|
#### 2.2.0
|
||||||
|
|
||||||
|
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
|
||||||
|
|
||||||
|
#### 2.1.0
|
||||||
|
|
||||||
|
Backwards compatible API change that was missed in 2.0.0.
|
||||||
|
|
||||||
|
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
|
||||||
|
|
||||||
|
#### 2.0.0
|
||||||
|
|
||||||
|
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
|
||||||
|
|
||||||
|
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
|
||||||
|
|
||||||
|
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
|
||||||
|
|
||||||
|
* **Compatibility Breaking Changes**
|
||||||
|
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
|
||||||
|
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
|
||||||
|
* `KeyFunc` now returns `interface{}` instead of `[]byte`
|
||||||
|
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
|
||||||
|
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
|
||||||
|
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
|
||||||
|
* Added public package global `SigningMethodHS256`
|
||||||
|
* Added public package global `SigningMethodHS384`
|
||||||
|
* Added public package global `SigningMethodHS512`
|
||||||
|
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
|
||||||
|
* Added public package global `SigningMethodRS256`
|
||||||
|
* Added public package global `SigningMethodRS384`
|
||||||
|
* Added public package global `SigningMethodRS512`
|
||||||
|
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
|
||||||
|
* Refactored the RSA implementation to be easier to read
|
||||||
|
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
|
||||||
|
|
||||||
|
#### 1.0.2
|
||||||
|
|
||||||
|
* Fixed bug in parsing public keys from certificates
|
||||||
|
* Added more tests around the parsing of keys for RS256
|
||||||
|
* Code refactoring in RS256 implementation. No functional changes
|
||||||
|
|
||||||
|
#### 1.0.1
|
||||||
|
|
||||||
|
* Fixed panic if RS256 signing method was passed an invalid key
|
||||||
|
|
||||||
|
#### 1.0.0
|
||||||
|
|
||||||
|
* First versioned release
|
||||||
|
* API stabilized
|
||||||
|
* Supports creating, signing, parsing, and validating JWT tokens
|
||||||
|
* Supports RS256 and HS256 signing methods
|
134
vendor/github.com/dgrijalva/jwt-go/claims.go
generated
vendored
Normal file
134
vendor/github.com/dgrijalva/jwt-go/claims.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For a type to be a Claims object, it must just have a Valid method that determines
|
||||||
|
// if the token is invalid for any supported reason
|
||||||
|
type Claims interface {
|
||||||
|
Valid() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structured version of Claims Section, as referenced at
|
||||||
|
// https://tools.ietf.org/html/rfc7519#section-4.1
|
||||||
|
// See examples for how to use this with your own claim types
|
||||||
|
type StandardClaims struct {
|
||||||
|
Audience string `json:"aud,omitempty"`
|
||||||
|
ExpiresAt int64 `json:"exp,omitempty"`
|
||||||
|
Id string `json:"jti,omitempty"`
|
||||||
|
IssuedAt int64 `json:"iat,omitempty"`
|
||||||
|
Issuer string `json:"iss,omitempty"`
|
||||||
|
NotBefore int64 `json:"nbf,omitempty"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates time based claims "exp, iat, nbf".
|
||||||
|
// There is no accounting for clock skew.
|
||||||
|
// As well, if any of the above claims are not in the token, it will still
|
||||||
|
// be considered a valid claim.
|
||||||
|
func (c StandardClaims) Valid() error {
|
||||||
|
vErr := new(ValidationError)
|
||||||
|
now := TimeFunc().Unix()
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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 {
|
||||||
|
vErr.Inner = fmt.Errorf("Token used before issued")
|
||||||
|
vErr.Errors |= ValidationErrorIssuedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.VerifyNotBefore(now, false) == false {
|
||||||
|
vErr.Inner = fmt.Errorf("token is not valid yet")
|
||||||
|
vErr.Errors |= ValidationErrorNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErr.valid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return vErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the exp claim against cmp.
|
||||||
|
// If required is false, this method will return true if the value matches or is unset
|
||||||
|
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
|
||||||
|
return verifyExp(c.ExpiresAt, cmp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the iat claim against cmp.
|
||||||
|
// If required is false, this method will return true if the value matches or is unset
|
||||||
|
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
|
||||||
|
return verifyIat(c.IssuedAt, cmp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the iss claim against cmp.
|
||||||
|
// If required is false, this method will return true if the value matches or is unset
|
||||||
|
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||||
|
return verifyIss(c.Issuer, cmp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the nbf claim against cmp.
|
||||||
|
// If required is false, this method will return true if the value matches or is unset
|
||||||
|
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
||||||
|
return verifyNbf(c.NotBefore, cmp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- helpers
|
||||||
|
|
||||||
|
func verifyAud(aud string, cmp string, required bool) bool {
|
||||||
|
if aud == "" {
|
||||||
|
return !required
|
||||||
|
}
|
||||||
|
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyExp(exp int64, now int64, required bool) bool {
|
||||||
|
if exp == 0 {
|
||||||
|
return !required
|
||||||
|
}
|
||||||
|
return now <= exp
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIat(iat int64, now int64, required bool) bool {
|
||||||
|
if iat == 0 {
|
||||||
|
return !required
|
||||||
|
}
|
||||||
|
return now >= iat
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIss(iss string, cmp string, required bool) bool {
|
||||||
|
if iss == "" {
|
||||||
|
return !required
|
||||||
|
}
|
||||||
|
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyNbf(nbf int64, now int64, required bool) bool {
|
||||||
|
if nbf == 0 {
|
||||||
|
return !required
|
||||||
|
}
|
||||||
|
return now >= nbf
|
||||||
|
}
|
4
vendor/github.com/dgrijalva/jwt-go/doc.go
generated
vendored
Normal file
4
vendor/github.com/dgrijalva/jwt-go/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
|
||||||
|
//
|
||||||
|
// See README.md for more info.
|
||||||
|
package jwt
|
148
vendor/github.com/dgrijalva/jwt-go/ecdsa.go
generated
vendored
Normal file
148
vendor/github.com/dgrijalva/jwt-go/ecdsa.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
|
||||||
|
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements the ECDSA family of signing methods signing methods
|
||||||
|
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
|
||||||
|
type SigningMethodECDSA struct {
|
||||||
|
Name string
|
||||||
|
Hash crypto.Hash
|
||||||
|
KeySize int
|
||||||
|
CurveBits int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific instances for EC256 and company
|
||||||
|
var (
|
||||||
|
SigningMethodES256 *SigningMethodECDSA
|
||||||
|
SigningMethodES384 *SigningMethodECDSA
|
||||||
|
SigningMethodES512 *SigningMethodECDSA
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// ES256
|
||||||
|
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
|
||||||
|
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodES256
|
||||||
|
})
|
||||||
|
|
||||||
|
// ES384
|
||||||
|
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
|
||||||
|
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodES384
|
||||||
|
})
|
||||||
|
|
||||||
|
// ES512
|
||||||
|
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
|
||||||
|
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodES512
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SigningMethodECDSA) Alg() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Verify method from SigningMethod
|
||||||
|
// For this verify method, key must be an ecdsa.PublicKey struct
|
||||||
|
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Decode the signature
|
||||||
|
var sig []byte
|
||||||
|
if sig, err = DecodeSegment(signature); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the key
|
||||||
|
var ecdsaKey *ecdsa.PublicKey
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
ecdsaKey = k
|
||||||
|
default:
|
||||||
|
return ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != 2*m.KeySize {
|
||||||
|
return ErrECDSAVerification
|
||||||
|
}
|
||||||
|
|
||||||
|
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
|
||||||
|
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
|
||||||
|
|
||||||
|
// Create hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return ErrHashUnavailable
|
||||||
|
}
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
// Verify the signature
|
||||||
|
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return ErrECDSAVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Sign method from SigningMethod
|
||||||
|
// For this signing method, key must be an ecdsa.PrivateKey struct
|
||||||
|
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
|
||||||
|
// Get the key
|
||||||
|
var ecdsaKey *ecdsa.PrivateKey
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
ecdsaKey = k
|
||||||
|
default:
|
||||||
|
return "", ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return "", ErrHashUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
// Sign the string and return r, s
|
||||||
|
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
|
||||||
|
curveBits := ecdsaKey.Curve.Params().BitSize
|
||||||
|
|
||||||
|
if m.CurveBits != curveBits {
|
||||||
|
return "", ErrInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes := curveBits / 8
|
||||||
|
if curveBits%8 > 0 {
|
||||||
|
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...)
|
||||||
|
|
||||||
|
return EncodeSegment(out), nil
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
67
vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go
generated
vendored
Normal file
67
vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
|
||||||
|
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse PEM encoded Elliptic Curve Private Key Structure
|
||||||
|
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.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.ParseECPrivateKey(block.Bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *ecdsa.PrivateKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
|
||||||
|
return nil, ErrNotECPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PEM encoded PKCS1 or PKCS8 public key
|
||||||
|
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.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 {
|
||||||
|
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
|
||||||
|
parsedKey = cert.PublicKey
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *ecdsa.PublicKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
|
||||||
|
return nil, ErrNotECPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
59
vendor/github.com/dgrijalva/jwt-go/errors.go
generated
vendored
Normal file
59
vendor/github.com/dgrijalva/jwt-go/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error constants
|
||||||
|
var (
|
||||||
|
ErrInvalidKey = errors.New("key is invalid")
|
||||||
|
ErrInvalidKeyType = errors.New("key is of invalid type")
|
||||||
|
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
|
||||||
|
)
|
||||||
|
|
||||||
|
// The errors that might occur when parsing and validating a token
|
||||||
|
const (
|
||||||
|
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
|
||||||
|
ValidationErrorUnverifiable // Token could not be verified because of signing problems
|
||||||
|
ValidationErrorSignatureInvalid // Signature validation failed
|
||||||
|
|
||||||
|
// Standard Claim validation errors
|
||||||
|
ValidationErrorAudience // AUD validation failed
|
||||||
|
ValidationErrorExpired // EXP validation failed
|
||||||
|
ValidationErrorIssuedAt // IAT validation failed
|
||||||
|
ValidationErrorIssuer // ISS validation failed
|
||||||
|
ValidationErrorNotValidYet // NBF validation failed
|
||||||
|
ValidationErrorId // JTI validation failed
|
||||||
|
ValidationErrorClaimsInvalid // Generic claims validation error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper for constructing a ValidationError with a string error message
|
||||||
|
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
|
||||||
|
return &ValidationError{
|
||||||
|
text: errorText,
|
||||||
|
Errors: errorFlags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The error from Parse if token is not valid
|
||||||
|
type ValidationError struct {
|
||||||
|
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
|
||||||
|
Errors uint32 // bitfield. see ValidationError... constants
|
||||||
|
text string // errors that do not have a valid error just have text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation error is an error type
|
||||||
|
func (e ValidationError) Error() string {
|
||||||
|
if e.Inner != nil {
|
||||||
|
return e.Inner.Error()
|
||||||
|
} else if e.text != "" {
|
||||||
|
return e.text
|
||||||
|
} else {
|
||||||
|
return "token is invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No errors
|
||||||
|
func (e *ValidationError) valid() bool {
|
||||||
|
return e.Errors == 0
|
||||||
|
}
|
95
vendor/github.com/dgrijalva/jwt-go/hmac.go
generated
vendored
Normal file
95
vendor/github.com/dgrijalva/jwt-go/hmac.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements the HMAC-SHA family of signing methods signing methods
|
||||||
|
// Expects key type of []byte for both signing and validation
|
||||||
|
type SigningMethodHMAC struct {
|
||||||
|
Name string
|
||||||
|
Hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific instances for HS256 and company
|
||||||
|
var (
|
||||||
|
SigningMethodHS256 *SigningMethodHMAC
|
||||||
|
SigningMethodHS384 *SigningMethodHMAC
|
||||||
|
SigningMethodHS512 *SigningMethodHMAC
|
||||||
|
ErrSignatureInvalid = errors.New("signature is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// HS256
|
||||||
|
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
|
||||||
|
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodHS256
|
||||||
|
})
|
||||||
|
|
||||||
|
// HS384
|
||||||
|
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
|
||||||
|
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodHS384
|
||||||
|
})
|
||||||
|
|
||||||
|
// HS512
|
||||||
|
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
|
||||||
|
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodHS512
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SigningMethodHMAC) Alg() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
|
||||||
|
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
|
||||||
|
// Verify the key is the right type
|
||||||
|
keyBytes, ok := key.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode signature, for comparison
|
||||||
|
sig, err := DecodeSegment(signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can we use the specified hashing method?
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return ErrHashUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// This signing method is symmetric, so we validate the signature
|
||||||
|
// by reproducing the signature from the signing string and key, then
|
||||||
|
// comparing that against the provided signature.
|
||||||
|
hasher := hmac.New(m.Hash.New, keyBytes)
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
if !hmac.Equal(sig, hasher.Sum(nil)) {
|
||||||
|
return ErrSignatureInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// No validation errors. Signature is good.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Sign method from SigningMethod for this signing method.
|
||||||
|
// Key must be []byte
|
||||||
|
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
|
||||||
|
if keyBytes, ok := key.([]byte); ok {
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return "", ErrHashUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hmac.New(m.Hash.New, keyBytes)
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
return EncodeSegment(hasher.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrInvalidKeyType
|
||||||
|
}
|
94
vendor/github.com/dgrijalva/jwt-go/map_claims.go
generated
vendored
Normal file
94
vendor/github.com/dgrijalva/jwt-go/map_claims.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
// "fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims type that uses the map[string]interface{} for JSON decoding
|
||||||
|
// This is the default claims type if you don't supply one
|
||||||
|
type MapClaims map[string]interface{}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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) {
|
||||||
|
case float64:
|
||||||
|
return verifyExp(int64(exp), cmp, req)
|
||||||
|
case json.Number:
|
||||||
|
v, _ := exp.Int64()
|
||||||
|
return verifyExp(v, cmp, req)
|
||||||
|
}
|
||||||
|
return req == 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) {
|
||||||
|
case float64:
|
||||||
|
return verifyIat(int64(iat), cmp, req)
|
||||||
|
case json.Number:
|
||||||
|
v, _ := iat.Int64()
|
||||||
|
return verifyIat(v, cmp, req)
|
||||||
|
}
|
||||||
|
return req == false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the iss claim against cmp.
|
||||||
|
// If required is false, this method will return true if the value matches or is unset
|
||||||
|
func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||||
|
iss, _ := m["iss"].(string)
|
||||||
|
return verifyIss(iss, cmp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
case float64:
|
||||||
|
return verifyNbf(int64(nbf), cmp, req)
|
||||||
|
case json.Number:
|
||||||
|
v, _ := nbf.Int64()
|
||||||
|
return verifyNbf(v, cmp, req)
|
||||||
|
}
|
||||||
|
return req == false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates time based claims "exp, iat, nbf".
|
||||||
|
// There is no accounting for clock skew.
|
||||||
|
// As well, if any of the above claims are not in the token, it will still
|
||||||
|
// be considered a valid claim.
|
||||||
|
func (m MapClaims) Valid() error {
|
||||||
|
vErr := new(ValidationError)
|
||||||
|
now := TimeFunc().Unix()
|
||||||
|
|
||||||
|
if m.VerifyExpiresAt(now, false) == false {
|
||||||
|
vErr.Inner = errors.New("Token is expired")
|
||||||
|
vErr.Errors |= ValidationErrorExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.VerifyIssuedAt(now, false) == false {
|
||||||
|
vErr.Inner = errors.New("Token used before issued")
|
||||||
|
vErr.Errors |= ValidationErrorIssuedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.VerifyNotBefore(now, false) == false {
|
||||||
|
vErr.Inner = errors.New("Token is not valid yet")
|
||||||
|
vErr.Errors |= ValidationErrorNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErr.valid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return vErr
|
||||||
|
}
|
52
vendor/github.com/dgrijalva/jwt-go/none.go
generated
vendored
Normal file
52
vendor/github.com/dgrijalva/jwt-go/none.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
// Implements the none signing method. This is required by the spec
|
||||||
|
// but you probably should never use it.
|
||||||
|
var SigningMethodNone *signingMethodNone
|
||||||
|
|
||||||
|
const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
|
||||||
|
|
||||||
|
var NoneSignatureTypeDisallowedError error
|
||||||
|
|
||||||
|
type signingMethodNone struct{}
|
||||||
|
type unsafeNoneMagicConstant string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SigningMethodNone = &signingMethodNone{}
|
||||||
|
NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
|
||||||
|
|
||||||
|
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodNone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *signingMethodNone) Alg() string {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
|
||||||
|
func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
|
||||||
|
// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
|
||||||
|
// accepting 'none' signing method
|
||||||
|
if _, ok := key.(unsafeNoneMagicConstant); !ok {
|
||||||
|
return NoneSignatureTypeDisallowedError
|
||||||
|
}
|
||||||
|
// If signing method is none, signature must be an empty string
|
||||||
|
if signature != "" {
|
||||||
|
return NewValidationError(
|
||||||
|
"'none' signing method with non-empty signature",
|
||||||
|
ValidationErrorSignatureInvalid,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept 'none' signing method.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
|
||||||
|
func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
|
||||||
|
if _, ok := key.(unsafeNoneMagicConstant); ok {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return "", NoneSignatureTypeDisallowedError
|
||||||
|
}
|
148
vendor/github.com/dgrijalva/jwt-go/parser.go
generated
vendored
Normal file
148
vendor/github.com/dgrijalva/jwt-go/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
ValidMethods []string // If populated, only these methods will be considered valid
|
||||||
|
UseJSONNumber bool // Use JSON Number format in JSON decoder
|
||||||
|
SkipClaimsValidation bool // Skip claims validation during token parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse, validate, and return a token.
|
||||||
|
// keyFunc will receive the parsed token and should return the key for validating.
|
||||||
|
// If everything is kosher, err will be nil
|
||||||
|
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
||||||
|
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||||
|
token, parts, err := p.ParseUnverified(tokenString, claims)
|
||||||
|
if err != nil {
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify signing method is in the required set
|
||||||
|
if p.ValidMethods != nil {
|
||||||
|
var signingMethodValid = false
|
||||||
|
var alg = token.Method.Alg()
|
||||||
|
for _, m := range p.ValidMethods {
|
||||||
|
if m == alg {
|
||||||
|
signingMethodValid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !signingMethodValid {
|
||||||
|
// signing method is not in the listed set
|
||||||
|
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup key
|
||||||
|
var key interface{}
|
||||||
|
if keyFunc == nil {
|
||||||
|
// keyFunc was not provided. short circuiting validation
|
||||||
|
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
|
||||||
|
}
|
||||||
|
if key, err = keyFunc(token); err != nil {
|
||||||
|
// keyFunc returned an error
|
||||||
|
if ve, ok := err.(*ValidationError); ok {
|
||||||
|
return token, ve
|
||||||
|
}
|
||||||
|
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
|
||||||
|
}
|
||||||
|
|
||||||
|
vErr := &ValidationError{}
|
||||||
|
|
||||||
|
// Validate Claims
|
||||||
|
if !p.SkipClaimsValidation {
|
||||||
|
if err := token.Claims.Valid(); err != nil {
|
||||||
|
|
||||||
|
// If the Claims Valid returned an error, check if it is a validation error,
|
||||||
|
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||||
|
if e, ok := err.(*ValidationError); !ok {
|
||||||
|
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
|
||||||
|
} else {
|
||||||
|
vErr = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform validation
|
||||||
|
token.Signature = parts[2]
|
||||||
|
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
|
||||||
|
vErr.Inner = err
|
||||||
|
vErr.Errors |= ValidationErrorSignatureInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErr.valid() {
|
||||||
|
token.Valid = true
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, vErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: Don't use this method unless you know what you're doing
|
||||||
|
//
|
||||||
|
// This method parses the token but doesn't validate the signature. It's only
|
||||||
|
// ever useful in cases where you know the signature is valid (because it has
|
||||||
|
// been checked previously in the stack) and you want to extract values from
|
||||||
|
// it.
|
||||||
|
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
|
||||||
|
parts = strings.Split(tokenString, ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = &Token{Raw: tokenString}
|
||||||
|
|
||||||
|
// parse Header
|
||||||
|
var headerBytes []byte
|
||||||
|
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
|
||||||
|
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
|
||||||
|
return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
|
||||||
|
}
|
||||||
|
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
|
||||||
|
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse Claims
|
||||||
|
var claimBytes []byte
|
||||||
|
token.Claims = claims
|
||||||
|
|
||||||
|
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
|
||||||
|
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
|
||||||
|
if p.UseJSONNumber {
|
||||||
|
dec.UseNumber()
|
||||||
|
}
|
||||||
|
// JSON Decode. Special case for map type to avoid weird pointer behavior
|
||||||
|
if c, ok := token.Claims.(MapClaims); ok {
|
||||||
|
err = dec.Decode(&c)
|
||||||
|
} else {
|
||||||
|
err = dec.Decode(&claims)
|
||||||
|
}
|
||||||
|
// Handle decode error
|
||||||
|
if err != nil {
|
||||||
|
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup signature method
|
||||||
|
if method, ok := token.Header["alg"].(string); ok {
|
||||||
|
if token.Method = GetSigningMethod(method); token.Method == nil {
|
||||||
|
return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, parts, nil
|
||||||
|
}
|
101
vendor/github.com/dgrijalva/jwt-go/rsa.go
generated
vendored
Normal file
101
vendor/github.com/dgrijalva/jwt-go/rsa.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements the RSA family of signing methods signing methods
|
||||||
|
// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
|
||||||
|
type SigningMethodRSA struct {
|
||||||
|
Name string
|
||||||
|
Hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific instances for RS256 and company
|
||||||
|
var (
|
||||||
|
SigningMethodRS256 *SigningMethodRSA
|
||||||
|
SigningMethodRS384 *SigningMethodRSA
|
||||||
|
SigningMethodRS512 *SigningMethodRSA
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// RS256
|
||||||
|
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
|
||||||
|
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodRS256
|
||||||
|
})
|
||||||
|
|
||||||
|
// RS384
|
||||||
|
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
|
||||||
|
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodRS384
|
||||||
|
})
|
||||||
|
|
||||||
|
// RS512
|
||||||
|
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
|
||||||
|
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodRS512
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SigningMethodRSA) Alg() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Verify method from SigningMethod
|
||||||
|
// For this signing method, must be an *rsa.PublicKey structure.
|
||||||
|
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Decode the signature
|
||||||
|
var sig []byte
|
||||||
|
if sig, err = DecodeSegment(signature); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsaKey *rsa.PublicKey
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
|
||||||
|
return ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return ErrHashUnavailable
|
||||||
|
}
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
// Verify the signature
|
||||||
|
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Sign method from SigningMethod
|
||||||
|
// For this signing method, must be an *rsa.PrivateKey structure.
|
||||||
|
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
|
||||||
|
var rsaKey *rsa.PrivateKey
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
// Validate type of key
|
||||||
|
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
|
||||||
|
return "", ErrInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return "", ErrHashUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
// Sign the string and return the encoded bytes
|
||||||
|
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
|
||||||
|
return EncodeSegment(sigBytes), nil
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
126
vendor/github.com/dgrijalva/jwt-go/rsa_pss.go
generated
vendored
Normal file
126
vendor/github.com/dgrijalva/jwt-go/rsa_pss.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// +build go1.4
|
||||||
|
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements the RSAPSS family of signing methods signing methods
|
||||||
|
type SigningMethodRSAPSS struct {
|
||||||
|
*SigningMethodRSA
|
||||||
|
Options *rsa.PSSOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific instances for RS/PS and company
|
||||||
|
var (
|
||||||
|
SigningMethodPS256 *SigningMethodRSAPSS
|
||||||
|
SigningMethodPS384 *SigningMethodRSAPSS
|
||||||
|
SigningMethodPS512 *SigningMethodRSAPSS
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// PS256
|
||||||
|
SigningMethodPS256 = &SigningMethodRSAPSS{
|
||||||
|
&SigningMethodRSA{
|
||||||
|
Name: "PS256",
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
},
|
||||||
|
&rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodPS256
|
||||||
|
})
|
||||||
|
|
||||||
|
// PS384
|
||||||
|
SigningMethodPS384 = &SigningMethodRSAPSS{
|
||||||
|
&SigningMethodRSA{
|
||||||
|
Name: "PS384",
|
||||||
|
Hash: crypto.SHA384,
|
||||||
|
},
|
||||||
|
&rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA384,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodPS384
|
||||||
|
})
|
||||||
|
|
||||||
|
// PS512
|
||||||
|
SigningMethodPS512 = &SigningMethodRSAPSS{
|
||||||
|
&SigningMethodRSA{
|
||||||
|
Name: "PS512",
|
||||||
|
Hash: crypto.SHA512,
|
||||||
|
},
|
||||||
|
&rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA512,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
|
||||||
|
return SigningMethodPS512
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Verify method from SigningMethod
|
||||||
|
// For this verify method, key must be an rsa.PublicKey struct
|
||||||
|
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Decode the signature
|
||||||
|
var sig []byte
|
||||||
|
if sig, err = DecodeSegment(signature); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsaKey *rsa.PublicKey
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
rsaKey = k
|
||||||
|
default:
|
||||||
|
return ErrInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return ErrHashUnavailable
|
||||||
|
}
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the Sign method from SigningMethod
|
||||||
|
// For this signing method, key must be an rsa.PrivateKey struct
|
||||||
|
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
|
||||||
|
var rsaKey *rsa.PrivateKey
|
||||||
|
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
rsaKey = k
|
||||||
|
default:
|
||||||
|
return "", ErrInvalidKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hasher
|
||||||
|
if !m.Hash.Available() {
|
||||||
|
return "", ErrHashUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := m.Hash.New()
|
||||||
|
hasher.Write([]byte(signingString))
|
||||||
|
|
||||||
|
// Sign the string and return the encoded bytes
|
||||||
|
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
|
||||||
|
return EncodeSegment(sigBytes), nil
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
101
vendor/github.com/dgrijalva/jwt-go/rsa_utils.go
generated
vendored
Normal file
101
vendor/github.com/dgrijalva/jwt-go/rsa_utils.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
|
||||||
|
ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
|
||||||
|
ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse PEM encoded PKCS1 or PKCS8 private key
|
||||||
|
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Parse PEM block
|
||||||
|
var block *pem.Block
|
||||||
|
if block, _ = pem.Decode(key); block == nil {
|
||||||
|
return nil, ErrKeyMustBePEMEncoded
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedKey interface{}
|
||||||
|
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
|
||||||
|
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *rsa.PrivateKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
|
||||||
|
return nil, ErrNotRSAPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PEM encoded PKCS1 or PKCS8 private key protected with password
|
||||||
|
func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Parse PEM block
|
||||||
|
var block *pem.Block
|
||||||
|
if block, _ = pem.Decode(key); block == nil {
|
||||||
|
return nil, ErrKeyMustBePEMEncoded
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedKey interface{}
|
||||||
|
|
||||||
|
var blockDecrypted []byte
|
||||||
|
if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
|
||||||
|
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *rsa.PrivateKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
|
||||||
|
return nil, ErrNotRSAPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PEM encoded PKCS1 or PKCS8 public key
|
||||||
|
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.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 {
|
||||||
|
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
|
||||||
|
parsedKey = cert.PublicKey
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *rsa.PublicKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
|
||||||
|
return nil, ErrNotRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
35
vendor/github.com/dgrijalva/jwt-go/signing_method.go
generated
vendored
Normal file
35
vendor/github.com/dgrijalva/jwt-go/signing_method.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var signingMethods = map[string]func() SigningMethod{}
|
||||||
|
var signingMethodLock = new(sync.RWMutex)
|
||||||
|
|
||||||
|
// Implement SigningMethod to add new methods for signing or verifying tokens.
|
||||||
|
type SigningMethod interface {
|
||||||
|
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
|
||||||
|
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
|
||||||
|
Alg() string // returns the alg identifier for this method (example: 'HS256')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the "alg" name and a factory function for signing method.
|
||||||
|
// This is typically done during init() in the method's implementation
|
||||||
|
func RegisterSigningMethod(alg string, f func() SigningMethod) {
|
||||||
|
signingMethodLock.Lock()
|
||||||
|
defer signingMethodLock.Unlock()
|
||||||
|
|
||||||
|
signingMethods[alg] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a signing method from an "alg" string
|
||||||
|
func GetSigningMethod(alg string) (method SigningMethod) {
|
||||||
|
signingMethodLock.RLock()
|
||||||
|
defer signingMethodLock.RUnlock()
|
||||||
|
|
||||||
|
if methodF, ok := signingMethods[alg]; ok {
|
||||||
|
method = methodF()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
108
vendor/github.com/dgrijalva/jwt-go/token.go
generated
vendored
Normal file
108
vendor/github.com/dgrijalva/jwt-go/token.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
||||||
|
// You can override it to use another time value. This is useful for testing or if your
|
||||||
|
// server uses a different time zone than your tokens.
|
||||||
|
var TimeFunc = time.Now
|
||||||
|
|
||||||
|
// Parse methods use this callback function to supply
|
||||||
|
// the key for verification. The function receives the parsed,
|
||||||
|
// but unverified Token. This allows you to use properties in the
|
||||||
|
// Header of the token (such as `kid`) to identify which key to use.
|
||||||
|
type Keyfunc func(*Token) (interface{}, error)
|
||||||
|
|
||||||
|
// A JWT Token. Different fields will be used depending on whether you're
|
||||||
|
// creating or parsing/verifying a token.
|
||||||
|
type Token struct {
|
||||||
|
Raw string // The raw token. Populated when you Parse a token
|
||||||
|
Method SigningMethod // The signing method used or to be used
|
||||||
|
Header map[string]interface{} // The first segment of the token
|
||||||
|
Claims Claims // The second segment of the token
|
||||||
|
Signature string // The third segment of the token. Populated when you Parse a token
|
||||||
|
Valid bool // Is the token valid? Populated when you Parse/Verify a token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Token. Takes a signing method
|
||||||
|
func New(method SigningMethod) *Token {
|
||||||
|
return NewWithClaims(method, MapClaims{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithClaims(method SigningMethod, claims Claims) *Token {
|
||||||
|
return &Token{
|
||||||
|
Header: map[string]interface{}{
|
||||||
|
"typ": "JWT",
|
||||||
|
"alg": method.Alg(),
|
||||||
|
},
|
||||||
|
Claims: claims,
|
||||||
|
Method: method,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the complete, signed token
|
||||||
|
func (t *Token) SignedString(key interface{}) (string, error) {
|
||||||
|
var sig, sstr string
|
||||||
|
var err error
|
||||||
|
if sstr, err = t.SigningString(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if sig, err = t.Method.Sign(sstr, key); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.Join([]string{sstr, sig}, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the signing string. This is the
|
||||||
|
// most expensive part of the whole deal. Unless you
|
||||||
|
// need this for something special, just go straight for
|
||||||
|
// the SignedString.
|
||||||
|
func (t *Token) SigningString() (string, error) {
|
||||||
|
var err error
|
||||||
|
parts := make([]string, 2)
|
||||||
|
for i, _ := range parts {
|
||||||
|
var jsonValue []byte
|
||||||
|
if i == 0 {
|
||||||
|
if jsonValue, err = json.Marshal(t.Header); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if jsonValue, err = json.Marshal(t.Claims); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[i] = EncodeSegment(jsonValue)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse, validate, and return a token.
|
||||||
|
// keyFunc will receive the parsed token and should return the key for validating.
|
||||||
|
// If everything is kosher, err will be nil
|
||||||
|
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
||||||
|
return new(Parser).Parse(tokenString, keyFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||||
|
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode JWT specific base64url encoding with padding stripped
|
||||||
|
func EncodeSegment(seg []byte) string {
|
||||||
|
return strings.TrimRight(base64.URLEncoding.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)
|
||||||
|
}
|
1
vendor/github.com/joho/godotenv/.gitignore
generated
vendored
Normal file
1
vendor/github.com/joho/godotenv/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.DS_Store
|
8
vendor/github.com/joho/godotenv/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/joho/godotenv/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.x
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
23
vendor/github.com/joho/godotenv/LICENCE
generated
vendored
Normal file
23
vendor/github.com/joho/godotenv/LICENCE
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Copyright (c) 2013 John Barton
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
163
vendor/github.com/joho/godotenv/README.md
generated
vendored
Normal file
163
vendor/github.com/joho/godotenv/README.md
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
# GoDotEnv [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4?svg=true)](https://ci.appveyor.com/project/joho/godotenv) [![Go Report Card](https://goreportcard.com/badge/github.com/joho/godotenv)](https://goreportcard.com/report/github.com/joho/godotenv)
|
||||||
|
|
||||||
|
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
|
||||||
|
|
||||||
|
From the original Library:
|
||||||
|
|
||||||
|
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
|
||||||
|
>
|
||||||
|
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
|
||||||
|
|
||||||
|
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
|
||||||
|
|
||||||
|
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
As a library
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/joho/godotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
or if you want to use it as a bin command
|
||||||
|
```shell
|
||||||
|
go get github.com/joho/godotenv/cmd/godotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add your application configuration to your `.env` file in the root of your project:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
S3_BUCKET=YOURS3BUCKET
|
||||||
|
SECRET_KEY=YOURSECRETKEYGOESHERE
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in your Go app you can do something like
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Bucket := os.Getenv("S3_BUCKET")
|
||||||
|
secretKey := os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
|
// now do something with s3 or whatever
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
|
||||||
|
|
||||||
|
```go
|
||||||
|
import _ "github.com/joho/godotenv/autoload"
|
||||||
|
```
|
||||||
|
|
||||||
|
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
|
||||||
|
|
||||||
|
```go
|
||||||
|
_ = godotenv.Load("somerandomfile")
|
||||||
|
_ = godotenv.Load("filenumberone.env", "filenumbertwo.env")
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# I am a comment and that is OK
|
||||||
|
SOME_VAR=someval
|
||||||
|
FOO=BAR # comments at line end are OK too
|
||||||
|
export BAR=BAZ
|
||||||
|
```
|
||||||
|
|
||||||
|
Or finally you can do YAML(ish) style
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
FOO: bar
|
||||||
|
BAR: baz
|
||||||
|
```
|
||||||
|
|
||||||
|
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
|
||||||
|
|
||||||
|
```go
|
||||||
|
var myEnv map[string]string
|
||||||
|
myEnv, err := godotenv.Read()
|
||||||
|
|
||||||
|
s3Bucket := myEnv["S3_BUCKET"]
|
||||||
|
```
|
||||||
|
|
||||||
|
... or from an `io.Reader` instead of a local file
|
||||||
|
|
||||||
|
```go
|
||||||
|
reader := getRemoteFile()
|
||||||
|
myEnv, err := godotenv.Parse(reader)
|
||||||
|
```
|
||||||
|
|
||||||
|
... or from a `string` if you so desire
|
||||||
|
|
||||||
|
```go
|
||||||
|
content := getRemoteFileContent()
|
||||||
|
myEnv, err := godotenv.Unmarshal(content)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Mode
|
||||||
|
|
||||||
|
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
|
||||||
|
|
||||||
|
```
|
||||||
|
godotenv -f /some/path/to/.env some_command with some args
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
|
||||||
|
|
||||||
|
### Writing Env Files
|
||||||
|
|
||||||
|
Godotenv can also write a map representing the environment to a correctly-formatted and escaped file
|
||||||
|
|
||||||
|
```go
|
||||||
|
env, err := godotenv.Unmarshal("KEY=value")
|
||||||
|
err := godotenv.Write(env, "./.env")
|
||||||
|
```
|
||||||
|
|
||||||
|
... or to a string
|
||||||
|
|
||||||
|
```go
|
||||||
|
env, err := godotenv.Unmarshal("KEY=value")
|
||||||
|
content, err := godotenv.Marshal(env)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases.
|
||||||
|
|
||||||
|
*code changes without tests will not be accepted*
|
||||||
|
|
||||||
|
1. Fork it
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create new Pull Request
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`.
|
||||||
|
|
||||||
|
Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1`
|
||||||
|
|
||||||
|
## CI
|
||||||
|
|
||||||
|
Linux: [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv)
|
||||||
|
|
||||||
|
## Who?
|
||||||
|
|
||||||
|
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library.
|
346
vendor/github.com/joho/godotenv/godotenv.go
generated
vendored
Normal file
346
vendor/github.com/joho/godotenv/godotenv.go
generated
vendored
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
||||||
|
//
|
||||||
|
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
|
||||||
|
//
|
||||||
|
// The TL;DR is that you make a .env file that looks something like
|
||||||
|
//
|
||||||
|
// SOME_ENV_VAR=somevalue
|
||||||
|
//
|
||||||
|
// and then in your go code you can call
|
||||||
|
//
|
||||||
|
// godotenv.Load()
|
||||||
|
//
|
||||||
|
// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
|
||||||
|
package godotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
|
||||||
|
|
||||||
|
// Load will read your env file(s) and load them into ENV for this process.
|
||||||
|
//
|
||||||
|
// Call this function as close as possible to the start of your program (ideally in main)
|
||||||
|
//
|
||||||
|
// If you call Load without any args it will default to loading .env in the current path
|
||||||
|
//
|
||||||
|
// You can otherwise tell it which files to load (there can be more than one) like
|
||||||
|
//
|
||||||
|
// godotenv.Load("fileone", "filetwo")
|
||||||
|
//
|
||||||
|
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
||||||
|
func Load(filenames ...string) (err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
err = loadFile(filename, false)
|
||||||
|
if err != nil {
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload will read your env file(s) and load them into ENV for this process.
|
||||||
|
//
|
||||||
|
// Call this function as close as possible to the start of your program (ideally in main)
|
||||||
|
//
|
||||||
|
// If you call Overload without any args it will default to loading .env in the current path
|
||||||
|
//
|
||||||
|
// You can otherwise tell it which files to load (there can be more than one) like
|
||||||
|
//
|
||||||
|
// godotenv.Overload("fileone", "filetwo")
|
||||||
|
//
|
||||||
|
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
|
||||||
|
func Overload(filenames ...string) (err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
err = loadFile(filename, true)
|
||||||
|
if err != nil {
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all env (with same file loading semantics as Load) but return values as
|
||||||
|
// a map rather than automatically writing values into env
|
||||||
|
func Read(filenames ...string) (envMap map[string]string, err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
envMap = make(map[string]string)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
individualEnvMap, individualErr := readFile(filename)
|
||||||
|
|
||||||
|
if individualErr != nil {
|
||||||
|
err = individualErr
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range individualEnvMap {
|
||||||
|
envMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse reads an env file from io.Reader, returning a map of keys and values.
|
||||||
|
func Parse(r io.Reader) (envMap map[string]string, err error) {
|
||||||
|
envMap = make(map[string]string)
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
lines = append(lines, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = scanner.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fullLine := range lines {
|
||||||
|
if !isIgnoredLine(fullLine) {
|
||||||
|
var key, value string
|
||||||
|
key, value, err = parseLine(fullLine, envMap)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
envMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Unmarshal reads an env file from a string, returning a map of keys and values.
|
||||||
|
func Unmarshal(str string) (envMap map[string]string, err error) {
|
||||||
|
return Parse(strings.NewReader(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec loads env vars from the specified filenames (empty map falls back to default)
|
||||||
|
// then executes the cmd specified.
|
||||||
|
//
|
||||||
|
// Simply hooks up os.Stdin/err/out to the command and calls Run()
|
||||||
|
//
|
||||||
|
// If you want more fine grained control over your command it's recommended
|
||||||
|
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
|
||||||
|
func Exec(filenames []string, cmd string, cmdArgs []string) error {
|
||||||
|
Load(filenames...)
|
||||||
|
|
||||||
|
command := exec.Command(cmd, cmdArgs...)
|
||||||
|
command.Stdin = os.Stdin
|
||||||
|
command.Stdout = os.Stdout
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
return command.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write serializes the given environment and writes it to a file
|
||||||
|
func Write(envMap map[string]string, filename string) error {
|
||||||
|
content, error := Marshal(envMap)
|
||||||
|
if error != nil {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
file, error := os.Create(filename)
|
||||||
|
if error != nil {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
_, err := file.WriteString(content)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal outputs the given environment as a dotenv-formatted environment file.
|
||||||
|
// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
|
||||||
|
func Marshal(envMap map[string]string) (string, error) {
|
||||||
|
lines := make([]string, 0, len(envMap))
|
||||||
|
for k, v := range envMap {
|
||||||
|
lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
|
||||||
|
}
|
||||||
|
sort.Strings(lines)
|
||||||
|
return strings.Join(lines, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filenamesOrDefault(filenames []string) []string {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return []string{".env"}
|
||||||
|
}
|
||||||
|
return filenames
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFile(filename string, overload bool) error {
|
||||||
|
envMap, err := readFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEnv := map[string]bool{}
|
||||||
|
rawEnv := os.Environ()
|
||||||
|
for _, rawEnvLine := range rawEnv {
|
||||||
|
key := strings.Split(rawEnvLine, "=")[0]
|
||||||
|
currentEnv[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range envMap {
|
||||||
|
if !currentEnv[key] || overload {
|
||||||
|
os.Setenv(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(filename string) (envMap map[string]string, err error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return Parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
|
||||||
|
if len(line) == 0 {
|
||||||
|
err = errors.New("zero length string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ditch the comments (but keep quoted hashes)
|
||||||
|
if strings.Contains(line, "#") {
|
||||||
|
segmentsBetweenHashes := strings.Split(line, "#")
|
||||||
|
quotesAreOpen := false
|
||||||
|
var segmentsToKeep []string
|
||||||
|
for _, segment := range segmentsBetweenHashes {
|
||||||
|
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
|
||||||
|
if quotesAreOpen {
|
||||||
|
quotesAreOpen = false
|
||||||
|
segmentsToKeep = append(segmentsToKeep, segment)
|
||||||
|
} else {
|
||||||
|
quotesAreOpen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(segmentsToKeep) == 0 || quotesAreOpen {
|
||||||
|
segmentsToKeep = append(segmentsToKeep, segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strings.Join(segmentsToKeep, "#")
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEquals := strings.Index(line, "=")
|
||||||
|
firstColon := strings.Index(line, ":")
|
||||||
|
splitString := strings.SplitN(line, "=", 2)
|
||||||
|
if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
|
||||||
|
//this is a yaml-style line
|
||||||
|
splitString = strings.SplitN(line, ":", 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(splitString) != 2 {
|
||||||
|
err = errors.New("Can't separate key from value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the key
|
||||||
|
key = splitString[0]
|
||||||
|
if strings.HasPrefix(key, "export") {
|
||||||
|
key = strings.TrimPrefix(key, "export")
|
||||||
|
}
|
||||||
|
key = strings.Trim(key, " ")
|
||||||
|
|
||||||
|
// Parse the value
|
||||||
|
value = parseValue(splitString[1], envMap)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(value string, envMap map[string]string) string {
|
||||||
|
|
||||||
|
// trim
|
||||||
|
value = strings.Trim(value, " ")
|
||||||
|
|
||||||
|
// check if we've got quoted values or possible escapes
|
||||||
|
if len(value) > 1 {
|
||||||
|
rs := regexp.MustCompile(`\A'(.*)'\z`)
|
||||||
|
singleQuotes := rs.FindStringSubmatch(value)
|
||||||
|
|
||||||
|
rd := regexp.MustCompile(`\A"(.*)"\z`)
|
||||||
|
doubleQuotes := rd.FindStringSubmatch(value)
|
||||||
|
|
||||||
|
if singleQuotes != nil || doubleQuotes != nil {
|
||||||
|
// pull the quotes off the edges
|
||||||
|
value = value[1 : len(value)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if doubleQuotes != nil {
|
||||||
|
// expand newlines
|
||||||
|
escapeRegex := regexp.MustCompile(`\\.`)
|
||||||
|
value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string {
|
||||||
|
c := strings.TrimPrefix(match, `\`)
|
||||||
|
switch c {
|
||||||
|
case "n":
|
||||||
|
return "\n"
|
||||||
|
case "r":
|
||||||
|
return "\r"
|
||||||
|
default:
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// unescape characters
|
||||||
|
e := regexp.MustCompile(`\\([^$])`)
|
||||||
|
value = e.ReplaceAllString(value, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if singleQuotes == nil {
|
||||||
|
value = expandVariables(value, envMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandVariables(v string, m map[string]string) string {
|
||||||
|
r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
|
||||||
|
|
||||||
|
return r.ReplaceAllStringFunc(v, func(s string) string {
|
||||||
|
submatch := r.FindStringSubmatch(s)
|
||||||
|
|
||||||
|
if submatch == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if submatch[1] == "\\" || submatch[2] == "(" {
|
||||||
|
return submatch[0][1:]
|
||||||
|
} else if submatch[4] != "" {
|
||||||
|
return m[submatch[4]]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIgnoredLine(line string) bool {
|
||||||
|
trimmedLine := strings.Trim(line, " \n\t")
|
||||||
|
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleQuoteEscape(line string) string {
|
||||||
|
for _, c := range doubleQuoteSpecialChars {
|
||||||
|
toReplace := "\\" + string(c)
|
||||||
|
if c == '\n' {
|
||||||
|
toReplace = `\n`
|
||||||
|
}
|
||||||
|
if c == '\r' {
|
||||||
|
toReplace = `\r`
|
||||||
|
}
|
||||||
|
line = strings.Replace(line, string(c), toReplace, -1)
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
18
vendor/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
Normal file
18
vendor/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
- master
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
- go vet ./...
|
||||||
|
- test -z "$(gofmt -d -s . | tee /dev/stderr)"
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
29
vendor/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
Normal file
29
vendor/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2013, Julien Schmidt
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
300
vendor/github.com/julienschmidt/httprouter/README.md
generated
vendored
Normal file
300
vendor/github.com/julienschmidt/httprouter/README.md
generated
vendored
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.svg?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![Coverage Status](https://coveralls.io/repos/github/julienschmidt/httprouter/badge.svg?branch=master)](https://coveralls.io/github/julienschmidt/httprouter?branch=master) [![GoDoc](https://godoc.org/github.com/julienschmidt/httprouter?status.svg)](http://godoc.org/github.com/julienschmidt/httprouter)
|
||||||
|
|
||||||
|
HttpRouter is a lightweight high performance HTTP request router (also called *multiplexer* or just *mux* for short) for [Go](https://golang.org/).
|
||||||
|
|
||||||
|
In contrast to the [default mux](https://golang.org/pkg/net/http/#ServeMux) of Go's `net/http` package, this router supports variables in the routing pattern and matches against the request method. It also scales better.
|
||||||
|
|
||||||
|
The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**Only explicit matches:** With other routers, like [`http.ServeMux`](https://golang.org/pkg/net/http/#ServeMux), a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like *longest match* or *first registered, first matched*. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO and improves the user experience.
|
||||||
|
|
||||||
|
**Stop caring about trailing slashes:** Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can [turn off this behavior](https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash).
|
||||||
|
|
||||||
|
**Path auto-correction:** Besides detecting the missing or additional trailing slash at no extra cost, the router can also fix wrong cases and remove superfluous path elements (like `../` or `//`). Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? HttpRouter can help him by making a case-insensitive look-up and redirecting him to the correct URL.
|
||||||
|
|
||||||
|
**Parameters in your routing pattern:** Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap.
|
||||||
|
|
||||||
|
**Zero Garbage:** The matching and dispatching process generates zero bytes of garbage. The only heap allocations that are made are building the slice of the key-value pairs for path parameters, and building new context and request objects (the latter only in the standard `Handler`/`HandlerFunc` API). In the 3-argument API, if the request path contains no parameters not a single heap allocation is necessary.
|
||||||
|
|
||||||
|
**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). See below for technical details of the implementation.
|
||||||
|
|
||||||
|
**No more server crashes:** You can set a [Panic handler](https://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics occurring during handling a HTTP request. The router then recovers and lets the `PanicHandler` log what happened and deliver a nice error page.
|
||||||
|
|
||||||
|
**Perfect for APIs:** The router design encourages to build sensible, hierarchical RESTful APIs. Moreover it has built-in native support for [OPTIONS requests](http://zacstewart.com/2012/04/14/http-options-method.html) and `405 Method Not Allowed` replies.
|
||||||
|
|
||||||
|
Of course you can also set **custom [`NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and [`MethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](https://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
|
||||||
|
|
||||||
|
Let's start with a trivial example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Welcome!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/hello/:name", Hello)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Named parameters
|
||||||
|
|
||||||
|
As you can see, `:name` is a *named parameter*. The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: `:name` can be retrieved by `ByName("name")`.
|
||||||
|
|
||||||
|
When using a `http.Handler` (using `router.Handler` or `http.HandlerFunc`) instead of HttpRouter's handle API using a 3rd function parameter, the named parameters are stored in the `request.Context`. See more below under [Why doesn't this work with http.Handler?](#why-doesnt-this-work-with-httphandler).
|
||||||
|
|
||||||
|
Named parameters only match a single path segment:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pattern: /user/:user
|
||||||
|
|
||||||
|
/user/gordon match
|
||||||
|
/user/you match
|
||||||
|
/user/gordon/profile no match
|
||||||
|
/user/ no match
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other.
|
||||||
|
|
||||||
|
### Catch-All parameters
|
||||||
|
|
||||||
|
The second type are *catch-all* parameters and have the form `*name`. Like the name suggests, they match everything. Therefore they must always be at the **end** of the pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pattern: /src/*filepath
|
||||||
|
|
||||||
|
/src/ match
|
||||||
|
/src/somefile.go match
|
||||||
|
/src/subdir/somefile.go match
|
||||||
|
```
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
The router relies on a tree structure which makes heavy use of *common prefixes*, it is basically a *compact* [*prefix tree*](https://en.wikipedia.org/wiki/Trie) (or just [*Radix tree*](https://en.wikipedia.org/wiki/Radix_tree)). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree for the `GET` request method could look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Priority Path Handle
|
||||||
|
9 \ *<1>
|
||||||
|
3 ├s nil
|
||||||
|
2 |├earch\ *<2>
|
||||||
|
1 |└upport\ *<3>
|
||||||
|
2 ├blog\ *<4>
|
||||||
|
1 | └:post nil
|
||||||
|
1 | └\ *<5>
|
||||||
|
2 ├about-us\ *<6>
|
||||||
|
1 | └team\ *<7>
|
||||||
|
1 └contact\ *<8>
|
||||||
|
```
|
||||||
|
|
||||||
|
Every `*<num>` represents the memory address of a handler function (a pointer). If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder ([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a tree structure also allows us to use dynamic parts like the `:post` parameter, since we actually match against the routing patterns instead of just comparing hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), this works very well and efficient.
|
||||||
|
|
||||||
|
Since URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, it also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree.
|
||||||
|
|
||||||
|
For even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..). This helps in two ways:
|
||||||
|
|
||||||
|
1. Nodes which are part of the most routing paths are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.
|
||||||
|
2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The following scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.
|
||||||
|
|
||||||
|
```
|
||||||
|
├------------
|
||||||
|
├---------
|
||||||
|
├-----
|
||||||
|
├----
|
||||||
|
├--
|
||||||
|
├--
|
||||||
|
└-
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why doesn't this work with `http.Handler`?
|
||||||
|
|
||||||
|
**It does!** The router itself implements the `http.Handler` interface. Moreover the router provides convenient [adapters for `http.Handler`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [`http.HandlerFunc`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s which allows them to be used as a [`httprouter.Handle`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route.
|
||||||
|
|
||||||
|
Named parameters can be accessed `request.Context`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Hello(w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := httprouter.ParamsFromContext(r.Context())
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, one can also use `params := r.Context().Value(httprouter.ParamsKey)` instead of the helper function.
|
||||||
|
|
||||||
|
Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
|
||||||
|
|
||||||
|
## Automatic OPTIONS responses and CORS
|
||||||
|
|
||||||
|
One might wish to modify automatic responses to OPTIONS requests, e.g. to support [CORS preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/preflight_request) or to set other headers.
|
||||||
|
This can be achieved using the [`Router.GlobalOPTIONS`](https://godoc.org/github.com/julienschmidt/httprouter#Router.GlobalOPTIONS) handler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Access-Control-Request-Method") != "" {
|
||||||
|
// Set CORS headers
|
||||||
|
header := w.Header()
|
||||||
|
header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
|
||||||
|
header.Set("Access-Control-Allow-Origin", "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust status code to 204
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Where can I find Middleware *X*?
|
||||||
|
|
||||||
|
This package just provides a very efficient request router with a few extra features. The router is just a [`http.Handler`](https://golang.org/pkg/net/http/#Handler), you can chain any http.Handler compatible middleware before the router, for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](https://justinas.org/writing-http-middleware-in-go/), it's very easy!
|
||||||
|
|
||||||
|
Alternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter).
|
||||||
|
|
||||||
|
### Multi-domain / Sub-domains
|
||||||
|
|
||||||
|
Here is a quick example: Does your server serve multiple domains / hosts?
|
||||||
|
You want to use sub-domains?
|
||||||
|
Define a router per host!
|
||||||
|
|
||||||
|
```go
|
||||||
|
// We need an object that implements the http.Handler interface.
|
||||||
|
// Therefore we need a type for which we implement the ServeHTTP method.
|
||||||
|
// We just use a map here, in which we map host names (with port) to http.Handlers
|
||||||
|
type HostSwitch map[string]http.Handler
|
||||||
|
|
||||||
|
// Implement the ServeHTTP method on our new type
|
||||||
|
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check if a http.Handler is registered for the given host.
|
||||||
|
// If yes, use it to handle the request.
|
||||||
|
if handler := hs[r.Host]; handler != nil {
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
// Handle host names for which no handler is registered
|
||||||
|
http.Error(w, "Forbidden", 403) // Or Redirect?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize a router as usual
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/hello/:name", Hello)
|
||||||
|
|
||||||
|
// Make a new HostSwitch and insert the router (our http handler)
|
||||||
|
// for example.com and port 12345
|
||||||
|
hs := make(HostSwitch)
|
||||||
|
hs["example.com:12345"] = router
|
||||||
|
|
||||||
|
// Use the HostSwitch to listen and serve on port 12345
|
||||||
|
log.Fatal(http.ListenAndServe(":12345", hs))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Authentication
|
||||||
|
|
||||||
|
Another quick example: Basic Authentication (RFC 2617) for handles:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// Get the Basic Authentication credentials
|
||||||
|
user, password, hasAuth := r.BasicAuth()
|
||||||
|
|
||||||
|
if hasAuth && user == requiredUser && password == requiredPassword {
|
||||||
|
// Delegate request to the given handle
|
||||||
|
h(w, r, ps)
|
||||||
|
} else {
|
||||||
|
// Request Basic Authentication otherwise
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Not protected!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Protected!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
user := "gordon"
|
||||||
|
pass := "secret!"
|
||||||
|
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/protected/", BasicAuth(Protected, user, pass))
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chaining with the NotFound handler
|
||||||
|
|
||||||
|
**NOTE: It might be required to set [`Router.HandleMethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.**
|
||||||
|
|
||||||
|
You can use another [`http.Handler`](https://golang.org/pkg/net/http/#Handler), for example another router, to handle requests which could not be matched by this router by using the [`Router.NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining.
|
||||||
|
|
||||||
|
### Static files
|
||||||
|
|
||||||
|
The `NotFound` handler can for example be used to serve static files from the root path `/` (like an `index.html` file along with other assets):
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Serve static files from the ./public directory
|
||||||
|
router.NotFound = http.FileServer(http.Dir("public"))
|
||||||
|
```
|
||||||
|
|
||||||
|
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
|
||||||
|
|
||||||
|
## Web Frameworks based on HttpRouter
|
||||||
|
|
||||||
|
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
|
||||||
|
|
||||||
|
* [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework
|
||||||
|
* [api2go](https://github.com/manyminds/api2go): A JSON API Implementation for Go
|
||||||
|
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
|
||||||
|
* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go
|
||||||
|
* [goMiddlewareChain](https://github.com/TobiEiss/goMiddlewareChain): An express.js-like-middleware-chain
|
||||||
|
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
|
||||||
|
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow
|
||||||
|
* [httpway](https://github.com/corneldamian/httpway): Simple middleware extension with context for httprouter and a server with gracefully shutdown support
|
||||||
|
* [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context
|
||||||
|
* [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba
|
||||||
|
* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang
|
||||||
|
* [pbgo](https://github.com/chai2010/pbgo): pbgo is a mini RPC/REST framework based on Protobuf
|
||||||
|
* [River](https://github.com/abiosoft/river): River is a simple and lightweight REST server
|
||||||
|
* [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts
|
||||||
|
* [xmux](https://github.com/rs/xmux): xmux is a httprouter fork on top of xhandler (net/context aware)
|
3
vendor/github.com/julienschmidt/httprouter/go.mod
generated
vendored
Normal file
3
vendor/github.com/julienschmidt/httprouter/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/julienschmidt/httprouter
|
||||||
|
|
||||||
|
go 1.7
|
123
vendor/github.com/julienschmidt/httprouter/path.go
generated
vendored
Normal file
123
vendor/github.com/julienschmidt/httprouter/path.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Based on the path package, Copyright 2009 The Go Authors.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||||
|
// for p, eliminating . and .. elements.
|
||||||
|
//
|
||||||
|
// The following rules are applied iteratively until no further processing can
|
||||||
|
// be done:
|
||||||
|
// 1. Replace multiple slashes with a single slash.
|
||||||
|
// 2. Eliminate each . path name element (the current directory).
|
||||||
|
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||||
|
// along with the non-.. element that precedes it.
|
||||||
|
// 4. Eliminate .. elements that begin a rooted path:
|
||||||
|
// that is, replace "/.." by "/" at the beginning of a path.
|
||||||
|
//
|
||||||
|
// If the result of this process is an empty string, "/" is returned
|
||||||
|
func CleanPath(p string) string {
|
||||||
|
// Turn empty string into "/"
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(p)
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// reading from path; r is index of next byte to process.
|
||||||
|
// writing to buf; w is index of next byte to write.
|
||||||
|
|
||||||
|
// path must start with '/'
|
||||||
|
r := 1
|
||||||
|
w := 1
|
||||||
|
|
||||||
|
if p[0] != '/' {
|
||||||
|
r = 0
|
||||||
|
buf = make([]byte, n+1)
|
||||||
|
buf[0] = '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing := n > 1 && p[n-1] == '/'
|
||||||
|
|
||||||
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||||
|
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||||
|
// loop has no expensive function calls (except 1x make)
|
||||||
|
|
||||||
|
for r < n {
|
||||||
|
switch {
|
||||||
|
case p[r] == '/':
|
||||||
|
// empty path element, trailing slash is added after the end
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && r+1 == n:
|
||||||
|
trailing = true
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
|
// . element
|
||||||
|
r += 2
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
|
// .. element: remove to last /
|
||||||
|
r += 3
|
||||||
|
|
||||||
|
if w > 1 {
|
||||||
|
// can backtrack
|
||||||
|
w--
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
for w > 1 && p[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for w > 1 && buf[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// real path element.
|
||||||
|
// add slash if needed
|
||||||
|
if w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy element
|
||||||
|
for r < n && p[r] != '/' {
|
||||||
|
bufApp(&buf, p, w, p[r])
|
||||||
|
w++
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-append trailing slash
|
||||||
|
if trailing && w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
return p[:w]
|
||||||
|
}
|
||||||
|
return string(buf[:w])
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helper to lazily create a buffer if necessary
|
||||||
|
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||||
|
if *buf == nil {
|
||||||
|
if s[w] == c {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*buf = make([]byte, len(s))
|
||||||
|
copy(*buf, s[:w])
|
||||||
|
}
|
||||||
|
(*buf)[w] = c
|
||||||
|
}
|
452
vendor/github.com/julienschmidt/httprouter/router.go
generated
vendored
Normal file
452
vendor/github.com/julienschmidt/httprouter/router.go
generated
vendored
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
// Package httprouter is a trie based high performance HTTP request router.
|
||||||
|
//
|
||||||
|
// A trivial example is:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
// "github.com/julienschmidt/httprouter"
|
||||||
|
// "net/http"
|
||||||
|
// "log"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
// fmt.Fprint(w, "Welcome!\n")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// router := httprouter.New()
|
||||||
|
// router.GET("/", Index)
|
||||||
|
// router.GET("/hello/:name", Hello)
|
||||||
|
//
|
||||||
|
// log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The router matches incoming requests by the request method and the path.
|
||||||
|
// If a handle is registered for this path and method, the router delegates the
|
||||||
|
// request to that function.
|
||||||
|
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
|
||||||
|
// register handles, for all other methods router.Handle can be used.
|
||||||
|
//
|
||||||
|
// The registered path, against which the router matches incoming requests, can
|
||||||
|
// contain two types of parameters:
|
||||||
|
// Syntax Type
|
||||||
|
// :name named parameter
|
||||||
|
// *name catch-all parameter
|
||||||
|
//
|
||||||
|
// Named parameters are dynamic path segments. They match anything until the
|
||||||
|
// next '/' or the path end:
|
||||||
|
// Path: /blog/:category/:post
|
||||||
|
//
|
||||||
|
// Requests:
|
||||||
|
// /blog/go/request-routers match: category="go", post="request-routers"
|
||||||
|
// /blog/go/request-routers/ no match, but the router would redirect
|
||||||
|
// /blog/go/ no match
|
||||||
|
// /blog/go/request-routers/comments no match
|
||||||
|
//
|
||||||
|
// Catch-all parameters match anything until the path end, including the
|
||||||
|
// directory index (the '/' before the catch-all). Since they match anything
|
||||||
|
// until the end, catch-all parameters must always be the final path element.
|
||||||
|
// Path: /files/*filepath
|
||||||
|
//
|
||||||
|
// Requests:
|
||||||
|
// /files/ match: filepath="/"
|
||||||
|
// /files/LICENSE match: filepath="/LICENSE"
|
||||||
|
// /files/templates/article.html match: filepath="/templates/article.html"
|
||||||
|
// /files no match, but the router would redirect
|
||||||
|
//
|
||||||
|
// The value of parameters is saved as a slice of the Param struct, consisting
|
||||||
|
// each of a key and a value. The slice is passed to the Handle func as a third
|
||||||
|
// parameter.
|
||||||
|
// There are two ways to retrieve the value of a parameter:
|
||||||
|
// // by the name of the parameter
|
||||||
|
// user := ps.ByName("user") // defined by :user or *user
|
||||||
|
//
|
||||||
|
// // by the index of the parameter. This way you can also get the name (key)
|
||||||
|
// thirdKey := ps[2].Key // the name of the 3rd parameter
|
||||||
|
// thirdValue := ps[2].Value // the value of the 3rd parameter
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle is a function that can be registered to a route to handle HTTP
|
||||||
|
// requests. Like http.HandlerFunc, but has a third parameter for the values of
|
||||||
|
// wildcards (variables).
|
||||||
|
type Handle func(http.ResponseWriter, *http.Request, Params)
|
||||||
|
|
||||||
|
// Param is a single URL parameter, consisting of a key and a value.
|
||||||
|
type Param struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params is a Param-slice, as returned by the router.
|
||||||
|
// The slice is ordered, the first URL parameter is also the first slice value.
|
||||||
|
// It is therefore safe to read values by the index.
|
||||||
|
type Params []Param
|
||||||
|
|
||||||
|
// ByName returns the value of the first Param which key matches the given name.
|
||||||
|
// If no matching Param is found, an empty string is returned.
|
||||||
|
func (ps Params) ByName(name string) string {
|
||||||
|
for i := range ps {
|
||||||
|
if ps[i].Key == name {
|
||||||
|
return ps[i].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramsKey struct{}
|
||||||
|
|
||||||
|
// ParamsKey is the request context key under which URL params are stored.
|
||||||
|
var ParamsKey = paramsKey{}
|
||||||
|
|
||||||
|
// ParamsFromContext pulls the URL parameters from a request context,
|
||||||
|
// or returns nil if none are present.
|
||||||
|
func ParamsFromContext(ctx context.Context) Params {
|
||||||
|
p, _ := ctx.Value(ParamsKey).(Params)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router is a http.Handler which can be used to dispatch requests to different
|
||||||
|
// handler functions via configurable routes
|
||||||
|
type Router struct {
|
||||||
|
trees map[string]*node
|
||||||
|
|
||||||
|
// Enables automatic redirection if the current route can't be matched but a
|
||||||
|
// handler for the path with (without) the trailing slash exists.
|
||||||
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
|
// client is redirected to /foo with http status code 301 for GET requests
|
||||||
|
// and 307 for all other request methods.
|
||||||
|
RedirectTrailingSlash bool
|
||||||
|
|
||||||
|
// If enabled, the router tries to fix the current request path, if no
|
||||||
|
// handle is registered for it.
|
||||||
|
// First superfluous path elements like ../ or // are removed.
|
||||||
|
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||||
|
// If a handle can be found for this route, the router makes a redirection
|
||||||
|
// to the corrected path with status code 301 for GET requests and 307 for
|
||||||
|
// all other request methods.
|
||||||
|
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||||
|
// RedirectTrailingSlash is independent of this option.
|
||||||
|
RedirectFixedPath bool
|
||||||
|
|
||||||
|
// If enabled, the router checks if another method is allowed for the
|
||||||
|
// current route, if the current request can not be routed.
|
||||||
|
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||||
|
// and HTTP status code 405.
|
||||||
|
// If no other Method is allowed, the request is delegated to the NotFound
|
||||||
|
// handler.
|
||||||
|
HandleMethodNotAllowed bool
|
||||||
|
|
||||||
|
// If enabled, the router automatically replies to OPTIONS requests.
|
||||||
|
// Custom OPTIONS handlers take priority over automatic replies.
|
||||||
|
HandleOPTIONS bool
|
||||||
|
|
||||||
|
// An optional http.Handler that is called on automatic OPTIONS requests.
|
||||||
|
// The handler is only called if HandleOPTIONS is true and no OPTIONS
|
||||||
|
// handler for the specific path was set.
|
||||||
|
// The "Allowed" header is set before calling the handler.
|
||||||
|
GlobalOPTIONS http.Handler
|
||||||
|
|
||||||
|
// Cached value of global (*) allowed methods
|
||||||
|
globalAllowed string
|
||||||
|
|
||||||
|
// Configurable http.Handler which is called when no matching route is
|
||||||
|
// found. If it is not set, http.NotFound is used.
|
||||||
|
NotFound http.Handler
|
||||||
|
|
||||||
|
// Configurable http.Handler which is called when a request
|
||||||
|
// cannot be routed and HandleMethodNotAllowed is true.
|
||||||
|
// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
|
||||||
|
// The "Allow" header with allowed request methods is set before the handler
|
||||||
|
// is called.
|
||||||
|
MethodNotAllowed http.Handler
|
||||||
|
|
||||||
|
// Function to handle panics recovered from http handlers.
|
||||||
|
// It should be used to generate a error page and return the http error code
|
||||||
|
// 500 (Internal Server Error).
|
||||||
|
// The handler can be used to keep your server from crashing because of
|
||||||
|
// unrecovered panics.
|
||||||
|
PanicHandler func(http.ResponseWriter, *http.Request, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the Router conforms with the http.Handler interface
|
||||||
|
var _ http.Handler = New()
|
||||||
|
|
||||||
|
// New returns a new initialized Router.
|
||||||
|
// Path auto-correction, including trailing slashes, is enabled by default.
|
||||||
|
func New() *Router {
|
||||||
|
return &Router{
|
||||||
|
RedirectTrailingSlash: true,
|
||||||
|
RedirectFixedPath: true,
|
||||||
|
HandleMethodNotAllowed: true,
|
||||||
|
HandleOPTIONS: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
|
||||||
|
func (r *Router) GET(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodGet, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
|
||||||
|
func (r *Router) HEAD(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodHead, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
|
||||||
|
func (r *Router) OPTIONS(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodOptions, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST is a shortcut for router.Handle(http.MethodPost, path, handle)
|
||||||
|
func (r *Router) POST(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPost, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
|
||||||
|
func (r *Router) PUT(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPut, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
|
||||||
|
func (r *Router) PATCH(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPatch, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
|
||||||
|
func (r *Router) DELETE(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodDelete, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new request handle with the given path and method.
|
||||||
|
//
|
||||||
|
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||||
|
// functions can be used.
|
||||||
|
//
|
||||||
|
// This function is intended for bulk loading and to allow the usage of less
|
||||||
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
|
// communication with a proxy).
|
||||||
|
func (r *Router) Handle(method, path string, handle Handle) {
|
||||||
|
if len(path) < 1 || path[0] != '/' {
|
||||||
|
panic("path must begin with '/' in path '" + path + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.trees == nil {
|
||||||
|
r.trees = make(map[string]*node)
|
||||||
|
}
|
||||||
|
|
||||||
|
root := r.trees[method]
|
||||||
|
if root == nil {
|
||||||
|
root = new(node)
|
||||||
|
r.trees[method] = root
|
||||||
|
|
||||||
|
r.globalAllowed = r.allowed("*", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
root.addRoute(path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is an adapter which allows the usage of an http.Handler as a
|
||||||
|
// request handle.
|
||||||
|
// The Params are available in the request context under ParamsKey.
|
||||||
|
func (r *Router) Handler(method, path string, handler http.Handler) {
|
||||||
|
r.Handle(method, path,
|
||||||
|
func(w http.ResponseWriter, req *http.Request, p Params) {
|
||||||
|
if len(p) > 0 {
|
||||||
|
ctx := req.Context()
|
||||||
|
ctx = context.WithValue(ctx, ParamsKey, p)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
|
||||||
|
// request handle.
|
||||||
|
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
||||||
|
r.Handler(method, path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFiles serves files from the given file system root.
|
||||||
|
// The path must end with "/*filepath", files are then served from the local
|
||||||
|
// path /defined/root/dir/*filepath.
|
||||||
|
// For example if root is "/etc" and *filepath is "passwd", the local file
|
||||||
|
// "/etc/passwd" would be served.
|
||||||
|
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
||||||
|
// of the Router's NotFound handler.
|
||||||
|
// To use the operating system's file system implementation,
|
||||||
|
// use http.Dir:
|
||||||
|
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
||||||
|
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
||||||
|
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
|
||||||
|
panic("path must end with /*filepath in path '" + path + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileServer := http.FileServer(root)
|
||||||
|
|
||||||
|
r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
|
||||||
|
req.URL.Path = ps.ByName("filepath")
|
||||||
|
fileServer.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if rcv := recover(); rcv != nil {
|
||||||
|
r.PanicHandler(w, req, rcv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup allows the manual lookup of a method + path combo.
|
||||||
|
// This is e.g. useful to build a framework around this router.
|
||||||
|
// If the path was found, it returns the handle function and the path parameter
|
||||||
|
// values. Otherwise the third return value indicates whether a redirection to
|
||||||
|
// the same path with an extra / without the trailing slash should be performed.
|
||||||
|
func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
|
||||||
|
if root := r.trees[method]; root != nil {
|
||||||
|
return root.getValue(path)
|
||||||
|
}
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) allowed(path, reqMethod string) (allow string) {
|
||||||
|
allowed := make([]string, 0, 9)
|
||||||
|
|
||||||
|
if path == "*" { // server-wide
|
||||||
|
// empty method is used for internal calls to refresh the cache
|
||||||
|
if reqMethod == "" {
|
||||||
|
for method := range r.trees {
|
||||||
|
if method == http.MethodOptions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, method)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return r.globalAllowed
|
||||||
|
}
|
||||||
|
} else { // specific path
|
||||||
|
for method := range r.trees {
|
||||||
|
// Skip the requested method - we already tried this one
|
||||||
|
if method == reqMethod || method == http.MethodOptions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, _, _ := r.trees[method].getValue(path)
|
||||||
|
if handle != nil {
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowed) > 0 {
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, http.MethodOptions)
|
||||||
|
|
||||||
|
// Sort allowed methods.
|
||||||
|
// sort.Strings(allowed) unfortunately causes unnecessary allocations
|
||||||
|
// due to allowed being moved to the heap and interface conversion
|
||||||
|
for i, l := 1, len(allowed); i < l; i++ {
|
||||||
|
for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
|
||||||
|
allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return as comma separated list
|
||||||
|
return strings.Join(allowed, ", ")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP makes the router implement the http.Handler interface.
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if r.PanicHandler != nil {
|
||||||
|
defer r.recv(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := req.URL.Path
|
||||||
|
|
||||||
|
if root := r.trees[req.Method]; root != nil {
|
||||||
|
if handle, ps, tsr := root.getValue(path); handle != nil {
|
||||||
|
handle(w, req, ps)
|
||||||
|
return
|
||||||
|
} else if req.Method != http.MethodConnect && path != "/" {
|
||||||
|
code := 301 // Permanent redirect, request with GET method
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
// Temporary redirect, request with same method
|
||||||
|
// As of Go 1.3, Go does not support status code 308.
|
||||||
|
code = 307
|
||||||
|
}
|
||||||
|
|
||||||
|
if tsr && r.RedirectTrailingSlash {
|
||||||
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||||
|
req.URL.Path = path[:len(path)-1]
|
||||||
|
} else {
|
||||||
|
req.URL.Path = path + "/"
|
||||||
|
}
|
||||||
|
http.Redirect(w, req, req.URL.String(), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to fix the request path
|
||||||
|
if r.RedirectFixedPath {
|
||||||
|
fixedPath, found := root.findCaseInsensitivePath(
|
||||||
|
CleanPath(path),
|
||||||
|
r.RedirectTrailingSlash,
|
||||||
|
)
|
||||||
|
if found {
|
||||||
|
req.URL.Path = string(fixedPath)
|
||||||
|
http.Redirect(w, req, req.URL.String(), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == http.MethodOptions && r.HandleOPTIONS {
|
||||||
|
// Handle OPTIONS requests
|
||||||
|
if allow := r.allowed(path, http.MethodOptions); allow != "" {
|
||||||
|
w.Header().Set("Allow", allow)
|
||||||
|
if r.GlobalOPTIONS != nil {
|
||||||
|
r.GlobalOPTIONS.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if r.HandleMethodNotAllowed { // Handle 405
|
||||||
|
if allow := r.allowed(path, req.Method); allow != "" {
|
||||||
|
w.Header().Set("Allow", allow)
|
||||||
|
if r.MethodNotAllowed != nil {
|
||||||
|
r.MethodNotAllowed.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
http.Error(w,
|
||||||
|
http.StatusText(http.StatusMethodNotAllowed),
|
||||||
|
http.StatusMethodNotAllowed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 404
|
||||||
|
if r.NotFound != nil {
|
||||||
|
r.NotFound.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, req)
|
||||||
|
}
|
||||||
|
}
|
666
vendor/github.com/julienschmidt/httprouter/tree.go
generated
vendored
Normal file
666
vendor/github.com/julienschmidt/httprouter/tree.go
generated
vendored
Normal file
|
@ -0,0 +1,666 @@
|
||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a <= b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxParamCount uint8 = ^uint8(0)
|
||||||
|
|
||||||
|
func countParams(path string) uint8 {
|
||||||
|
var n uint
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] != ':' && path[i] != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n >= uint(maxParamCount) {
|
||||||
|
return maxParamCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
static nodeType = iota // default
|
||||||
|
root
|
||||||
|
param
|
||||||
|
catchAll
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
path string
|
||||||
|
wildChild bool
|
||||||
|
nType nodeType
|
||||||
|
maxParams uint8
|
||||||
|
priority uint32
|
||||||
|
indices string
|
||||||
|
children []*node
|
||||||
|
handle Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// increments priority of the given child and reorders if necessary
|
||||||
|
func (n *node) incrementChildPrio(pos int) int {
|
||||||
|
n.children[pos].priority++
|
||||||
|
prio := n.children[pos].priority
|
||||||
|
|
||||||
|
// adjust position (move to front)
|
||||||
|
newPos := pos
|
||||||
|
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||||
|
// swap node positions
|
||||||
|
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||||
|
|
||||||
|
newPos--
|
||||||
|
}
|
||||||
|
|
||||||
|
// build new index char string
|
||||||
|
if newPos != pos {
|
||||||
|
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||||
|
n.indices[pos:pos+1] + // the index char we move
|
||||||
|
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoute adds a node with the given handle to the path.
|
||||||
|
// Not concurrency-safe!
|
||||||
|
func (n *node) addRoute(path string, handle Handle) {
|
||||||
|
fullPath := path
|
||||||
|
n.priority++
|
||||||
|
numParams := countParams(path)
|
||||||
|
|
||||||
|
// non-empty tree
|
||||||
|
if len(n.path) > 0 || len(n.children) > 0 {
|
||||||
|
walk:
|
||||||
|
for {
|
||||||
|
// Update maxParams of the current node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the longest common prefix.
|
||||||
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
|
// since the existing key can't contain those chars.
|
||||||
|
i := 0
|
||||||
|
max := min(len(path), len(n.path))
|
||||||
|
for i < max && path[i] == n.path[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split edge
|
||||||
|
if i < len(n.path) {
|
||||||
|
child := node{
|
||||||
|
path: n.path[i:],
|
||||||
|
wildChild: n.wildChild,
|
||||||
|
nType: static,
|
||||||
|
indices: n.indices,
|
||||||
|
children: n.children,
|
||||||
|
handle: n.handle,
|
||||||
|
priority: n.priority - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update maxParams (max of all children)
|
||||||
|
for i := range child.children {
|
||||||
|
if child.children[i].maxParams > child.maxParams {
|
||||||
|
child.maxParams = child.children[i].maxParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.children = []*node{&child}
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices = string([]byte{n.path[i]})
|
||||||
|
n.path = path[:i]
|
||||||
|
n.handle = nil
|
||||||
|
n.wildChild = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make new node a child of this node
|
||||||
|
if i < len(path) {
|
||||||
|
path = path[i:]
|
||||||
|
|
||||||
|
if n.wildChild {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// Update maxParams of the child node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// Check if the wildcard matches
|
||||||
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||||
|
// Adding a child to a catchAll is not possible
|
||||||
|
n.nType != catchAll &&
|
||||||
|
// Check for longer wildcard, e.g. :name and :names
|
||||||
|
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||||
|
continue walk
|
||||||
|
} else {
|
||||||
|
// Wildcard conflict
|
||||||
|
var pathSeg string
|
||||||
|
if n.nType == catchAll {
|
||||||
|
pathSeg = path
|
||||||
|
} else {
|
||||||
|
pathSeg = strings.SplitN(path, "/", 2)[0]
|
||||||
|
}
|
||||||
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
|
panic("'" + pathSeg +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in existing prefix '" + prefix +
|
||||||
|
"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := path[0]
|
||||||
|
|
||||||
|
// slash after param
|
||||||
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a child with the next path byte exists
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
i = n.incrementChildPrio(i)
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert it
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
|
if n.handle != nil {
|
||||||
|
panic("a handle is already registered for path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
n.handle = handle
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else { // Empty tree
|
||||||
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
|
n.nType = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
|
||||||
|
var offset int // already handled bytes of the path
|
||||||
|
|
||||||
|
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||||
|
for i, max := 0, len(path); numParams > 0; i++ {
|
||||||
|
c := path[i]
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// find wildcard end (either '/' or path end)
|
||||||
|
end := i + 1
|
||||||
|
for end < max && path[end] != '/' {
|
||||||
|
switch path[end] {
|
||||||
|
// the wildcard name must not contain ':' and '*'
|
||||||
|
case ':', '*':
|
||||||
|
panic("only one wildcard per path segment is allowed, has: '" +
|
||||||
|
path[i:] + "' in path '" + fullPath + "'")
|
||||||
|
default:
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this Node existing children which would be
|
||||||
|
// unreachable if we insert the wildcard here
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
panic("wildcard route '" + path[i:end] +
|
||||||
|
"' conflicts with existing children in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the wildcard has a name
|
||||||
|
if end-i < 2 {
|
||||||
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == ':' { // param
|
||||||
|
// split path at the beginning of the wildcard
|
||||||
|
if i > 0 {
|
||||||
|
n.path = path[offset:i]
|
||||||
|
offset = i
|
||||||
|
}
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
nType: param,
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.wildChild = true
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// if the path doesn't end with the wildcard, then there
|
||||||
|
// will be another non-wildcard subpath starting with '/'
|
||||||
|
if end < max {
|
||||||
|
n.path = path[offset:end]
|
||||||
|
offset = end
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // catchAll
|
||||||
|
if end != max || numParams > 1 {
|
||||||
|
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
|
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently fixed width 1 for '/'
|
||||||
|
i--
|
||||||
|
if path[i] != '/' {
|
||||||
|
panic("no / before catch-all in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
n.path = path[offset:i]
|
||||||
|
|
||||||
|
// first node: catchAll node with empty path
|
||||||
|
child := &node{
|
||||||
|
wildChild: true,
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
}
|
||||||
|
// update maxParams of the parent node
|
||||||
|
if n.maxParams < 1 {
|
||||||
|
n.maxParams = 1
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.indices = string(path[i])
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// second node: node holding the variable
|
||||||
|
child = &node{
|
||||||
|
path: path[i:],
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
handle: handle,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert remaining path part and handle to the leaf
|
||||||
|
n.path = path[offset:]
|
||||||
|
n.handle = handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the handle registered with the given path (key). The values of
|
||||||
|
// wildcards are saved to a map.
|
||||||
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||||
|
// made if a handle exists with an extra (without the) trailing slash for the
|
||||||
|
// given path.
|
||||||
|
func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
|
||||||
|
walk: // outer loop for walking the tree
|
||||||
|
for {
|
||||||
|
if len(path) > len(n.path) {
|
||||||
|
if path[:len(n.path)] == n.path {
|
||||||
|
path = path[len(n.path):]
|
||||||
|
// If this node does not have a wildcard (param or catchAll)
|
||||||
|
// child, we can just look up the next child node and continue
|
||||||
|
// to walk down the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
c := path[0]
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// We can recommend to redirect to the same URL without a
|
||||||
|
// trailing slash if a leaf exists for that path.
|
||||||
|
tsr = (path == "/" && n.handle != nil)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle wildcard child
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
end := 0
|
||||||
|
for end < len(path) && path[end] != '/' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// save param value
|
||||||
|
if p == nil {
|
||||||
|
// lazy allocation
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[1:]
|
||||||
|
p[i].Value = path[:end]
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if end < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
path = path[end:]
|
||||||
|
n = n.children[0]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
tsr = (len(path) == end+1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle = n.handle; handle != nil {
|
||||||
|
return
|
||||||
|
} else if len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for TSR recommendation
|
||||||
|
n = n.children[0]
|
||||||
|
tsr = (n.path == "/" && n.handle != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
// save param value
|
||||||
|
if p == nil {
|
||||||
|
// lazy allocation
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[2:]
|
||||||
|
p[i].Value = path
|
||||||
|
|
||||||
|
handle = n.handle
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if path == n.path {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if handle = n.handle; handle != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
|
tsr = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for trailing slash recommendation
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handle != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
|
// extra trailing slash if a leaf exists for that path
|
||||||
|
tsr = (path == "/") ||
|
||||||
|
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||||
|
path == n.path[:len(n.path)-1] && n.handle != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||||
|
// It can optionally also fix trailing slashes.
|
||||||
|
// It returns the case-corrected path and a bool indicating whether the lookup
|
||||||
|
// was successful.
|
||||||
|
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
||||||
|
return n.findCaseInsensitivePathRec(
|
||||||
|
path,
|
||||||
|
make([]byte, 0, len(path)+1), // preallocate enough memory for new path
|
||||||
|
[4]byte{}, // empty rune buffer
|
||||||
|
fixTrailingSlash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift bytes in array by n bytes left
|
||||||
|
func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return rb
|
||||||
|
case 1:
|
||||||
|
return [4]byte{rb[1], rb[2], rb[3], 0}
|
||||||
|
case 2:
|
||||||
|
return [4]byte{rb[2], rb[3]}
|
||||||
|
case 3:
|
||||||
|
return [4]byte{rb[3]}
|
||||||
|
default:
|
||||||
|
return [4]byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive case-insensitive lookup function used by n.findCaseInsensitivePath
|
||||||
|
func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) {
|
||||||
|
npLen := len(n.path)
|
||||||
|
|
||||||
|
walk: // outer loop for walking the tree
|
||||||
|
for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
|
||||||
|
// add common prefix to result
|
||||||
|
|
||||||
|
oldPath := path
|
||||||
|
path = path[npLen:]
|
||||||
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
// If this node does not have a wildcard (param or catchAll) child,
|
||||||
|
// we can just look up the next child node and continue to walk down
|
||||||
|
// the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
// skip rune bytes already processed
|
||||||
|
rb = shiftNRuneBytes(rb, npLen)
|
||||||
|
|
||||||
|
if rb[0] != 0 {
|
||||||
|
// old rune not finished
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == rb[0] {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// process a new rune
|
||||||
|
var rv rune
|
||||||
|
|
||||||
|
// find rune start
|
||||||
|
// runes are up to 4 byte long,
|
||||||
|
// -4 would definitely be another rune
|
||||||
|
var off int
|
||||||
|
for max := min(npLen, 3); off < max; off++ {
|
||||||
|
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
||||||
|
// read rune from cached path
|
||||||
|
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate lowercase bytes of current rune
|
||||||
|
lo := unicode.ToLower(rv)
|
||||||
|
utf8.EncodeRune(rb[:], lo)
|
||||||
|
|
||||||
|
// skip already processed bytes
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
// lowercase matches
|
||||||
|
if n.indices[i] == rb[0] {
|
||||||
|
// must use a recursive approach since both the
|
||||||
|
// uppercase byte and the lowercase byte might exist
|
||||||
|
// as an index
|
||||||
|
if out, found := n.children[i].findCaseInsensitivePathRec(
|
||||||
|
path, ciPath, rb, fixTrailingSlash,
|
||||||
|
); found {
|
||||||
|
return out, true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found no match, the same for the uppercase rune,
|
||||||
|
// if it differs
|
||||||
|
if up := unicode.ToUpper(rv); up != lo {
|
||||||
|
utf8.EncodeRune(rb[:], up)
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
for i, c := 0, rb[0]; i < len(n.indices); i++ {
|
||||||
|
// uppercase matches
|
||||||
|
if n.indices[i] == c {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL
|
||||||
|
// without a trailing slash if a leaf exists for that path
|
||||||
|
return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
k := 0
|
||||||
|
for k < len(path) && path[k] != '/' {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
// add param value to case insensitive path
|
||||||
|
ciPath = append(ciPath, path[:k]...)
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if k < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[0]
|
||||||
|
npLen = len(n.path)
|
||||||
|
path = path[k:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == k+1 {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.handle != nil {
|
||||||
|
return ciPath, true
|
||||||
|
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists
|
||||||
|
n = n.children[0]
|
||||||
|
if n.path == "/" && n.handle != nil {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...), true
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if n.handle != nil {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found.
|
||||||
|
// Try to fix the path by adding a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
if (len(n.path) == 1 && n.handle != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handle != nil) {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// Try to fix the path by adding / removing a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
if path == "/" {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
if len(path)+1 == npLen && n.path[len(path)] == '/' &&
|
||||||
|
strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handle != nil {
|
||||||
|
return append(ciPath, n.path...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.db
|
||||||
|
*.test
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.idea
|
||||||
|
.vscode
|
73
vendor/github.com/lib/pq/.travis.sh
generated
vendored
Normal file
73
vendor/github.com/lib/pq/.travis.sh
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
client_configure() {
|
||||||
|
sudo chmod 600 $PQSSLCERTTEST_PATH/postgresql.key
|
||||||
|
}
|
||||||
|
|
||||||
|
pgdg_repository() {
|
||||||
|
local sourcelist='sources.list.d/postgresql.list'
|
||||||
|
|
||||||
|
curl -sS 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' | sudo apt-key add -
|
||||||
|
echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PGVERSION | sudo tee "/etc/apt/$sourcelist"
|
||||||
|
sudo apt-get -o Dir::Etc::sourcelist="$sourcelist" -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0' update
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_configure() {
|
||||||
|
sudo tee /etc/postgresql/$PGVERSION/main/pg_hba.conf > /dev/null <<-config
|
||||||
|
local all all trust
|
||||||
|
hostnossl all pqgossltest 127.0.0.1/32 reject
|
||||||
|
hostnossl all pqgosslcert 127.0.0.1/32 reject
|
||||||
|
hostssl all pqgossltest 127.0.0.1/32 trust
|
||||||
|
hostssl all pqgosslcert 127.0.0.1/32 cert
|
||||||
|
host all all 127.0.0.1/32 trust
|
||||||
|
hostnossl all pqgossltest ::1/128 reject
|
||||||
|
hostnossl all pqgosslcert ::1/128 reject
|
||||||
|
hostssl all pqgossltest ::1/128 trust
|
||||||
|
hostssl all pqgosslcert ::1/128 cert
|
||||||
|
host all all ::1/128 trust
|
||||||
|
config
|
||||||
|
|
||||||
|
xargs sudo install -o postgres -g postgres -m 600 -t /var/lib/postgresql/$PGVERSION/main/ <<-certificates
|
||||||
|
certs/root.crt
|
||||||
|
certs/server.crt
|
||||||
|
certs/server.key
|
||||||
|
certificates
|
||||||
|
|
||||||
|
sort -VCu <<-versions ||
|
||||||
|
$PGVERSION
|
||||||
|
9.2
|
||||||
|
versions
|
||||||
|
sudo tee -a /etc/postgresql/$PGVERSION/main/postgresql.conf > /dev/null <<-config
|
||||||
|
ssl_ca_file = 'root.crt'
|
||||||
|
ssl_cert_file = 'server.crt'
|
||||||
|
ssl_key_file = 'server.key'
|
||||||
|
config
|
||||||
|
|
||||||
|
echo 127.0.0.1 postgres | sudo tee -a /etc/hosts > /dev/null
|
||||||
|
|
||||||
|
sudo service postgresql restart
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_install() {
|
||||||
|
xargs sudo apt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confnew' install <<-packages
|
||||||
|
postgresql-$PGVERSION
|
||||||
|
postgresql-server-dev-$PGVERSION
|
||||||
|
postgresql-contrib-$PGVERSION
|
||||||
|
packages
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_uninstall() {
|
||||||
|
sudo service postgresql stop
|
||||||
|
xargs sudo apt-get -y --purge remove <<-packages
|
||||||
|
libpq-dev
|
||||||
|
libpq5
|
||||||
|
postgresql
|
||||||
|
postgresql-client-common
|
||||||
|
postgresql-common
|
||||||
|
packages
|
||||||
|
sudo rm -rf /var/lib/postgresql
|
||||||
|
}
|
||||||
|
|
||||||
|
$1
|
45
vendor/github.com/lib/pq/.travis.yml
generated
vendored
Normal file
45
vendor/github.com/lib/pq/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.14.x
|
||||||
|
- 1.15.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
sudo: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- PGUSER=postgres
|
||||||
|
- PQGOSSLTESTS=1
|
||||||
|
- PQSSLCERTTEST_PATH=$PWD/certs
|
||||||
|
- PGHOST=127.0.0.1
|
||||||
|
- GODEBUG=x509ignoreCN=0
|
||||||
|
matrix:
|
||||||
|
- PGVERSION=10
|
||||||
|
- PGVERSION=9.6
|
||||||
|
- PGVERSION=9.5
|
||||||
|
- PGVERSION=9.4
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- ./.travis.sh postgresql_uninstall
|
||||||
|
- ./.travis.sh pgdg_repository
|
||||||
|
- ./.travis.sh postgresql_install
|
||||||
|
- ./.travis.sh postgresql_configure
|
||||||
|
- ./.travis.sh client_configure
|
||||||
|
- go get golang.org/x/tools/cmd/goimports
|
||||||
|
- go get golang.org/x/lint/golint
|
||||||
|
- GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@2020.1.3
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- createdb pqgotest
|
||||||
|
- createuser -DRS pqgossltest
|
||||||
|
- createuser -DRS pqgosslcert
|
||||||
|
|
||||||
|
script:
|
||||||
|
- >
|
||||||
|
goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
|
||||||
|
- go vet ./...
|
||||||
|
- staticcheck -go 1.13 ./...
|
||||||
|
- golint ./...
|
||||||
|
- PQTEST_BINARY_PARAMETERS=no go test -race -v ./...
|
||||||
|
- PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...
|
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Copyright (c) 2011-2013, 'pq' Contributors
|
||||||
|
Portions Copyright (C) 2011 Blake Mizerany
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
30
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
30
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# pq - A pure Go postgres driver for Go's database/sql package
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/lib/pq?status.svg)](https://pkg.go.dev/github.com/lib/pq?tab=doc)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/lib/pq
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* SSL
|
||||||
|
* Handles bad connections for `database/sql`
|
||||||
|
* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`)
|
||||||
|
* Scan binary blobs correctly (i.e. `bytea`)
|
||||||
|
* Package for `hstore` support
|
||||||
|
* COPY FROM support
|
||||||
|
* pq.ParseURL for converting urls to connection strings for sql.Open.
|
||||||
|
* Many libpq compatible environment variables
|
||||||
|
* Unix socket support
|
||||||
|
* Notifications: `LISTEN`/`NOTIFY`
|
||||||
|
* pgpass support
|
||||||
|
* GSS (Kerberos) auth
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. See [TESTS.md](TESTS.md) for more details.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
This package is effectively in maintenance mode and is not actively developed. Small patches and features are only rarely reviewed and merged. We recommend using [pgx](https://github.com/jackc/pgx) which is actively maintained.
|
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. A running PostgreSQL
|
||||||
|
server is required, with the ability to log in. The
|
||||||
|
database to connect to test with is "pqgotest," on
|
||||||
|
"localhost" but these can be overridden using [environment
|
||||||
|
variables](https://www.postgresql.org/docs/9.3/static/libpq-envars.html).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
PGHOST=/run/postgresql go test
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
A benchmark suite can be run as part of the tests:
|
||||||
|
|
||||||
|
go test -bench .
|
||||||
|
|
||||||
|
## Example setup (Docker)
|
||||||
|
|
||||||
|
Run a postgres container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --expose 5432:5432 postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
PGHOST=localhost PGPORT=5432 PGUSER=postgres PGSSLMODE=disable PGDATABASE=postgres go test
|
||||||
|
```
|
895
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
895
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
|
@ -0,0 +1,895 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||||
|
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||||
|
|
||||||
|
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||||
|
// slice of any dimension.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||||
|
//
|
||||||
|
// var x []sql.NullInt64
|
||||||
|
// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x))
|
||||||
|
//
|
||||||
|
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||||
|
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||||
|
func Array(a interface{}) interface {
|
||||||
|
driver.Valuer
|
||||||
|
sql.Scanner
|
||||||
|
} {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case []bool:
|
||||||
|
return (*BoolArray)(&a)
|
||||||
|
case []float64:
|
||||||
|
return (*Float64Array)(&a)
|
||||||
|
case []float32:
|
||||||
|
return (*Float32Array)(&a)
|
||||||
|
case []int64:
|
||||||
|
return (*Int64Array)(&a)
|
||||||
|
case []int32:
|
||||||
|
return (*Int32Array)(&a)
|
||||||
|
case []string:
|
||||||
|
return (*StringArray)(&a)
|
||||||
|
case [][]byte:
|
||||||
|
return (*ByteaArray)(&a)
|
||||||
|
|
||||||
|
case *[]bool:
|
||||||
|
return (*BoolArray)(a)
|
||||||
|
case *[]float64:
|
||||||
|
return (*Float64Array)(a)
|
||||||
|
case *[]float32:
|
||||||
|
return (*Float32Array)(a)
|
||||||
|
case *[]int64:
|
||||||
|
return (*Int64Array)(a)
|
||||||
|
case *[]int32:
|
||||||
|
return (*Int32Array)(a)
|
||||||
|
case *[]string:
|
||||||
|
return (*StringArray)(a)
|
||||||
|
case *[][]byte:
|
||||||
|
return (*ByteaArray)(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericArray{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||||
|
// to override the array delimiter used by GenericArray.
|
||||||
|
type ArrayDelimiter interface {
|
||||||
|
// ArrayDelimiter returns the delimiter character(s) for this element's type.
|
||||||
|
ArrayDelimiter() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolArray represents a one-dimensional array of the PostgreSQL boolean type.
|
||||||
|
type BoolArray []bool
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *BoolArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BoolArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "BoolArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(BoolArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if len(v) != 1 {
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case 't':
|
||||||
|
b[i] = true
|
||||||
|
case 'f':
|
||||||
|
b[i] = false
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a BoolArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be exactly two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1+2*n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[2*i] = ','
|
||||||
|
if a[i] {
|
||||||
|
b[1+2*i] = 't'
|
||||||
|
} else {
|
||||||
|
b[1+2*i] = 'f'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[2*n] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type.
|
||||||
|
type ByteaArray [][]byte
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *ByteaArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ByteaArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "ByteaArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(ByteaArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = parseBytea(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||||
|
// is only supported on PostgreSQL 9.0 or newer.
|
||||||
|
func (a ByteaArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||||
|
size := 1 + 6*n
|
||||||
|
for _, x := range a {
|
||||||
|
size += hex.EncodedLen(len(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, size)
|
||||||
|
|
||||||
|
for i, s := 0, b; i < n; i++ {
|
||||||
|
o := copy(s, `,"\\x`)
|
||||||
|
o += hex.Encode(s[o:], a[i])
|
||||||
|
s[o] = '"'
|
||||||
|
s = s[o+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[size-1] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float64Array []float64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float32Array []float32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float32Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
var x float64
|
||||||
|
if x, err = strconv.ParseFloat(string(v), 32); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
b[i] = float32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||||
|
// an array or slice of any dimension.
|
||||||
|
type GenericArray struct{ A interface{} }
|
||||||
|
|
||||||
|
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||||
|
var assign func([]byte, reflect.Value) error
|
||||||
|
var del = ","
|
||||||
|
|
||||||
|
// TODO calculate the assign function for other types
|
||||||
|
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||||
|
{
|
||||||
|
if reflect.PtrTo(rt).Implements(typeSQLScanner) {
|
||||||
|
// dest is always addressable because it is an element of a slice.
|
||||||
|
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||||
|
ss := dest.Addr().Interface().(sql.Scanner)
|
||||||
|
if src == nil {
|
||||||
|
err = ss.Scan(nil)
|
||||||
|
} else {
|
||||||
|
err = ss.Scan(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goto FoundType
|
||||||
|
}
|
||||||
|
|
||||||
|
assign = func([]byte, reflect.Value) error {
|
||||||
|
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoundType:
|
||||||
|
|
||||||
|
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt, assign, del
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a GenericArray) Scan(src interface{}) error {
|
||||||
|
dpv := reflect.ValueOf(a.A)
|
||||||
|
switch {
|
||||||
|
case dpv.Kind() != reflect.Ptr:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
case dpv.IsNil():
|
||||||
|
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
dv := dpv.Elem()
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src, dv)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src), dv)
|
||||||
|
case nil:
|
||||||
|
if dv.Kind() == reflect.Slice {
|
||||||
|
dv.Set(reflect.Zero(dv.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||||
|
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||||
|
dims, elems, err := parseArray(src, []byte(del))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow multidimensional
|
||||||
|
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||||
|
if len(dims) == 0 {
|
||||||
|
dims = append(dims, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
if rt.Len() != dims[i] {
|
||||||
|
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO handle multidimensional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||||
|
for i, e := range elems {
|
||||||
|
if err := assign(e, values.Index(i)); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle multidimensional
|
||||||
|
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
dv.Set(values.Slice(0, dims[0]))
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < dims[0]; i++ {
|
||||||
|
dv.Index(i).Set(values.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a GenericArray) Value() (driver.Value, error) {
|
||||||
|
if a.A == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(a.A)
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 0, 1+2*n)
|
||||||
|
|
||||||
|
b, _, err := appendArray(b, rv, n)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int64Array []int64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, a[0], 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, a[i], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int32Array []int32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int32Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
var x int
|
||||||
|
if x, err = strconv.Atoi(string(v)); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
b[i] = int32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, int64(a[0]), 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, int64(a[i]), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
||||||
|
type StringArray []string
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *StringArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(StringArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i] = string(v); v == nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a StringArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+3*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArray appends rv to the buffer, returning the extended buffer and
|
||||||
|
// the delimiter used between elements.
|
||||||
|
//
|
||||||
|
// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice.
|
||||||
|
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||||
|
var del string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b = append(b, '{')
|
||||||
|
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, del...)
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(b, '}'), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||||
|
// and the delimiter to use before the next element.
|
||||||
|
//
|
||||||
|
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||||
|
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||||
|
// is double-quoted.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||||
|
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||||
|
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
return appendArray(b, rv, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = ","
|
||||||
|
var err error
|
||||||
|
var iv interface{} = rv.Interface()
|
||||||
|
|
||||||
|
if ad, ok := iv.(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := iv.(type) {
|
||||||
|
case nil:
|
||||||
|
return append(b, "NULL"...), del, nil
|
||||||
|
case []byte:
|
||||||
|
return appendArrayQuotedBytes(b, v), del, nil
|
||||||
|
case string:
|
||||||
|
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = appendValue(b, iv)
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||||
|
b = append(b, '"')
|
||||||
|
for {
|
||||||
|
i := bytes.IndexAny(v, `"\`)
|
||||||
|
if i < 0 {
|
||||||
|
b = append(b, v...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, v[:i]...)
|
||||||
|
}
|
||||||
|
b = append(b, '\\', v[i])
|
||||||
|
v = v[i+1:]
|
||||||
|
}
|
||||||
|
return append(b, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||||
|
return append(b, encode(nil, v, 0)...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArray extracts the dimensions and elements of an array represented in
|
||||||
|
// text format. Only representations emitted by the backend are supported.
|
||||||
|
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||||
|
// is case-sensitive.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||||
|
var depth, i int
|
||||||
|
|
||||||
|
if len(src) < 1 || src[0] != '{' {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Open:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
depth++
|
||||||
|
i++
|
||||||
|
case '}':
|
||||||
|
elems = make([][]byte, 0)
|
||||||
|
goto Close
|
||||||
|
default:
|
||||||
|
break Open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dims = make([]int, i)
|
||||||
|
|
||||||
|
Element:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
if depth == len(dims) {
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
depth++
|
||||||
|
dims[depth-1] = 0
|
||||||
|
i++
|
||||||
|
case '"':
|
||||||
|
var elem = []byte{}
|
||||||
|
var escape bool
|
||||||
|
for i++; i < len(src); i++ {
|
||||||
|
if escape {
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
switch src[i] {
|
||||||
|
default:
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
case '\\':
|
||||||
|
escape = true
|
||||||
|
case '"':
|
||||||
|
elems = append(elems, elem)
|
||||||
|
i++
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for start := i; i < len(src); i++ {
|
||||||
|
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||||
|
elem := src[start:i]
|
||||||
|
if len(elem) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
if bytes.Equal(elem, []byte("NULL")) {
|
||||||
|
elem = nil
|
||||||
|
}
|
||||||
|
elems = append(elems, elem)
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(src) {
|
||||||
|
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
i += len(del)
|
||||||
|
goto Element
|
||||||
|
} else if src[i] == '}' && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close:
|
||||||
|
for i < len(src) {
|
||||||
|
if src[i] == '}' && depth > 0 {
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth > 0 {
|
||||||
|
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, d := range dims {
|
||||||
|
if (len(elems) % d) != 0 {
|
||||||
|
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||||
|
dims, elems, err := parseArray(src, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||||
|
}
|
||||||
|
return elems, err
|
||||||
|
}
|
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readBuf []byte
|
||||||
|
|
||||||
|
func (b *readBuf) int32() (n int) {
|
||||||
|
n = int(int32(binary.BigEndian.Uint32(*b)))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) oid() (n oid.Oid) {
|
||||||
|
n = oid.Oid(binary.BigEndian.Uint32(*b))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B: this is actually an unsigned 16-bit integer, unlike int32
|
||||||
|
func (b *readBuf) int16() (n int) {
|
||||||
|
n = int(binary.BigEndian.Uint16(*b))
|
||||||
|
*b = (*b)[2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) string() string {
|
||||||
|
i := bytes.IndexByte(*b, 0)
|
||||||
|
if i < 0 {
|
||||||
|
errorf("invalid message format; expected string terminator")
|
||||||
|
}
|
||||||
|
s := (*b)[:i]
|
||||||
|
*b = (*b)[i+1:]
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) next(n int) (v []byte) {
|
||||||
|
v = (*b)[:n]
|
||||||
|
*b = (*b)[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) byte() byte {
|
||||||
|
return b.next(1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeBuf struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int32(n int) {
|
||||||
|
x := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(x, uint32(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int16(n int) {
|
||||||
|
x := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(x, uint16(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) string(s string) {
|
||||||
|
b.buf = append(append(b.buf, s...), '\000')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) byte(c byte) {
|
||||||
|
b.buf = append(b.buf, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) bytes(v []byte) {
|
||||||
|
b.buf = append(b.buf, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) wrap() []byte {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) next(c byte) {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
b.pos = len(b.buf) + 1
|
||||||
|
b.buf = append(b.buf, c, 0, 0, 0, 0)
|
||||||
|
}
|
2037
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
2037
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
174
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
174
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implement the "QueryerContext" interface
|
||||||
|
func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
finish := cn.watchCancel(ctx)
|
||||||
|
r, err := cn.query(query, list)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ExecerContext" interface
|
||||||
|
func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn.Exec(query, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnBeginTx" interface
|
||||||
|
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
var mode string
|
||||||
|
|
||||||
|
switch sql.IsolationLevel(opts.Isolation) {
|
||||||
|
case sql.LevelDefault:
|
||||||
|
// Don't touch mode: use the server's default
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ UNCOMMITTED"
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ COMMITTED"
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
mode = " ISOLATION LEVEL REPEATABLE READ"
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
mode = " ISOLATION LEVEL SERIALIZABLE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ReadOnly {
|
||||||
|
mode += " READ ONLY"
|
||||||
|
} else {
|
||||||
|
mode += " READ WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := cn.begin(mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn.txnFinish = cn.watchCancel(ctx)
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) Ping(ctx context.Context) error {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
rows, err := cn.simpleQuery(";")
|
||||||
|
if err != nil {
|
||||||
|
return driver.ErrBadConn // https://golang.org/pkg/database/sql/driver/#Pinger
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
select {
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// We raced with the finish func, let the next query handle this with the
|
||||||
|
// context.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the connection state to bad so it does not get reused.
|
||||||
|
cn.setBad()
|
||||||
|
|
||||||
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = cn.cancel(ctxCancel)
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
cn.setBad()
|
||||||
|
cn.Close()
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) cancel(ctx context.Context) error {
|
||||||
|
// Create a new values map (copy). This makes sure the connection created
|
||||||
|
// in this method cannot write to the same underlying data, which could
|
||||||
|
// cause a concurrent map write panic. This is necessary because cancel
|
||||||
|
// is called from a goroutine in watchCancel.
|
||||||
|
o := make(values)
|
||||||
|
for k, v := range cn.opts {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := dial(ctx, cn.dialer, o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
{
|
||||||
|
bad := &atomic.Value{}
|
||||||
|
bad.Store(false)
|
||||||
|
can := conn{
|
||||||
|
c: c,
|
||||||
|
bad: bad,
|
||||||
|
}
|
||||||
|
err = can.ssl(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := can.writeBuf(0)
|
||||||
|
w.int32(80877102) // cancel request code
|
||||||
|
w.int32(cn.processID)
|
||||||
|
w.int32(cn.secretKey)
|
||||||
|
|
||||||
|
if err := can.sendStartupPacket(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read until EOF to ensure that the server received the cancel.
|
||||||
|
{
|
||||||
|
_, err := io.Copy(ioutil.Discard, c)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
115
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
115
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector represents a fixed configuration for the pq driver with a given
|
||||||
|
// name. Connector satisfies the database/sql/driver Connector interface and
|
||||||
|
// can be used to create any number of DB Conn's via the database/sql OpenDB
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
type Connector struct {
|
||||||
|
opts values
|
||||||
|
dialer Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect returns a connection to the database using the fixed configuration
|
||||||
|
// of this Connector. Context is not used.
|
||||||
|
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
return c.open(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver returns the underlying driver of this Connector.
|
||||||
|
func (c *Connector) Driver() driver.Driver {
|
||||||
|
return &Driver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnector returns a connector for the pq driver in a fixed configuration
|
||||||
|
// with the given dsn. The returned connector can be used to create any number
|
||||||
|
// of equivalent Conn's. The returned connector is intended to be used with
|
||||||
|
// database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
func NewConnector(dsn string) (*Connector, error) {
|
||||||
|
var err error
|
||||||
|
o := make(values)
|
||||||
|
|
||||||
|
// A number of defaults are applied here, in this order:
|
||||||
|
//
|
||||||
|
// * Very low precedence defaults applied in every situation
|
||||||
|
// * Environment variables
|
||||||
|
// * Explicitly passed connection information
|
||||||
|
o["host"] = "localhost"
|
||||||
|
o["port"] = "5432"
|
||||||
|
// N.B.: Extra float digits should be set to 3, but that breaks
|
||||||
|
// Postgres 8.4 and older, where the max is 2.
|
||||||
|
o["extra_float_digits"] = "2"
|
||||||
|
for k, v := range parseEnviron(os.Environ()) {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||||
|
dsn, err = ParseURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseOpts(dsn, o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the "fallback" application name if necessary
|
||||||
|
if fallback, ok := o["fallback_application_name"]; ok {
|
||||||
|
if _, ok := o["application_name"]; !ok {
|
||||||
|
o["application_name"] = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't work with any client_encoding other than UTF-8 currently.
|
||||||
|
// However, we have historically allowed the user to set it to UTF-8
|
||||||
|
// explicitly, and there's no reason to break such programs, so allow that.
|
||||||
|
// Note that the "options" setting could also set client_encoding, but
|
||||||
|
// parsing its value is not worth it. Instead, we always explicitly send
|
||||||
|
// client_encoding as a separate run-time parameter, which should override
|
||||||
|
// anything set in options.
|
||||||
|
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
||||||
|
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
||||||
|
}
|
||||||
|
o["client_encoding"] = "UTF8"
|
||||||
|
// DateStyle needs a similar treatment.
|
||||||
|
if datestyle, ok := o["datestyle"]; ok {
|
||||||
|
if datestyle != "ISO, MDY" {
|
||||||
|
return nil, fmt.Errorf("setting datestyle must be absent or %v; got %v", "ISO, MDY", datestyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o["datestyle"] = "ISO, MDY"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user is not provided by any other means, the last
|
||||||
|
// resort is to use the current operating system provided user
|
||||||
|
// name.
|
||||||
|
if _, ok := o["user"]; !ok {
|
||||||
|
u, err := userCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o["user"] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSL is not necessary or supported over UNIX domain sockets
|
||||||
|
if network, _ := network(o); network == "unix" {
|
||||||
|
o["sslmode"] = "disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Connector{opts: o, dialer: defaultDialer{}}, nil
|
||||||
|
}
|
307
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
307
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCopyInClosed = errors.New("pq: copyin statement has already been closed")
|
||||||
|
errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
|
||||||
|
errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
|
||||||
|
errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
|
||||||
|
errCopyInProgress = errors.New("pq: COPY in progress")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyIn creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare(). The target table should be visible in search_path.
|
||||||
|
func CopyIn(table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInSchema creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare().
|
||||||
|
func CopyInSchema(schema, table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
type copyin struct {
|
||||||
|
cn *conn
|
||||||
|
buffer []byte
|
||||||
|
rowData chan []byte
|
||||||
|
done chan bool
|
||||||
|
driver.Result
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
sync.Mutex // guards err
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciBufferSize = 64 * 1024
|
||||||
|
|
||||||
|
// flush buffer before the buffer is filled up and needs reallocation
|
||||||
|
const ciBufferFlushSize = 63 * 1024
|
||||||
|
|
||||||
|
func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
|
||||||
|
if !cn.isInTransaction() {
|
||||||
|
return nil, errCopyNotSupportedOutsideTxn
|
||||||
|
}
|
||||||
|
|
||||||
|
ci := ©in{
|
||||||
|
cn: cn,
|
||||||
|
buffer: make([]byte, 0, ciBufferSize),
|
||||||
|
rowData: make(chan []byte),
|
||||||
|
done: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
// add CopyData identifier + 4 bytes for message length
|
||||||
|
ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
|
||||||
|
|
||||||
|
b := cn.writeBuf('Q')
|
||||||
|
b.string(q)
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
awaitCopyInResponse:
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'G':
|
||||||
|
if r.byte() != 0 {
|
||||||
|
err = errBinaryCopyNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
}
|
||||||
|
go ci.resploop()
|
||||||
|
return ci, nil
|
||||||
|
case 'H':
|
||||||
|
err = errCopyToNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
case 'E':
|
||||||
|
err = parseError(r)
|
||||||
|
case 'Z':
|
||||||
|
if err == nil {
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unexpected ReadyForQuery in response to COPY")
|
||||||
|
}
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unknown response for copy query: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// something went wrong, abort COPY before we return
|
||||||
|
b = cn.writeBuf('f')
|
||||||
|
b.string(err.Error())
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'c', 'C', 'E':
|
||||||
|
case 'Z':
|
||||||
|
// correctly aborted, we're done
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unknown response for CopyFail: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) flush(buf []byte) {
|
||||||
|
// set message length (without message identifier)
|
||||||
|
binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
|
||||||
|
|
||||||
|
_, err := ci.cn.c.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) resploop() {
|
||||||
|
for {
|
||||||
|
var r readBuf
|
||||||
|
t, err := ci.cn.recvMessage(&r)
|
||||||
|
if err != nil {
|
||||||
|
ci.setBad()
|
||||||
|
ci.setError(err)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case 'C':
|
||||||
|
// complete
|
||||||
|
res, _ := ci.cn.parseComplete(r.string())
|
||||||
|
ci.setResult(res)
|
||||||
|
case 'N':
|
||||||
|
if n := ci.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(&r))
|
||||||
|
}
|
||||||
|
case 'Z':
|
||||||
|
ci.cn.processReadyForQuery(&r)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
case 'E':
|
||||||
|
err := parseError(&r)
|
||||||
|
ci.setError(err)
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setBad() {
|
||||||
|
ci.Lock()
|
||||||
|
ci.cn.setBad()
|
||||||
|
ci.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) isBad() bool {
|
||||||
|
ci.Lock()
|
||||||
|
b := ci.cn.getBad()
|
||||||
|
ci.Unlock()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) isErrorSet() bool {
|
||||||
|
ci.Lock()
|
||||||
|
isSet := (ci.err != nil)
|
||||||
|
ci.Unlock()
|
||||||
|
return isSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// setError() sets ci.err if one has not been set already. Caller must not be
|
||||||
|
// holding ci.Mutex.
|
||||||
|
func (ci *copyin) setError(err error) {
|
||||||
|
ci.Lock()
|
||||||
|
if ci.err == nil {
|
||||||
|
ci.err = err
|
||||||
|
}
|
||||||
|
ci.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setResult(result driver.Result) {
|
||||||
|
ci.Lock()
|
||||||
|
ci.Result = result
|
||||||
|
ci.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) getResult() driver.Result {
|
||||||
|
ci.Lock()
|
||||||
|
result := ci.Result
|
||||||
|
ci.Unlock()
|
||||||
|
if result == nil {
|
||||||
|
return driver.RowsAffected(0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) NumInput() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec inserts values into the COPY stream. The insert is asynchronous
|
||||||
|
// and Exec can return errors from previous Exec calls to the same
|
||||||
|
// COPY stmt.
|
||||||
|
//
|
||||||
|
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||||
|
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||||
|
// to the user.
|
||||||
|
func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
|
||||||
|
if ci.closed {
|
||||||
|
return nil, errCopyInClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.isBad() {
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if ci.isErrorSet() {
|
||||||
|
return nil, ci.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) == 0 {
|
||||||
|
if err := ci.Close(); err != nil {
|
||||||
|
return driver.RowsAffected(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ci.getResult(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numValues := len(v)
|
||||||
|
for i, value := range v {
|
||||||
|
ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value)
|
||||||
|
if i < numValues-1 {
|
||||||
|
ci.buffer = append(ci.buffer, '\t')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.buffer = append(ci.buffer, '\n')
|
||||||
|
|
||||||
|
if len(ci.buffer) > ciBufferFlushSize {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
// reset buffer, keep bytes for message identifier and length
|
||||||
|
ci.buffer = ci.buffer[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.RowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Close() (err error) {
|
||||||
|
if ci.closed { // Don't do anything, we're already closed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ci.closed = true
|
||||||
|
|
||||||
|
if ci.isBad() {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if len(ci.buffer) > 0 {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
}
|
||||||
|
// Avoid touching the scratch buffer as resploop could be using it.
|
||||||
|
err = ci.cn.sendSimpleMessage('c')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ci.done
|
||||||
|
ci.cn.inCopy = false
|
||||||
|
|
||||||
|
if ci.isErrorSet() {
|
||||||
|
err = ci.err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
268
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
268
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
/*
|
||||||
|
Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
In most cases clients will use the database/sql package instead of
|
||||||
|
using this package directly. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := 21
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
|
||||||
|
…
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also connect to a database using a URL. For example:
|
||||||
|
|
||||||
|
connStr := "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
|
||||||
|
|
||||||
|
Connection String Parameters
|
||||||
|
|
||||||
|
|
||||||
|
Similarly to libpq, when establishing a connection using pq you are expected to
|
||||||
|
supply a connection string containing zero or more parameters.
|
||||||
|
A subset of the connection parameters supported by libpq are also supported by pq.
|
||||||
|
Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem)
|
||||||
|
directly in the connection string. This is different from libpq, which does not allow
|
||||||
|
run-time parameters in the connection string, instead requiring you to supply
|
||||||
|
them in the options parameter.
|
||||||
|
|
||||||
|
For compatibility with libpq, the following special connection parameters are
|
||||||
|
supported:
|
||||||
|
|
||||||
|
* dbname - The name of the database to connect to
|
||||||
|
* user - The user to sign in as
|
||||||
|
* password - The user's password
|
||||||
|
* host - The host to connect to. Values that start with / are for unix
|
||||||
|
domain sockets. (default is localhost)
|
||||||
|
* port - The port to bind to. (default is 5432)
|
||||||
|
* sslmode - Whether or not to use SSL (default is require, this is not
|
||||||
|
the default for libpq)
|
||||||
|
* fallback_application_name - An application_name to fall back to if one isn't provided.
|
||||||
|
* connect_timeout - Maximum wait for connection, in seconds. Zero or
|
||||||
|
not specified means wait indefinitely.
|
||||||
|
* sslcert - Cert file location. The file must contain PEM encoded data.
|
||||||
|
* sslkey - Key file location. The file must contain PEM encoded data.
|
||||||
|
* sslrootcert - The location of the root certificate file. The file
|
||||||
|
must contain PEM encoded data.
|
||||||
|
|
||||||
|
Valid values for sslmode are:
|
||||||
|
|
||||||
|
* disable - No SSL
|
||||||
|
* require - Always SSL (skip verification)
|
||||||
|
* verify-ca - Always SSL (verify that the certificate presented by the
|
||||||
|
server was signed by a trusted CA)
|
||||||
|
* verify-full - Always SSL (verify that the certification presented by
|
||||||
|
the server was signed by a trusted CA and the server host name
|
||||||
|
matches the one in the certificate)
|
||||||
|
|
||||||
|
See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
|
for more information about connection string parameters.
|
||||||
|
|
||||||
|
Use single quotes for values that contain whitespace:
|
||||||
|
|
||||||
|
"user=pqgotest password='with spaces'"
|
||||||
|
|
||||||
|
A backslash will escape the next character in values:
|
||||||
|
|
||||||
|
"user=space\ man password='it\'s valid'"
|
||||||
|
|
||||||
|
Note that the connection parameter client_encoding (which sets the
|
||||||
|
text encoding for the connection) may be set but must be "UTF8",
|
||||||
|
matching with the same rules as Postgres. It is an error to provide
|
||||||
|
any other value.
|
||||||
|
|
||||||
|
In addition to the parameters listed above, any run-time parameter that can be
|
||||||
|
set at backend start time can be set in the connection string. For more
|
||||||
|
information, see
|
||||||
|
http://www.postgresql.org/docs/current/static/runtime-config.html.
|
||||||
|
|
||||||
|
Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||||
|
supported by libpq are also supported by pq. If any of the environment
|
||||||
|
variables not supported by pq are set, pq will panic during connection
|
||||||
|
establishment. Environment variables have a lower precedence than explicitly
|
||||||
|
provided connection parameters.
|
||||||
|
|
||||||
|
The pgpass mechanism as described in http://www.postgresql.org/docs/current/static/libpq-pgpass.html
|
||||||
|
is supported, but on Windows PGPASSFILE must be specified explicitly.
|
||||||
|
|
||||||
|
|
||||||
|
Queries
|
||||||
|
|
||||||
|
|
||||||
|
database/sql does not dictate any specific format for parameter
|
||||||
|
markers in query strings, and pq uses the Postgres-native ordinal markers,
|
||||||
|
as shown above. The same marker can be reused for the same parameter:
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1
|
||||||
|
OR age BETWEEN $2 AND $2 + 3`, "orange", 64)
|
||||||
|
|
||||||
|
pq does not support the LastInsertId() method of the Result type in database/sql.
|
||||||
|
To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres
|
||||||
|
RETURNING clause with a standard Query or QueryRow call:
|
||||||
|
|
||||||
|
var userid int
|
||||||
|
err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age)
|
||||||
|
VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid)
|
||||||
|
|
||||||
|
For more details on RETURNING, see the Postgres documentation:
|
||||||
|
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-insert.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-update.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-delete.html
|
||||||
|
|
||||||
|
For additional instructions on querying see the documentation for the database/sql package.
|
||||||
|
|
||||||
|
|
||||||
|
Data Types
|
||||||
|
|
||||||
|
|
||||||
|
Parameters pass through driver.DefaultParameterConverter before they are handled
|
||||||
|
by this package. When the binary_parameters connection option is enabled,
|
||||||
|
[]byte values are sent directly to the backend as data in binary format.
|
||||||
|
|
||||||
|
This package returns the following types for values from the PostgreSQL backend:
|
||||||
|
|
||||||
|
- integer types smallint, integer, and bigint are returned as int64
|
||||||
|
- floating-point types real and double precision are returned as float64
|
||||||
|
- character types char, varchar, and text are returned as string
|
||||||
|
- temporal types date, time, timetz, timestamp, and timestamptz are
|
||||||
|
returned as time.Time
|
||||||
|
- the boolean type is returned as bool
|
||||||
|
- the bytea type is returned as []byte
|
||||||
|
|
||||||
|
All other types are returned directly from the backend as []byte values in text format.
|
||||||
|
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
|
||||||
|
pq may return errors of type *pq.Error which can be interrogated for error details:
|
||||||
|
|
||||||
|
if err, ok := err.(*pq.Error); ok {
|
||||||
|
fmt.Println("pq error:", err.Code.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
See the pq.Error type for details.
|
||||||
|
|
||||||
|
|
||||||
|
Bulk imports
|
||||||
|
|
||||||
|
You can perform bulk imports by preparing a statement returned by pq.CopyIn (or
|
||||||
|
pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement
|
||||||
|
handle can then be repeatedly "executed" to copy data into the target table.
|
||||||
|
After all data has been processed you should call Exec() once with no arguments
|
||||||
|
to flush all buffered data. Any call to Exec() might return an error which
|
||||||
|
should be handled appropriately, but because of the internal buffering an error
|
||||||
|
returned by Exec() might not be related to the data passed in the call that
|
||||||
|
failed.
|
||||||
|
|
||||||
|
CopyIn uses COPY FROM internally. It is not possible to COPY outside of an
|
||||||
|
explicit transaction in pq.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
_, err = stmt.Exec(user.Name, int64(user.Age))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Notifications
|
||||||
|
|
||||||
|
|
||||||
|
PostgreSQL supports a simple publish/subscribe model over database
|
||||||
|
connections. See http://www.postgresql.org/docs/current/static/sql-notify.html
|
||||||
|
for more information about the general mechanism.
|
||||||
|
|
||||||
|
To start listening for notifications, you first have to open a new connection
|
||||||
|
to the database by calling NewListener. This connection can not be used for
|
||||||
|
anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
|
||||||
|
channel"; once a notification channel is open, a notification generated on that
|
||||||
|
channel will effect a send on the Listener.Notify channel. A notification
|
||||||
|
channel will remain open until Unlisten is called, though connection loss might
|
||||||
|
result in some notifications being lost. To solve this problem, Listener sends
|
||||||
|
a nil pointer over the Notify channel any time the connection is re-established
|
||||||
|
following a connection loss. The application can get information about the
|
||||||
|
state of the underlying connection by setting an event callback in the call to
|
||||||
|
NewListener.
|
||||||
|
|
||||||
|
A single Listener can safely be used from concurrent goroutines, which means
|
||||||
|
that there is often no need to create more than one Listener in your
|
||||||
|
application. However, a Listener is always connected to a single database, so
|
||||||
|
you will need to create a new Listener instance for every database you want to
|
||||||
|
receive notifications in.
|
||||||
|
|
||||||
|
The channel name in both Listen and Unlisten is case sensitive, and can contain
|
||||||
|
any characters legal in an identifier (see
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||||
|
for more information). Note that the channel name will be truncated to 63
|
||||||
|
bytes by the PostgreSQL server.
|
||||||
|
|
||||||
|
You can find a complete, working example of Listener usage at
|
||||||
|
https://godoc.org/github.com/lib/pq/example/listen.
|
||||||
|
|
||||||
|
|
||||||
|
Kerberos Support
|
||||||
|
|
||||||
|
|
||||||
|
If you need support for Kerberos authentication, add the following to your main
|
||||||
|
package:
|
||||||
|
|
||||||
|
import "github.com/lib/pq/auth/kerberos"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pq.RegisterGSSProvider(func() (pq.Gss, error) { return kerberos.NewGSS() })
|
||||||
|
}
|
||||||
|
|
||||||
|
This package is in a separate module so that users who don't need Kerberos
|
||||||
|
don't have to download unnecessary dependencies.
|
||||||
|
|
||||||
|
When imported, additional connection string parameters are supported:
|
||||||
|
|
||||||
|
* krbsrvname - GSS (Kerberos) service name when constructing the
|
||||||
|
SPN (default is `postgres`). This will be combined with the host
|
||||||
|
to form the full SPN: `krbsrvname/host`.
|
||||||
|
* krbspn - GSS (Kerberos) SPN. This takes priority over
|
||||||
|
`krbsrvname` if present.
|
||||||
|
*/
|
||||||
|
package pq
|
622
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
622
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
|
@ -0,0 +1,622 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var time2400Regex = regexp.MustCompile(`^(24:00(?::00(?:\.0+)?)?)(?:[Z+-].*)?$`)
|
||||||
|
|
||||||
|
func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case []byte:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return encode(parameterStatus, x, oid.T_unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(nil, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(nil, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
case string:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, []byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(nil, v)
|
||||||
|
case time.Time:
|
||||||
|
return formatTs(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} {
|
||||||
|
switch f {
|
||||||
|
case formatBinary:
|
||||||
|
return binaryDecode(parameterStatus, s, typ)
|
||||||
|
case formatText:
|
||||||
|
return textDecode(parameterStatus, s, typ)
|
||||||
|
default:
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_bytea:
|
||||||
|
return s
|
||||||
|
case oid.T_int8:
|
||||||
|
return int64(binary.BigEndian.Uint64(s))
|
||||||
|
case oid.T_int4:
|
||||||
|
return int64(int32(binary.BigEndian.Uint32(s)))
|
||||||
|
case oid.T_int2:
|
||||||
|
return int64(int16(binary.BigEndian.Uint16(s)))
|
||||||
|
case oid.T_uuid:
|
||||||
|
b, err := decodeUUIDBinary(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("don't know how to decode binary parameter of type %d", uint32(typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_char, oid.T_varchar, oid.T_text:
|
||||||
|
return string(s)
|
||||||
|
case oid.T_bytea:
|
||||||
|
b, err := parseBytea(s)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
case oid.T_timestamptz:
|
||||||
|
return parseTs(parameterStatus.currentLocation, string(s))
|
||||||
|
case oid.T_timestamp, oid.T_date:
|
||||||
|
return parseTs(nil, string(s))
|
||||||
|
case oid.T_time:
|
||||||
|
return mustParse("15:04:05", typ, s)
|
||||||
|
case oid.T_timetz:
|
||||||
|
return mustParse("15:04:05-07", typ, s)
|
||||||
|
case oid.T_bool:
|
||||||
|
return s[0] == 't'
|
||||||
|
case oid.T_int8, oid.T_int4, oid.T_int2:
|
||||||
|
i, err := strconv.ParseInt(string(s), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
case oid.T_float4, oid.T_float8:
|
||||||
|
// We always use 64 bit parsing, regardless of whether the input text is for
|
||||||
|
// a float4 or float8, because clients expect float64s for all float datatypes
|
||||||
|
// and returning a 32-bit parsed float64 produces lossy results.
|
||||||
|
f, err := strconv.ParseFloat(string(s), 64)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEncodedText encodes item in text format as required by COPY
|
||||||
|
// and appends to buf
|
||||||
|
func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(buf, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(buf, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
encodedBytea := encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
return appendEscapedText(buf, string(encodedBytea))
|
||||||
|
case string:
|
||||||
|
return appendEscapedText(buf, v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(buf, v)
|
||||||
|
case time.Time:
|
||||||
|
return append(buf, formatTs(v)...)
|
||||||
|
case nil:
|
||||||
|
return append(buf, "\\N"...)
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendEscapedText(buf []byte, text string) []byte {
|
||||||
|
escapeNeeded := false
|
||||||
|
startPos := 0
|
||||||
|
var c byte
|
||||||
|
|
||||||
|
// check if we need to escape
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
|
||||||
|
escapeNeeded = true
|
||||||
|
startPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !escapeNeeded {
|
||||||
|
return append(buf, text...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy till first char to escape, iterate the rest
|
||||||
|
result := append(buf, text[:startPos]...)
|
||||||
|
for i := startPos; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
case '\n':
|
||||||
|
result = append(result, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
result = append(result, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
result = append(result, '\\', 't')
|
||||||
|
default:
|
||||||
|
result = append(result, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParse(f string, typ oid.Oid, s []byte) time.Time {
|
||||||
|
str := string(s)
|
||||||
|
|
||||||
|
// check for a 30-minute-offset timezone
|
||||||
|
if (typ == oid.T_timestamptz || typ == oid.T_timetz) &&
|
||||||
|
str[len(str)-3] == ':' {
|
||||||
|
f += ":00"
|
||||||
|
}
|
||||||
|
// Special case for 24:00 time.
|
||||||
|
// Unfortunately, golang does not parse 24:00 as a proper time.
|
||||||
|
// In this case, we want to try "round to the next day", to differentiate.
|
||||||
|
// As such, we find if the 24:00 time matches at the beginning; if so,
|
||||||
|
// we default it back to 00:00 but add a day later.
|
||||||
|
var is2400Time bool
|
||||||
|
switch typ {
|
||||||
|
case oid.T_timetz, oid.T_time:
|
||||||
|
if matches := time2400Regex.FindStringSubmatch(str); matches != nil {
|
||||||
|
// Concatenate timezone information at the back.
|
||||||
|
str = "00:00:00" + str[len(matches[1]):]
|
||||||
|
is2400Time = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, err := time.Parse(f, str)
|
||||||
|
if err != nil {
|
||||||
|
errorf("decode: %s", err)
|
||||||
|
}
|
||||||
|
if is2400Time {
|
||||||
|
t = t.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
|
|
||||||
|
type timestampParser struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) expect(str string, char byte, pos int) {
|
||||||
|
if p.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pos+1 > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := str[pos]; c != char && p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) mustAtoi(str string, begin int, end int) int {
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if begin < 0 || end < 0 || begin > end || end > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
result, err := strconv.Atoi(str[begin:end])
|
||||||
|
if err != nil {
|
||||||
|
if p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected number; got '%v'", str)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The location cache caches the time zones typically used by the client.
|
||||||
|
type locationCache struct {
|
||||||
|
cache map[int]*time.Location
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// All connections share the same list of timezones. Benchmarking shows that
|
||||||
|
// about 5% speed could be gained by putting the cache in the connection and
|
||||||
|
// losing the mutex, at the cost of a small amount of memory and a somewhat
|
||||||
|
// significant increase in code complexity.
|
||||||
|
var globalLocationCache = newLocationCache()
|
||||||
|
|
||||||
|
func newLocationCache() *locationCache {
|
||||||
|
return &locationCache{cache: make(map[int]*time.Location)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cached timezone for the specified offset, creating and caching
|
||||||
|
// it if necessary.
|
||||||
|
func (c *locationCache) getLocation(offset int) *time.Location {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
location, ok := c.cache[offset]
|
||||||
|
if !ok {
|
||||||
|
location = time.FixedZone("", offset)
|
||||||
|
c.cache[offset] = location
|
||||||
|
}
|
||||||
|
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
var infinityTsEnabled = false
|
||||||
|
var infinityTsNegative time.Time
|
||||||
|
var infinityTsPositive time.Time
|
||||||
|
|
||||||
|
const (
|
||||||
|
infinityTsEnabledAlready = "pq: infinity timestamp enabled already"
|
||||||
|
infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableInfinityTs controls the handling of Postgres' "-infinity" and
|
||||||
|
// "infinity" "timestamp"s.
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is not called, "-infinity" and "infinity" will return
|
||||||
|
// []byte("-infinity") and []byte("infinity") respectively, and potentially
|
||||||
|
// cause error "sql: Scan error on column index 0: unsupported driver -> Scan
|
||||||
|
// pair: []uint8 -> *time.Time", when scanning into a time.Time value.
|
||||||
|
//
|
||||||
|
// Once EnableInfinityTs has been called, all connections created using this
|
||||||
|
// driver will decode Postgres' "-infinity" and "infinity" for "timestamp",
|
||||||
|
// "timestamp with time zone" and "date" types to the predefined minimum and
|
||||||
|
// maximum times, respectively. When encoding time.Time values, any time which
|
||||||
|
// equals or precedes the predefined minimum time will be encoded to
|
||||||
|
// "-infinity". Any values at or past the maximum time will similarly be
|
||||||
|
// encoded to "infinity".
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is called with negative >= positive, it will panic.
|
||||||
|
// Calling EnableInfinityTs after a connection has been established results in
|
||||||
|
// undefined behavior. If EnableInfinityTs is called more than once, it will
|
||||||
|
// panic.
|
||||||
|
func EnableInfinityTs(negative time.Time, positive time.Time) {
|
||||||
|
if infinityTsEnabled {
|
||||||
|
panic(infinityTsEnabledAlready)
|
||||||
|
}
|
||||||
|
if !negative.Before(positive) {
|
||||||
|
panic(infinityTsNegativeMustBeSmaller)
|
||||||
|
}
|
||||||
|
infinityTsEnabled = true
|
||||||
|
infinityTsNegative = negative
|
||||||
|
infinityTsPositive = positive
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Testing might want to toggle infinityTsEnabled
|
||||||
|
*/
|
||||||
|
func disableInfinityTs() {
|
||||||
|
infinityTsEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a time function specific to the Postgres default DateStyle
|
||||||
|
// setting ("ISO, MDY"), the only one we currently support. This
|
||||||
|
// accounts for the discrepancies between the parsing available with
|
||||||
|
// time.Parse and the Postgres date formatting quirks.
|
||||||
|
func parseTs(currentLocation *time.Location, str string) interface{} {
|
||||||
|
switch str {
|
||||||
|
case "-infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsNegative
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
case "infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsPositive
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
}
|
||||||
|
t, err := ParseTimestamp(currentLocation, str)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimestamp parses Postgres' text format. It returns a time.Time in
|
||||||
|
// currentLocation iff that time's offset agrees with the offset sent from the
|
||||||
|
// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the
|
||||||
|
// fixed offset offset provided by the Postgres server.
|
||||||
|
func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) {
|
||||||
|
p := timestampParser{}
|
||||||
|
|
||||||
|
monSep := strings.IndexRune(str, '-')
|
||||||
|
// this is Gregorian year, not ISO Year
|
||||||
|
// In Gregorian system, the year 1 BC is followed by AD 1
|
||||||
|
year := p.mustAtoi(str, 0, monSep)
|
||||||
|
daySep := monSep + 3
|
||||||
|
month := p.mustAtoi(str, monSep+1, daySep)
|
||||||
|
p.expect(str, '-', daySep)
|
||||||
|
timeSep := daySep + 3
|
||||||
|
day := p.mustAtoi(str, daySep+1, timeSep)
|
||||||
|
|
||||||
|
minLen := monSep + len("01-01") + 1
|
||||||
|
|
||||||
|
isBC := strings.HasSuffix(str, " BC")
|
||||||
|
if isBC {
|
||||||
|
minLen += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
var hour, minute, second int
|
||||||
|
if len(str) > minLen {
|
||||||
|
p.expect(str, ' ', timeSep)
|
||||||
|
minSep := timeSep + 3
|
||||||
|
p.expect(str, ':', minSep)
|
||||||
|
hour = p.mustAtoi(str, timeSep+1, minSep)
|
||||||
|
secSep := minSep + 3
|
||||||
|
p.expect(str, ':', secSep)
|
||||||
|
minute = p.mustAtoi(str, minSep+1, secSep)
|
||||||
|
secEnd := secSep + 3
|
||||||
|
second = p.mustAtoi(str, secSep+1, secEnd)
|
||||||
|
}
|
||||||
|
remainderIdx := monSep + len("01-01 00:00:00") + 1
|
||||||
|
// Three optional (but ordered) sections follow: the
|
||||||
|
// fractional seconds, the time zone offset, and the BC
|
||||||
|
// designation. We set them up here and adjust the other
|
||||||
|
// offsets if the preceding sections exist.
|
||||||
|
|
||||||
|
nanoSec := 0
|
||||||
|
tzOff := 0
|
||||||
|
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == '.' {
|
||||||
|
fracStart := remainderIdx + 1
|
||||||
|
fracOff := strings.IndexAny(str[fracStart:], "-+ ")
|
||||||
|
if fracOff < 0 {
|
||||||
|
fracOff = len(str) - fracStart
|
||||||
|
}
|
||||||
|
fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff)
|
||||||
|
nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
|
||||||
|
|
||||||
|
remainderIdx += fracOff + 1
|
||||||
|
}
|
||||||
|
if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') {
|
||||||
|
// time zone separator is always '-' or '+' (UTC is +00)
|
||||||
|
var tzSign int
|
||||||
|
switch c := str[tzStart]; c {
|
||||||
|
case '-':
|
||||||
|
tzSign = -1
|
||||||
|
case '+':
|
||||||
|
tzSign = +1
|
||||||
|
default:
|
||||||
|
return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
|
||||||
|
}
|
||||||
|
tzHours := p.mustAtoi(str, tzStart+1, tzStart+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
var tzMin, tzSec int
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||||
|
}
|
||||||
|
var isoYear int
|
||||||
|
|
||||||
|
if isBC {
|
||||||
|
isoYear = 1 - year
|
||||||
|
remainderIdx += 3
|
||||||
|
} else {
|
||||||
|
isoYear = year
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) {
|
||||||
|
return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:])
|
||||||
|
}
|
||||||
|
t := time.Date(isoYear, time.Month(month), day,
|
||||||
|
hour, minute, second, nanoSec,
|
||||||
|
globalLocationCache.getLocation(tzOff))
|
||||||
|
|
||||||
|
if currentLocation != nil {
|
||||||
|
// Set the location of the returned Time based on the session's
|
||||||
|
// TimeZone value, but only if the local time zone database agrees with
|
||||||
|
// the remote database on the offset.
|
||||||
|
lt := t.In(currentLocation)
|
||||||
|
_, newOff := lt.Zone()
|
||||||
|
if newOff == tzOff {
|
||||||
|
t = lt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTs formats t into a format postgres understands.
|
||||||
|
func formatTs(t time.Time) []byte {
|
||||||
|
if infinityTsEnabled {
|
||||||
|
// t <= -infinity : ! (t > -infinity)
|
||||||
|
if !t.After(infinityTsNegative) {
|
||||||
|
return []byte("-infinity")
|
||||||
|
}
|
||||||
|
// t >= infinity : ! (!t < infinity)
|
||||||
|
if !t.Before(infinityTsPositive) {
|
||||||
|
return []byte("infinity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FormatTimestamp(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatTimestamp formats t into Postgres' text format for timestamps.
|
||||||
|
func FormatTimestamp(t time.Time) []byte {
|
||||||
|
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||||
|
// minus sign preferred by Go.
|
||||||
|
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||||
|
bc := false
|
||||||
|
if t.Year() <= 0 {
|
||||||
|
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||||
|
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||||
|
bc = true
|
||||||
|
}
|
||||||
|
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
|
||||||
|
|
||||||
|
_, offset := t.Zone()
|
||||||
|
offset %= 60
|
||||||
|
if offset != 0 {
|
||||||
|
// RFC3339Nano already printed the minus sign
|
||||||
|
if offset < 0 {
|
||||||
|
offset = -offset
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, ':')
|
||||||
|
if offset < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
b = strconv.AppendInt(b, int64(offset), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
b = append(b, " BC"...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||||
|
// "escape" format are supported.
|
||||||
|
func parseBytea(s []byte) (result []byte, err error) {
|
||||||
|
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||||
|
// bytea_output = hex
|
||||||
|
s = s[2:] // trim off leading "\\x"
|
||||||
|
result = make([]byte, hex.DecodedLen(len(s)))
|
||||||
|
_, err := hex.Decode(result, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bytea_output = escape
|
||||||
|
for len(s) > 0 {
|
||||||
|
if s[0] == '\\' {
|
||||||
|
// escaped '\\'
|
||||||
|
if len(s) >= 2 && s[1] == '\\' {
|
||||||
|
result = append(result, '\\')
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// '\\' followed by an octal number
|
||||||
|
if len(s) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
|
||||||
|
}
|
||||||
|
result = append(result, byte(r))
|
||||||
|
s = s[4:]
|
||||||
|
} else {
|
||||||
|
// We hit an unescaped, raw byte. Try to read in as many as
|
||||||
|
// possible in one go.
|
||||||
|
i := bytes.IndexByte(s, '\\')
|
||||||
|
if i == -1 {
|
||||||
|
result = append(result, s...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result = append(result, s[:i]...)
|
||||||
|
s = s[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBytea(serverVersion int, v []byte) (result []byte) {
|
||||||
|
if serverVersion >= 90000 {
|
||||||
|
// Use the hex format if we know that the server supports it
|
||||||
|
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||||
|
result[0] = '\\'
|
||||||
|
result[1] = 'x'
|
||||||
|
hex.Encode(result[2:], v)
|
||||||
|
} else {
|
||||||
|
// .. or resort to "escape"
|
||||||
|
for _, b := range v {
|
||||||
|
if b == '\\' {
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
} else if b < 0x20 || b > 0x7e {
|
||||||
|
result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
|
||||||
|
} else {
|
||||||
|
result = append(result, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be null. NullTime implements the
|
||||||
|
// sql.Scanner interface so it can be used as a scan destination, similar to
|
||||||
|
// sql.NullString.
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) error {
|
||||||
|
nt.Time, nt.Valid = value.(time.Time)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
518
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
518
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
|
@ -0,0 +1,518 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error severities
|
||||||
|
const (
|
||||||
|
Efatal = "FATAL"
|
||||||
|
Epanic = "PANIC"
|
||||||
|
Ewarning = "WARNING"
|
||||||
|
Enotice = "NOTICE"
|
||||||
|
Edebug = "DEBUG"
|
||||||
|
Einfo = "INFO"
|
||||||
|
Elog = "LOG"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an error communicating with the server.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields
|
||||||
|
type Error struct {
|
||||||
|
Severity string
|
||||||
|
Code ErrorCode
|
||||||
|
Message string
|
||||||
|
Detail string
|
||||||
|
Hint string
|
||||||
|
Position string
|
||||||
|
InternalPosition string
|
||||||
|
InternalQuery string
|
||||||
|
Where string
|
||||||
|
Schema string
|
||||||
|
Table string
|
||||||
|
Column string
|
||||||
|
DataTypeName string
|
||||||
|
Constraint string
|
||||||
|
File string
|
||||||
|
Line string
|
||||||
|
Routine string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode is a five-character error code.
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
// Name returns a more human friendly rendering of the error code, namely the
|
||||||
|
// "condition name".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Name() string {
|
||||||
|
return errorCodeNames[ec]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorClass is only the class part of an error code.
|
||||||
|
type ErrorClass string
|
||||||
|
|
||||||
|
// Name returns the condition name of an error class. It is equivalent to the
|
||||||
|
// condition name of the "standard" error code (i.e. the one having the last
|
||||||
|
// three characters "000").
|
||||||
|
func (ec ErrorClass) Name() string {
|
||||||
|
return errorCodeNames[ErrorCode(ec+"000")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class returns the error class, e.g. "28".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Class() ErrorClass {
|
||||||
|
return ErrorClass(ec[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorCodeNames is a mapping between the five-character error codes and the
|
||||||
|
// human readable "condition names". It is derived from the list at
|
||||||
|
// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
|
||||||
|
var errorCodeNames = map[ErrorCode]string{
|
||||||
|
// Class 00 - Successful Completion
|
||||||
|
"00000": "successful_completion",
|
||||||
|
// Class 01 - Warning
|
||||||
|
"01000": "warning",
|
||||||
|
"0100C": "dynamic_result_sets_returned",
|
||||||
|
"01008": "implicit_zero_bit_padding",
|
||||||
|
"01003": "null_value_eliminated_in_set_function",
|
||||||
|
"01007": "privilege_not_granted",
|
||||||
|
"01006": "privilege_not_revoked",
|
||||||
|
"01004": "string_data_right_truncation",
|
||||||
|
"01P01": "deprecated_feature",
|
||||||
|
// Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||||
|
"02000": "no_data",
|
||||||
|
"02001": "no_additional_dynamic_result_sets_returned",
|
||||||
|
// Class 03 - SQL Statement Not Yet Complete
|
||||||
|
"03000": "sql_statement_not_yet_complete",
|
||||||
|
// Class 08 - Connection Exception
|
||||||
|
"08000": "connection_exception",
|
||||||
|
"08003": "connection_does_not_exist",
|
||||||
|
"08006": "connection_failure",
|
||||||
|
"08001": "sqlclient_unable_to_establish_sqlconnection",
|
||||||
|
"08004": "sqlserver_rejected_establishment_of_sqlconnection",
|
||||||
|
"08007": "transaction_resolution_unknown",
|
||||||
|
"08P01": "protocol_violation",
|
||||||
|
// Class 09 - Triggered Action Exception
|
||||||
|
"09000": "triggered_action_exception",
|
||||||
|
// Class 0A - Feature Not Supported
|
||||||
|
"0A000": "feature_not_supported",
|
||||||
|
// Class 0B - Invalid Transaction Initiation
|
||||||
|
"0B000": "invalid_transaction_initiation",
|
||||||
|
// Class 0F - Locator Exception
|
||||||
|
"0F000": "locator_exception",
|
||||||
|
"0F001": "invalid_locator_specification",
|
||||||
|
// Class 0L - Invalid Grantor
|
||||||
|
"0L000": "invalid_grantor",
|
||||||
|
"0LP01": "invalid_grant_operation",
|
||||||
|
// Class 0P - Invalid Role Specification
|
||||||
|
"0P000": "invalid_role_specification",
|
||||||
|
// Class 0Z - Diagnostics Exception
|
||||||
|
"0Z000": "diagnostics_exception",
|
||||||
|
"0Z002": "stacked_diagnostics_accessed_without_active_handler",
|
||||||
|
// Class 20 - Case Not Found
|
||||||
|
"20000": "case_not_found",
|
||||||
|
// Class 21 - Cardinality Violation
|
||||||
|
"21000": "cardinality_violation",
|
||||||
|
// Class 22 - Data Exception
|
||||||
|
"22000": "data_exception",
|
||||||
|
"2202E": "array_subscript_error",
|
||||||
|
"22021": "character_not_in_repertoire",
|
||||||
|
"22008": "datetime_field_overflow",
|
||||||
|
"22012": "division_by_zero",
|
||||||
|
"22005": "error_in_assignment",
|
||||||
|
"2200B": "escape_character_conflict",
|
||||||
|
"22022": "indicator_overflow",
|
||||||
|
"22015": "interval_field_overflow",
|
||||||
|
"2201E": "invalid_argument_for_logarithm",
|
||||||
|
"22014": "invalid_argument_for_ntile_function",
|
||||||
|
"22016": "invalid_argument_for_nth_value_function",
|
||||||
|
"2201F": "invalid_argument_for_power_function",
|
||||||
|
"2201G": "invalid_argument_for_width_bucket_function",
|
||||||
|
"22018": "invalid_character_value_for_cast",
|
||||||
|
"22007": "invalid_datetime_format",
|
||||||
|
"22019": "invalid_escape_character",
|
||||||
|
"2200D": "invalid_escape_octet",
|
||||||
|
"22025": "invalid_escape_sequence",
|
||||||
|
"22P06": "nonstandard_use_of_escape_character",
|
||||||
|
"22010": "invalid_indicator_parameter_value",
|
||||||
|
"22023": "invalid_parameter_value",
|
||||||
|
"2201B": "invalid_regular_expression",
|
||||||
|
"2201W": "invalid_row_count_in_limit_clause",
|
||||||
|
"2201X": "invalid_row_count_in_result_offset_clause",
|
||||||
|
"22009": "invalid_time_zone_displacement_value",
|
||||||
|
"2200C": "invalid_use_of_escape_character",
|
||||||
|
"2200G": "most_specific_type_mismatch",
|
||||||
|
"22004": "null_value_not_allowed",
|
||||||
|
"22002": "null_value_no_indicator_parameter",
|
||||||
|
"22003": "numeric_value_out_of_range",
|
||||||
|
"2200H": "sequence_generator_limit_exceeded",
|
||||||
|
"22026": "string_data_length_mismatch",
|
||||||
|
"22001": "string_data_right_truncation",
|
||||||
|
"22011": "substring_error",
|
||||||
|
"22027": "trim_error",
|
||||||
|
"22024": "unterminated_c_string",
|
||||||
|
"2200F": "zero_length_character_string",
|
||||||
|
"22P01": "floating_point_exception",
|
||||||
|
"22P02": "invalid_text_representation",
|
||||||
|
"22P03": "invalid_binary_representation",
|
||||||
|
"22P04": "bad_copy_file_format",
|
||||||
|
"22P05": "untranslatable_character",
|
||||||
|
"2200L": "not_an_xml_document",
|
||||||
|
"2200M": "invalid_xml_document",
|
||||||
|
"2200N": "invalid_xml_content",
|
||||||
|
"2200S": "invalid_xml_comment",
|
||||||
|
"2200T": "invalid_xml_processing_instruction",
|
||||||
|
// Class 23 - Integrity Constraint Violation
|
||||||
|
"23000": "integrity_constraint_violation",
|
||||||
|
"23001": "restrict_violation",
|
||||||
|
"23502": "not_null_violation",
|
||||||
|
"23503": "foreign_key_violation",
|
||||||
|
"23505": "unique_violation",
|
||||||
|
"23514": "check_violation",
|
||||||
|
"23P01": "exclusion_violation",
|
||||||
|
// Class 24 - Invalid Cursor State
|
||||||
|
"24000": "invalid_cursor_state",
|
||||||
|
// Class 25 - Invalid Transaction State
|
||||||
|
"25000": "invalid_transaction_state",
|
||||||
|
"25001": "active_sql_transaction",
|
||||||
|
"25002": "branch_transaction_already_active",
|
||||||
|
"25008": "held_cursor_requires_same_isolation_level",
|
||||||
|
"25003": "inappropriate_access_mode_for_branch_transaction",
|
||||||
|
"25004": "inappropriate_isolation_level_for_branch_transaction",
|
||||||
|
"25005": "no_active_sql_transaction_for_branch_transaction",
|
||||||
|
"25006": "read_only_sql_transaction",
|
||||||
|
"25007": "schema_and_data_statement_mixing_not_supported",
|
||||||
|
"25P01": "no_active_sql_transaction",
|
||||||
|
"25P02": "in_failed_sql_transaction",
|
||||||
|
// Class 26 - Invalid SQL Statement Name
|
||||||
|
"26000": "invalid_sql_statement_name",
|
||||||
|
// Class 27 - Triggered Data Change Violation
|
||||||
|
"27000": "triggered_data_change_violation",
|
||||||
|
// Class 28 - Invalid Authorization Specification
|
||||||
|
"28000": "invalid_authorization_specification",
|
||||||
|
"28P01": "invalid_password",
|
||||||
|
// Class 2B - Dependent Privilege Descriptors Still Exist
|
||||||
|
"2B000": "dependent_privilege_descriptors_still_exist",
|
||||||
|
"2BP01": "dependent_objects_still_exist",
|
||||||
|
// Class 2D - Invalid Transaction Termination
|
||||||
|
"2D000": "invalid_transaction_termination",
|
||||||
|
// Class 2F - SQL Routine Exception
|
||||||
|
"2F000": "sql_routine_exception",
|
||||||
|
"2F005": "function_executed_no_return_statement",
|
||||||
|
"2F002": "modifying_sql_data_not_permitted",
|
||||||
|
"2F003": "prohibited_sql_statement_attempted",
|
||||||
|
"2F004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 34 - Invalid Cursor Name
|
||||||
|
"34000": "invalid_cursor_name",
|
||||||
|
// Class 38 - External Routine Exception
|
||||||
|
"38000": "external_routine_exception",
|
||||||
|
"38001": "containing_sql_not_permitted",
|
||||||
|
"38002": "modifying_sql_data_not_permitted",
|
||||||
|
"38003": "prohibited_sql_statement_attempted",
|
||||||
|
"38004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 39 - External Routine Invocation Exception
|
||||||
|
"39000": "external_routine_invocation_exception",
|
||||||
|
"39001": "invalid_sqlstate_returned",
|
||||||
|
"39004": "null_value_not_allowed",
|
||||||
|
"39P01": "trigger_protocol_violated",
|
||||||
|
"39P02": "srf_protocol_violated",
|
||||||
|
// Class 3B - Savepoint Exception
|
||||||
|
"3B000": "savepoint_exception",
|
||||||
|
"3B001": "invalid_savepoint_specification",
|
||||||
|
// Class 3D - Invalid Catalog Name
|
||||||
|
"3D000": "invalid_catalog_name",
|
||||||
|
// Class 3F - Invalid Schema Name
|
||||||
|
"3F000": "invalid_schema_name",
|
||||||
|
// Class 40 - Transaction Rollback
|
||||||
|
"40000": "transaction_rollback",
|
||||||
|
"40002": "transaction_integrity_constraint_violation",
|
||||||
|
"40001": "serialization_failure",
|
||||||
|
"40003": "statement_completion_unknown",
|
||||||
|
"40P01": "deadlock_detected",
|
||||||
|
// Class 42 - Syntax Error or Access Rule Violation
|
||||||
|
"42000": "syntax_error_or_access_rule_violation",
|
||||||
|
"42601": "syntax_error",
|
||||||
|
"42501": "insufficient_privilege",
|
||||||
|
"42846": "cannot_coerce",
|
||||||
|
"42803": "grouping_error",
|
||||||
|
"42P20": "windowing_error",
|
||||||
|
"42P19": "invalid_recursion",
|
||||||
|
"42830": "invalid_foreign_key",
|
||||||
|
"42602": "invalid_name",
|
||||||
|
"42622": "name_too_long",
|
||||||
|
"42939": "reserved_name",
|
||||||
|
"42804": "datatype_mismatch",
|
||||||
|
"42P18": "indeterminate_datatype",
|
||||||
|
"42P21": "collation_mismatch",
|
||||||
|
"42P22": "indeterminate_collation",
|
||||||
|
"42809": "wrong_object_type",
|
||||||
|
"42703": "undefined_column",
|
||||||
|
"42883": "undefined_function",
|
||||||
|
"42P01": "undefined_table",
|
||||||
|
"42P02": "undefined_parameter",
|
||||||
|
"42704": "undefined_object",
|
||||||
|
"42701": "duplicate_column",
|
||||||
|
"42P03": "duplicate_cursor",
|
||||||
|
"42P04": "duplicate_database",
|
||||||
|
"42723": "duplicate_function",
|
||||||
|
"42P05": "duplicate_prepared_statement",
|
||||||
|
"42P06": "duplicate_schema",
|
||||||
|
"42P07": "duplicate_table",
|
||||||
|
"42712": "duplicate_alias",
|
||||||
|
"42710": "duplicate_object",
|
||||||
|
"42702": "ambiguous_column",
|
||||||
|
"42725": "ambiguous_function",
|
||||||
|
"42P08": "ambiguous_parameter",
|
||||||
|
"42P09": "ambiguous_alias",
|
||||||
|
"42P10": "invalid_column_reference",
|
||||||
|
"42611": "invalid_column_definition",
|
||||||
|
"42P11": "invalid_cursor_definition",
|
||||||
|
"42P12": "invalid_database_definition",
|
||||||
|
"42P13": "invalid_function_definition",
|
||||||
|
"42P14": "invalid_prepared_statement_definition",
|
||||||
|
"42P15": "invalid_schema_definition",
|
||||||
|
"42P16": "invalid_table_definition",
|
||||||
|
"42P17": "invalid_object_definition",
|
||||||
|
// Class 44 - WITH CHECK OPTION Violation
|
||||||
|
"44000": "with_check_option_violation",
|
||||||
|
// Class 53 - Insufficient Resources
|
||||||
|
"53000": "insufficient_resources",
|
||||||
|
"53100": "disk_full",
|
||||||
|
"53200": "out_of_memory",
|
||||||
|
"53300": "too_many_connections",
|
||||||
|
"53400": "configuration_limit_exceeded",
|
||||||
|
// Class 54 - Program Limit Exceeded
|
||||||
|
"54000": "program_limit_exceeded",
|
||||||
|
"54001": "statement_too_complex",
|
||||||
|
"54011": "too_many_columns",
|
||||||
|
"54023": "too_many_arguments",
|
||||||
|
// Class 55 - Object Not In Prerequisite State
|
||||||
|
"55000": "object_not_in_prerequisite_state",
|
||||||
|
"55006": "object_in_use",
|
||||||
|
"55P02": "cant_change_runtime_param",
|
||||||
|
"55P03": "lock_not_available",
|
||||||
|
// Class 57 - Operator Intervention
|
||||||
|
"57000": "operator_intervention",
|
||||||
|
"57014": "query_canceled",
|
||||||
|
"57P01": "admin_shutdown",
|
||||||
|
"57P02": "crash_shutdown",
|
||||||
|
"57P03": "cannot_connect_now",
|
||||||
|
"57P04": "database_dropped",
|
||||||
|
// Class 58 - System Error (errors external to PostgreSQL itself)
|
||||||
|
"58000": "system_error",
|
||||||
|
"58030": "io_error",
|
||||||
|
"58P01": "undefined_file",
|
||||||
|
"58P02": "duplicate_file",
|
||||||
|
// Class F0 - Configuration File Error
|
||||||
|
"F0000": "config_file_error",
|
||||||
|
"F0001": "lock_file_exists",
|
||||||
|
// Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||||
|
"HV000": "fdw_error",
|
||||||
|
"HV005": "fdw_column_name_not_found",
|
||||||
|
"HV002": "fdw_dynamic_parameter_value_needed",
|
||||||
|
"HV010": "fdw_function_sequence_error",
|
||||||
|
"HV021": "fdw_inconsistent_descriptor_information",
|
||||||
|
"HV024": "fdw_invalid_attribute_value",
|
||||||
|
"HV007": "fdw_invalid_column_name",
|
||||||
|
"HV008": "fdw_invalid_column_number",
|
||||||
|
"HV004": "fdw_invalid_data_type",
|
||||||
|
"HV006": "fdw_invalid_data_type_descriptors",
|
||||||
|
"HV091": "fdw_invalid_descriptor_field_identifier",
|
||||||
|
"HV00B": "fdw_invalid_handle",
|
||||||
|
"HV00C": "fdw_invalid_option_index",
|
||||||
|
"HV00D": "fdw_invalid_option_name",
|
||||||
|
"HV090": "fdw_invalid_string_length_or_buffer_length",
|
||||||
|
"HV00A": "fdw_invalid_string_format",
|
||||||
|
"HV009": "fdw_invalid_use_of_null_pointer",
|
||||||
|
"HV014": "fdw_too_many_handles",
|
||||||
|
"HV001": "fdw_out_of_memory",
|
||||||
|
"HV00P": "fdw_no_schemas",
|
||||||
|
"HV00J": "fdw_option_name_not_found",
|
||||||
|
"HV00K": "fdw_reply_handle",
|
||||||
|
"HV00Q": "fdw_schema_not_found",
|
||||||
|
"HV00R": "fdw_table_not_found",
|
||||||
|
"HV00L": "fdw_unable_to_create_execution",
|
||||||
|
"HV00M": "fdw_unable_to_create_reply",
|
||||||
|
"HV00N": "fdw_unable_to_establish_connection",
|
||||||
|
// Class P0 - PL/pgSQL Error
|
||||||
|
"P0000": "plpgsql_error",
|
||||||
|
"P0001": "raise_exception",
|
||||||
|
"P0002": "no_data_found",
|
||||||
|
"P0003": "too_many_rows",
|
||||||
|
// Class XX - Internal Error
|
||||||
|
"XX000": "internal_error",
|
||||||
|
"XX001": "data_corrupted",
|
||||||
|
"XX002": "index_corrupted",
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseError(r *readBuf) *Error {
|
||||||
|
err := new(Error)
|
||||||
|
for t := r.byte(); t != 0; t = r.byte() {
|
||||||
|
msg := r.string()
|
||||||
|
switch t {
|
||||||
|
case 'S':
|
||||||
|
err.Severity = msg
|
||||||
|
case 'C':
|
||||||
|
err.Code = ErrorCode(msg)
|
||||||
|
case 'M':
|
||||||
|
err.Message = msg
|
||||||
|
case 'D':
|
||||||
|
err.Detail = msg
|
||||||
|
case 'H':
|
||||||
|
err.Hint = msg
|
||||||
|
case 'P':
|
||||||
|
err.Position = msg
|
||||||
|
case 'p':
|
||||||
|
err.InternalPosition = msg
|
||||||
|
case 'q':
|
||||||
|
err.InternalQuery = msg
|
||||||
|
case 'W':
|
||||||
|
err.Where = msg
|
||||||
|
case 's':
|
||||||
|
err.Schema = msg
|
||||||
|
case 't':
|
||||||
|
err.Table = msg
|
||||||
|
case 'c':
|
||||||
|
err.Column = msg
|
||||||
|
case 'd':
|
||||||
|
err.DataTypeName = msg
|
||||||
|
case 'n':
|
||||||
|
err.Constraint = msg
|
||||||
|
case 'F':
|
||||||
|
err.File = msg
|
||||||
|
case 'L':
|
||||||
|
err.Line = msg
|
||||||
|
case 'R':
|
||||||
|
err.Routine = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal returns true if the Error Severity is fatal.
|
||||||
|
func (err *Error) Fatal() bool {
|
||||||
|
return err.Severity == Efatal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the legacy PGError interface. New code should use the fields
|
||||||
|
// of the Error struct directly.
|
||||||
|
func (err *Error) Get(k byte) (v string) {
|
||||||
|
switch k {
|
||||||
|
case 'S':
|
||||||
|
return err.Severity
|
||||||
|
case 'C':
|
||||||
|
return string(err.Code)
|
||||||
|
case 'M':
|
||||||
|
return err.Message
|
||||||
|
case 'D':
|
||||||
|
return err.Detail
|
||||||
|
case 'H':
|
||||||
|
return err.Hint
|
||||||
|
case 'P':
|
||||||
|
return err.Position
|
||||||
|
case 'p':
|
||||||
|
return err.InternalPosition
|
||||||
|
case 'q':
|
||||||
|
return err.InternalQuery
|
||||||
|
case 'W':
|
||||||
|
return err.Where
|
||||||
|
case 's':
|
||||||
|
return err.Schema
|
||||||
|
case 't':
|
||||||
|
return err.Table
|
||||||
|
case 'c':
|
||||||
|
return err.Column
|
||||||
|
case 'd':
|
||||||
|
return err.DataTypeName
|
||||||
|
case 'n':
|
||||||
|
return err.Constraint
|
||||||
|
case 'F':
|
||||||
|
return err.File
|
||||||
|
case 'L':
|
||||||
|
return err.Line
|
||||||
|
case 'R':
|
||||||
|
return err.Routine
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err Error) Error() string {
|
||||||
|
return "pq: " + err.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// PGError is an interface used by previous versions of pq. It is provided
|
||||||
|
// only to support legacy code. New code should use the Error type.
|
||||||
|
type PGError interface {
|
||||||
|
Error() string
|
||||||
|
Fatal() bool
|
||||||
|
Get(k byte) (v string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(s string, args ...interface{}) {
|
||||||
|
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ainar-g) Rename to errorf after removing panics.
|
||||||
|
func fmterrorf(s string, args ...interface{}) error {
|
||||||
|
return fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRecoverNoErrBadConn(err *error) {
|
||||||
|
e := recover()
|
||||||
|
if e == nil {
|
||||||
|
// Do nothing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
*err, ok = e.(error)
|
||||||
|
if !ok {
|
||||||
|
*err = fmt.Errorf("pq: unexpected error: %#v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) errRecover(err *error) {
|
||||||
|
e := recover()
|
||||||
|
switch v := e.(type) {
|
||||||
|
case nil:
|
||||||
|
// Do nothing
|
||||||
|
case runtime.Error:
|
||||||
|
cn.setBad()
|
||||||
|
panic(v)
|
||||||
|
case *Error:
|
||||||
|
if v.Fatal() {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
case *net.OpError:
|
||||||
|
cn.setBad()
|
||||||
|
*err = v
|
||||||
|
case *safeRetryError:
|
||||||
|
cn.setBad()
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
case error:
|
||||||
|
if v == io.EOF || v.(error).Error() == "remote error: handshake failure" {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
cn.setBad()
|
||||||
|
panic(fmt.Sprintf("unknown error: %#v", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any time we return ErrBadConn, we need to remember it since *Tx doesn't
|
||||||
|
// mark the connection bad in database/sql.
|
||||||
|
if *err == driver.ErrBadConn {
|
||||||
|
cn.setBad()
|
||||||
|
}
|
||||||
|
}
|
3
vendor/github.com/lib/pq/go.mod
generated
vendored
Normal file
3
vendor/github.com/lib/pq/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/lib/pq
|
||||||
|
|
||||||
|
go 1.13
|
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// NewGSSFunc creates a GSS authentication provider, for use with
|
||||||
|
// RegisterGSSProvider.
|
||||||
|
type NewGSSFunc func() (GSS, error)
|
||||||
|
|
||||||
|
var newGss NewGSSFunc
|
||||||
|
|
||||||
|
// RegisterGSSProvider registers a GSS authentication provider. For example, if
|
||||||
|
// you need to use Kerberos to authenticate with your server, add this to your
|
||||||
|
// main package:
|
||||||
|
//
|
||||||
|
// import "github.com/lib/pq/auth/kerberos"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// pq.RegisterGSSProvider(func() (pq.GSS, error) { return kerberos.NewGSS() })
|
||||||
|
// }
|
||||||
|
func RegisterGSSProvider(newGssArg NewGSSFunc) {
|
||||||
|
newGss = newGssArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// GSS provides GSSAPI authentication (e.g., Kerberos).
|
||||||
|
type GSS interface {
|
||||||
|
GetInitToken(host string, service string) ([]byte, error)
|
||||||
|
GetInitTokenFromSpn(spn string) ([]byte, error)
|
||||||
|
Continue(inToken []byte) (done bool, outToken []byte, err error)
|
||||||
|
}
|
71
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
71
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoticeHandler returns the notice handler on the given connection, if any. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. This is rarely used
|
||||||
|
// directly, use ConnectorNoticeHandler and ConnectorWithNoticeHandler instead.
|
||||||
|
func NoticeHandler(c driver.Conn) func(*Error) {
|
||||||
|
return c.(*conn).noticeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoticeHandler sets the given notice handler on the given connection. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. A nil handler may be used
|
||||||
|
// to unset it. This is rarely used directly, use ConnectorNoticeHandler and
|
||||||
|
// ConnectorWithNoticeHandler instead.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNoticeHandler(c driver.Conn, handler func(*Error)) {
|
||||||
|
c.(*conn).noticeHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoticeHandlerConnector wraps a regular connector and sets a notice handler
|
||||||
|
// on it.
|
||||||
|
type NoticeHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
noticeHandler func(*Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notice handler.
|
||||||
|
func (n *NoticeHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNoticeHandler(c, n.noticeHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNoticeHandler returns the currently set notice handler, if any. If
|
||||||
|
// the given connector is not a result of ConnectorWithNoticeHandler, nil is
|
||||||
|
// returned.
|
||||||
|
func ConnectorNoticeHandler(c driver.Connector) func(*Error) {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
return c.noticeHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNoticeHandler creates or sets the given handler for the given
|
||||||
|
// connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the notice
|
||||||
|
// handler. A nil notice handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNoticeHandler(c driver.Connector, handler func(*Error)) *NoticeHandlerConnector {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
c.noticeHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NoticeHandlerConnector{Connector: c, noticeHandler: handler}
|
||||||
|
}
|
858
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
858
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
|
@ -0,0 +1,858 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
// This module contains support for Postgres LISTEN/NOTIFY.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notification represents a single notification from the database.
|
||||||
|
type Notification struct {
|
||||||
|
// Process ID (PID) of the notifying postgres backend.
|
||||||
|
BePid int
|
||||||
|
// Name of the channel the notification was sent on.
|
||||||
|
Channel string
|
||||||
|
// Payload, or the empty string if unspecified.
|
||||||
|
Extra string
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvNotification(r *readBuf) *Notification {
|
||||||
|
bePid := r.int32()
|
||||||
|
channel := r.string()
|
||||||
|
extra := r.string()
|
||||||
|
|
||||||
|
return &Notification{bePid, channel, extra}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNotificationHandler sets the given notification handler on the given
|
||||||
|
// connection. A runtime panic occurs if c is not a pq connection. A nil handler
|
||||||
|
// may be used to unset it.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNotificationHandler(c driver.Conn, handler func(*Notification)) {
|
||||||
|
c.(*conn).notificationHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationHandlerConnector wraps a regular connector and sets a notification handler
|
||||||
|
// on it.
|
||||||
|
type NotificationHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
notificationHandler func(*Notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notification handler.
|
||||||
|
func (n *NotificationHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNotificationHandler(c, n.notificationHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNotificationHandler returns the currently set notification handler, if any. If
|
||||||
|
// the given connector is not a result of ConnectorWithNotificationHandler, nil is
|
||||||
|
// returned.
|
||||||
|
func ConnectorNotificationHandler(c driver.Connector) func(*Notification) {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
return c.notificationHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNotificationHandler creates or sets the given handler for the given
|
||||||
|
// connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the notification
|
||||||
|
// handler. A nil notification handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNotificationHandler(c driver.Connector, handler func(*Notification)) *NotificationHandlerConnector {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
c.notificationHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NotificationHandlerConnector{Connector: c, notificationHandler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
connStateIdle int32 = iota
|
||||||
|
connStateExpectResponse
|
||||||
|
connStateExpectReadyForQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
typ byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
|
||||||
|
|
||||||
|
// ListenerConn is a low-level interface for waiting for notifications. You
|
||||||
|
// should use Listener instead.
|
||||||
|
type ListenerConn struct {
|
||||||
|
// guards cn and err
|
||||||
|
connectionLock sync.Mutex
|
||||||
|
cn *conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
connState int32
|
||||||
|
|
||||||
|
// the sending goroutine will be holding this lock
|
||||||
|
senderLock sync.Mutex
|
||||||
|
|
||||||
|
notificationChan chan<- *Notification
|
||||||
|
|
||||||
|
replyChan chan message
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListenerConn creates a new ListenerConn. Use NewListener instead.
|
||||||
|
func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
return newDialListenerConn(defaultDialer{}, name, notificationChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
cn, err := DialOpen(d, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &ListenerConn{
|
||||||
|
cn: cn.(*conn),
|
||||||
|
notificationChan: c,
|
||||||
|
connState: connStateIdle,
|
||||||
|
replyChan: make(chan message, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.listenerConnMain()
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only allow one goroutine at a time to be running a query on the
|
||||||
|
// connection for various reasons, so the goroutine sending on the connection
|
||||||
|
// must be holding senderLock.
|
||||||
|
//
|
||||||
|
// Returns an error if an unrecoverable error has occurred and the ListenerConn
|
||||||
|
// should be abandoned.
|
||||||
|
func (l *ListenerConn) acquireSenderLock() error {
|
||||||
|
// we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery
|
||||||
|
l.senderLock.Lock()
|
||||||
|
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerConn) releaseSenderLock() {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setState advances the protocol state to newState. Returns false if moving
|
||||||
|
// to that state from the current state is not allowed.
|
||||||
|
func (l *ListenerConn) setState(newState int32) bool {
|
||||||
|
var expectedState int32
|
||||||
|
|
||||||
|
switch newState {
|
||||||
|
case connStateIdle:
|
||||||
|
expectedState = connStateExpectReadyForQuery
|
||||||
|
case connStateExpectResponse:
|
||||||
|
expectedState = connStateIdle
|
||||||
|
case connStateExpectReadyForQuery:
|
||||||
|
expectedState = connStateExpectResponse
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
|
||||||
|
}
|
||||||
|
|
||||||
|
return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic is here: receive messages from the postgres backend, forward
|
||||||
|
// notifications and query replies and keep the internal state in sync with the
|
||||||
|
// protocol state. Returns when the connection has been lost, is about to go
|
||||||
|
// away or should be discarded because we couldn't agree on the state with the
|
||||||
|
// server backend.
|
||||||
|
func (l *ListenerConn) listenerConnLoop() (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
r := &readBuf{}
|
||||||
|
for {
|
||||||
|
t, err := l.cn.recvMessage(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case 'A':
|
||||||
|
// recvNotification copies all the data so we don't need to worry
|
||||||
|
// about the scratch buffer being overwritten.
|
||||||
|
l.notificationChan <- recvNotification(r)
|
||||||
|
|
||||||
|
case 'T', 'D':
|
||||||
|
// only used by tests; ignore
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// We might receive an ErrorResponse even when not in a query; it
|
||||||
|
// is expected that the server will close the connection after
|
||||||
|
// that, but we should make sure that the error we display is the
|
||||||
|
// one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
return parseError(r)
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, parseError(r)}
|
||||||
|
|
||||||
|
case 'C', 'I':
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected CommandComplete")
|
||||||
|
}
|
||||||
|
// ExecSimpleQuery doesn't need to know about this message
|
||||||
|
|
||||||
|
case 'Z':
|
||||||
|
if !l.setState(connStateIdle) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected ReadyForQuery")
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, nil}
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
// ignore
|
||||||
|
case 'N':
|
||||||
|
if n := l.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(r))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the main routine for the goroutine receiving on the database
|
||||||
|
// connection. Most of the main logic is in listenerConnLoop.
|
||||||
|
func (l *ListenerConn) listenerConnMain() {
|
||||||
|
err := l.listenerConnLoop()
|
||||||
|
|
||||||
|
// listenerConnLoop terminated; we're done, but we still have to clean up.
|
||||||
|
// Make sure nobody tries to start any new queries by making sure the err
|
||||||
|
// pointer is set. It is important that we do not overwrite its value; a
|
||||||
|
// connection could be closed by either this goroutine or one sending on
|
||||||
|
// the connection -- whoever closes the connection is assumed to have the
|
||||||
|
// more meaningful error message (as the other one will probably get
|
||||||
|
// net.errClosed), so that goroutine sets the error we expose while the
|
||||||
|
// other error is discarded. If the connection is lost while two
|
||||||
|
// goroutines are operating on the socket, it probably doesn't matter which
|
||||||
|
// error we expose so we don't try to do anything more complex.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.cn.Close()
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
|
||||||
|
// There might be a query in-flight; make sure nobody's waiting for a
|
||||||
|
// response to it, since there's not going to be one.
|
||||||
|
close(l.replyChan)
|
||||||
|
|
||||||
|
// let the listener know we're done
|
||||||
|
close(l.notificationChan)
|
||||||
|
|
||||||
|
// this ListenerConn is done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen sends a LISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten sends an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll sends an `UNLISTEN *` query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) UnlistenAll() (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN *")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil error means the
|
||||||
|
// connection has failed and should be abandoned.
|
||||||
|
func (l *ListenerConn) Ping() error {
|
||||||
|
sent, err := l.ExecSimpleQuery("")
|
||||||
|
if !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// shouldn't happen
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send a query on the connection. Returns an error if sending the
|
||||||
|
// query failed, and the caller should initiate closure of this connection.
|
||||||
|
// The caller must be holding senderLock (see acquireSenderLock and
|
||||||
|
// releaseSenderLock).
|
||||||
|
func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
// must set connection state before sending the query
|
||||||
|
if !l.setState(connStateExpectResponse) {
|
||||||
|
panic("two queries running at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use l.cn.writeBuf here because it uses the scratch buffer which
|
||||||
|
// might get overwritten by listenerConnLoop.
|
||||||
|
b := &writeBuf{
|
||||||
|
buf: []byte("Q\x00\x00\x00\x00"),
|
||||||
|
pos: 1,
|
||||||
|
}
|
||||||
|
b.string(q)
|
||||||
|
l.cn.send(b)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecSimpleQuery executes a "simple query" (i.e. one with no bindable
|
||||||
|
// parameters) on the connection. The possible return values are:
|
||||||
|
// 1) "executed" is true; the query was executed to completion on the
|
||||||
|
// database server. If the query failed, err will be set to the error
|
||||||
|
// returned by the database, otherwise err will be nil.
|
||||||
|
// 2) If "executed" is false, the query could not be executed on the remote
|
||||||
|
// server. err will be non-nil.
|
||||||
|
//
|
||||||
|
// After a call to ExecSimpleQuery has returned an executed=false value, the
|
||||||
|
// connection has either been closed or will be closed shortly thereafter, and
|
||||||
|
// all subsequently executed queries will return an error.
|
||||||
|
func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
|
||||||
|
if err = l.acquireSenderLock(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer l.releaseSenderLock()
|
||||||
|
|
||||||
|
err = l.sendSimpleQuery(q)
|
||||||
|
if err != nil {
|
||||||
|
// We can't know what state the protocol is in, so we need to abandon
|
||||||
|
// this connection.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
// Set the error pointer if it hasn't been set already; see
|
||||||
|
// listenerConnMain.
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
l.cn.c.Close()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we just wait for a reply..
|
||||||
|
for {
|
||||||
|
m, ok := <-l.replyChan
|
||||||
|
if !ok {
|
||||||
|
// We lost the connection to server, don't bother waiting for a
|
||||||
|
// a response. err should have been set already.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch m.typ {
|
||||||
|
case 'Z':
|
||||||
|
// sanity check
|
||||||
|
if m.err != nil {
|
||||||
|
panic("m.err != nil")
|
||||||
|
}
|
||||||
|
// done; err might or might not be set
|
||||||
|
return true, err
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// sanity check
|
||||||
|
if m.err == nil {
|
||||||
|
panic("m.err == nil")
|
||||||
|
}
|
||||||
|
// server responded with an error; ReadyForQuery to follow
|
||||||
|
err = m.err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (l *ListenerConn) Close() error {
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err != nil {
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return errListenerConnClosed
|
||||||
|
}
|
||||||
|
l.err = errListenerConnClosed
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
// We can't send anything on the connection without holding senderLock.
|
||||||
|
// Simply close the net.Conn to wake up everyone operating on it.
|
||||||
|
return l.cn.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the reason the connection was closed. It is not safe to call
|
||||||
|
// this function until l.Notify has been closed.
|
||||||
|
func (l *ListenerConn) Err() error {
|
||||||
|
return l.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerClosed = errors.New("pq: Listener has been closed")
|
||||||
|
|
||||||
|
// ErrChannelAlreadyOpen is returned from Listen when a channel is already
|
||||||
|
// open.
|
||||||
|
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||||
|
|
||||||
|
// ErrChannelNotOpen is returned from Unlisten when a channel is not open.
|
||||||
|
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||||
|
|
||||||
|
// ListenerEventType is an enumeration of listener event types.
|
||||||
|
type ListenerEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ListenerEventConnected is emitted only when the database connection
|
||||||
|
// has been initially initialized. The err argument of the callback
|
||||||
|
// will always be nil.
|
||||||
|
ListenerEventConnected ListenerEventType = iota
|
||||||
|
|
||||||
|
// ListenerEventDisconnected is emitted after a database connection has
|
||||||
|
// been lost, either because of an error or because Close has been
|
||||||
|
// called. The err argument will be set to the reason the database
|
||||||
|
// connection was lost.
|
||||||
|
ListenerEventDisconnected
|
||||||
|
|
||||||
|
// ListenerEventReconnected is emitted after a database connection has
|
||||||
|
// been re-established after connection loss. The err argument of the
|
||||||
|
// callback will always be nil. After this event has been emitted, a
|
||||||
|
// nil pq.Notification is sent on the Listener.Notify channel.
|
||||||
|
ListenerEventReconnected
|
||||||
|
|
||||||
|
// ListenerEventConnectionAttemptFailed is emitted after a connection
|
||||||
|
// to the database was attempted, but failed. The err argument will be
|
||||||
|
// set to an error describing why the connection attempt did not
|
||||||
|
// succeed.
|
||||||
|
ListenerEventConnectionAttemptFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventCallbackType is the event callback type. See also ListenerEventType
|
||||||
|
// constants' documentation.
|
||||||
|
type EventCallbackType func(event ListenerEventType, err error)
|
||||||
|
|
||||||
|
// Listener provides an interface for listening to notifications from a
|
||||||
|
// PostgreSQL database. For general usage information, see section
|
||||||
|
// "Notifications".
|
||||||
|
//
|
||||||
|
// Listener can safely be used from concurrently running goroutines.
|
||||||
|
type Listener struct {
|
||||||
|
// Channel for receiving notifications from the database. In some cases a
|
||||||
|
// nil value will be sent. See section "Notifications" above.
|
||||||
|
Notify chan *Notification
|
||||||
|
|
||||||
|
name string
|
||||||
|
minReconnectInterval time.Duration
|
||||||
|
maxReconnectInterval time.Duration
|
||||||
|
dialer Dialer
|
||||||
|
eventCallback EventCallbackType
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
isClosed bool
|
||||||
|
reconnectCond *sync.Cond
|
||||||
|
cn *ListenerConn
|
||||||
|
connNotificationChan <-chan *Notification
|
||||||
|
channels map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
|
||||||
|
//
|
||||||
|
// name should be set to a connection string to be used to establish the
|
||||||
|
// database connection (see section "Connection String Parameters" above).
|
||||||
|
//
|
||||||
|
// minReconnectInterval controls the duration to wait before trying to
|
||||||
|
// re-establish the database connection after connection loss. After each
|
||||||
|
// consecutive failure this interval is doubled, until maxReconnectInterval is
|
||||||
|
// reached. Successfully completing the connection establishment procedure
|
||||||
|
// resets the interval back to minReconnectInterval.
|
||||||
|
//
|
||||||
|
// The last parameter eventCallback can be set to a function which will be
|
||||||
|
// called by the Listener when the state of the underlying database connection
|
||||||
|
// changes. This callback will be called by the goroutine which dispatches the
|
||||||
|
// notifications over the Notify channel, so you should try to avoid doing
|
||||||
|
// potentially time-consuming operations from the callback.
|
||||||
|
func NewListener(name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
return NewDialListener(defaultDialer{}, name, minReconnectInterval, maxReconnectInterval, eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialListener is like NewListener but it takes a Dialer.
|
||||||
|
func NewDialListener(d Dialer,
|
||||||
|
name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
|
||||||
|
l := &Listener{
|
||||||
|
name: name,
|
||||||
|
minReconnectInterval: minReconnectInterval,
|
||||||
|
maxReconnectInterval: maxReconnectInterval,
|
||||||
|
dialer: d,
|
||||||
|
eventCallback: eventCallback,
|
||||||
|
|
||||||
|
channels: make(map[string]struct{}),
|
||||||
|
|
||||||
|
Notify: make(chan *Notification, 32),
|
||||||
|
}
|
||||||
|
l.reconnectCond = sync.NewCond(&l.lock)
|
||||||
|
|
||||||
|
go l.listenerMain()
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationChannel returns the notification channel for this listener.
|
||||||
|
// This is the same channel as Notify, and will not be recreated during the
|
||||||
|
// life time of the Listener.
|
||||||
|
func (l *Listener) NotificationChannel() <-chan *Notification {
|
||||||
|
return l.Notify
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen starts listening for notifications on a channel. Calls to this
|
||||||
|
// function will block until an acknowledgement has been received from the
|
||||||
|
// server. Note that Listener automatically re-establishes the connection
|
||||||
|
// after connection loss, so this function may block indefinitely if the
|
||||||
|
// connection can not be re-established.
|
||||||
|
//
|
||||||
|
// Listen will only fail in three conditions:
|
||||||
|
// 1) The channel is already open. The returned error will be
|
||||||
|
// ErrChannelAlreadyOpen.
|
||||||
|
// 2) The query was executed on the remote server, but PostgreSQL returned an
|
||||||
|
// error message in response to the query. The returned error will be a
|
||||||
|
// pq.Error containing the information the server supplied.
|
||||||
|
// 3) Close is called on the Listener before the request could be completed.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Listen(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server allows you to issue a LISTEN on a channel which is already
|
||||||
|
// open, but it seems useful to be able to detect this case to spot for
|
||||||
|
// mistakes in application logic. If the application genuinely does't
|
||||||
|
// care, it can check the exported error and ignore it.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if exists {
|
||||||
|
return ErrChannelAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// If gotResponse is true but error is set, the query was executed on
|
||||||
|
// the remote server, but resulted in an error. This should be
|
||||||
|
// relatively rare, so it's fine if we just pass the error to our
|
||||||
|
// caller. However, if gotResponse is false, we could not complete the
|
||||||
|
// query on the remote server and our underlying connection is about
|
||||||
|
// to go away, so we only add relname to l.channels, and wait for
|
||||||
|
// resync() to take care of the rest.
|
||||||
|
gotResponse, err := l.cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.channels[channel] = struct{}{}
|
||||||
|
for l.cn == nil {
|
||||||
|
l.reconnectCond.Wait()
|
||||||
|
// we let go of the mutex for a while
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten removes a channel from the Listener's channel list. Returns
|
||||||
|
// ErrChannelNotOpen if the Listener is not listening on the specified channel.
|
||||||
|
// Returns immediately with no error if there is no connection. Note that you
|
||||||
|
// might still get notifications for this channel even after Unlisten has
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Unlisten(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly to LISTEN, this is not an error in Postgres, but it seems
|
||||||
|
// useful to distinguish from the normal conditions.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if !exists {
|
||||||
|
return ErrChannelNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.Unlisten(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
delete(l.channels, channel)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll removes all channels from the Listener's channel list. Returns
|
||||||
|
// immediately with no error if there is no connection. Note that you might
|
||||||
|
// still get notifications for any of the deleted channels even after
|
||||||
|
// UnlistenAll has returned.
|
||||||
|
func (l *Listener) UnlistenAll() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.UnlistenAll()
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
l.channels = make(map[string]struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil return value means
|
||||||
|
// that there is no active connection.
|
||||||
|
func (l *Listener) Ping() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
if l.cn == nil {
|
||||||
|
return errors.New("no connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.cn.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after losing the server connection. Returns l.cn.Err(), which
|
||||||
|
// should have the reason the connection was lost.
|
||||||
|
func (l *Listener) disconnectCleanup() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
// sanity check; can't look at Err() until the channel has been closed
|
||||||
|
select {
|
||||||
|
case _, ok := <-l.connNotificationChan:
|
||||||
|
if ok {
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.cn.Err()
|
||||||
|
l.cn.Close()
|
||||||
|
l.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the list of channels we want to be listening on with the server
|
||||||
|
// after the connection has been established.
|
||||||
|
func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
|
||||||
|
doneChan := make(chan error)
|
||||||
|
go func(notificationChan <-chan *Notification) {
|
||||||
|
for channel := range l.channels {
|
||||||
|
// If we got a response, return that error to our caller as it's
|
||||||
|
// going to be more descriptive than cn.Err().
|
||||||
|
gotResponse, err := cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
doneChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't reach the server, wait for notificationChan to
|
||||||
|
// close and then return the error message from the connection, as
|
||||||
|
// per ListenerConn's interface.
|
||||||
|
if err != nil {
|
||||||
|
for range notificationChan {
|
||||||
|
}
|
||||||
|
doneChan <- cn.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneChan <- nil
|
||||||
|
}(notificationChan)
|
||||||
|
|
||||||
|
// Ignore notifications while synchronization is going on to avoid
|
||||||
|
// deadlocks. We have to send a nil notification over Notify anyway as
|
||||||
|
// we can't possibly know which notifications (if any) were lost while
|
||||||
|
// the connection was down, so there's no reason to try and process
|
||||||
|
// these messages at all.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-notificationChan:
|
||||||
|
if !ok {
|
||||||
|
notificationChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case err := <-doneChan:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should NOT be holding l.lock
|
||||||
|
func (l *Listener) closed() bool {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
return l.isClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) connect() error {
|
||||||
|
notificationChan := make(chan *Notification, 32)
|
||||||
|
cn, err := newDialListenerConn(l.dialer, l.name, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
err = l.resync(cn, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
cn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.cn = cn
|
||||||
|
l.connNotificationChan = notificationChan
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close disconnects the Listener from the database and shuts it down.
|
||||||
|
// Subsequent calls to its methods will return an error. Close returns an
|
||||||
|
// error if the connection has already been closed.
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
l.cn.Close()
|
||||||
|
}
|
||||||
|
l.isClosed = true
|
||||||
|
|
||||||
|
// Unblock calls to Listen()
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) emitEvent(event ListenerEventType, err error) {
|
||||||
|
if l.eventCallback != nil {
|
||||||
|
l.eventCallback(event, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic here: maintain a connection to the server when possible, wait
|
||||||
|
// for notifications and emit events.
|
||||||
|
func (l *Listener) listenerConnLoop() {
|
||||||
|
var nextReconnect time.Time
|
||||||
|
|
||||||
|
reconnectInterval := l.minReconnectInterval
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
err := l.connect()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventConnectionAttemptFailed, err)
|
||||||
|
|
||||||
|
time.Sleep(reconnectInterval)
|
||||||
|
reconnectInterval *= 2
|
||||||
|
if reconnectInterval > l.maxReconnectInterval {
|
||||||
|
reconnectInterval = l.maxReconnectInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextReconnect.IsZero() {
|
||||||
|
l.emitEvent(ListenerEventConnected, nil)
|
||||||
|
} else {
|
||||||
|
l.emitEvent(ListenerEventReconnected, nil)
|
||||||
|
l.Notify <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectInterval = l.minReconnectInterval
|
||||||
|
nextReconnect = time.Now().Add(reconnectInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
notification, ok := <-l.connNotificationChan
|
||||||
|
if !ok {
|
||||||
|
// lost connection, loop again
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.Notify <- notification
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.disconnectCleanup()
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventDisconnected, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Until(nextReconnect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) listenerMain() {
|
||||||
|
l.listenerConnLoop()
|
||||||
|
close(l.Notify)
|
||||||
|
}
|
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Package oid contains OID constants
|
||||||
|
// as defined by the Postgres server.
|
||||||
|
package oid
|
||||||
|
|
||||||
|
// Oid is a Postgres Object ID.
|
||||||
|
type Oid uint32
|
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
// Code generated by gen.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
package oid
|
||||||
|
|
||||||
|
const (
|
||||||
|
T_bool Oid = 16
|
||||||
|
T_bytea Oid = 17
|
||||||
|
T_char Oid = 18
|
||||||
|
T_name Oid = 19
|
||||||
|
T_int8 Oid = 20
|
||||||
|
T_int2 Oid = 21
|
||||||
|
T_int2vector Oid = 22
|
||||||
|
T_int4 Oid = 23
|
||||||
|
T_regproc Oid = 24
|
||||||
|
T_text Oid = 25
|
||||||
|
T_oid Oid = 26
|
||||||
|
T_tid Oid = 27
|
||||||
|
T_xid Oid = 28
|
||||||
|
T_cid Oid = 29
|
||||||
|
T_oidvector Oid = 30
|
||||||
|
T_pg_ddl_command Oid = 32
|
||||||
|
T_pg_type Oid = 71
|
||||||
|
T_pg_attribute Oid = 75
|
||||||
|
T_pg_proc Oid = 81
|
||||||
|
T_pg_class Oid = 83
|
||||||
|
T_json Oid = 114
|
||||||
|
T_xml Oid = 142
|
||||||
|
T__xml Oid = 143
|
||||||
|
T_pg_node_tree Oid = 194
|
||||||
|
T__json Oid = 199
|
||||||
|
T_smgr Oid = 210
|
||||||
|
T_index_am_handler Oid = 325
|
||||||
|
T_point Oid = 600
|
||||||
|
T_lseg Oid = 601
|
||||||
|
T_path Oid = 602
|
||||||
|
T_box Oid = 603
|
||||||
|
T_polygon Oid = 604
|
||||||
|
T_line Oid = 628
|
||||||
|
T__line Oid = 629
|
||||||
|
T_cidr Oid = 650
|
||||||
|
T__cidr Oid = 651
|
||||||
|
T_float4 Oid = 700
|
||||||
|
T_float8 Oid = 701
|
||||||
|
T_abstime Oid = 702
|
||||||
|
T_reltime Oid = 703
|
||||||
|
T_tinterval Oid = 704
|
||||||
|
T_unknown Oid = 705
|
||||||
|
T_circle Oid = 718
|
||||||
|
T__circle Oid = 719
|
||||||
|
T_money Oid = 790
|
||||||
|
T__money Oid = 791
|
||||||
|
T_macaddr Oid = 829
|
||||||
|
T_inet Oid = 869
|
||||||
|
T__bool Oid = 1000
|
||||||
|
T__bytea Oid = 1001
|
||||||
|
T__char Oid = 1002
|
||||||
|
T__name Oid = 1003
|
||||||
|
T__int2 Oid = 1005
|
||||||
|
T__int2vector Oid = 1006
|
||||||
|
T__int4 Oid = 1007
|
||||||
|
T__regproc Oid = 1008
|
||||||
|
T__text Oid = 1009
|
||||||
|
T__tid Oid = 1010
|
||||||
|
T__xid Oid = 1011
|
||||||
|
T__cid Oid = 1012
|
||||||
|
T__oidvector Oid = 1013
|
||||||
|
T__bpchar Oid = 1014
|
||||||
|
T__varchar Oid = 1015
|
||||||
|
T__int8 Oid = 1016
|
||||||
|
T__point Oid = 1017
|
||||||
|
T__lseg Oid = 1018
|
||||||
|
T__path Oid = 1019
|
||||||
|
T__box Oid = 1020
|
||||||
|
T__float4 Oid = 1021
|
||||||
|
T__float8 Oid = 1022
|
||||||
|
T__abstime Oid = 1023
|
||||||
|
T__reltime Oid = 1024
|
||||||
|
T__tinterval Oid = 1025
|
||||||
|
T__polygon Oid = 1027
|
||||||
|
T__oid Oid = 1028
|
||||||
|
T_aclitem Oid = 1033
|
||||||
|
T__aclitem Oid = 1034
|
||||||
|
T__macaddr Oid = 1040
|
||||||
|
T__inet Oid = 1041
|
||||||
|
T_bpchar Oid = 1042
|
||||||
|
T_varchar Oid = 1043
|
||||||
|
T_date Oid = 1082
|
||||||
|
T_time Oid = 1083
|
||||||
|
T_timestamp Oid = 1114
|
||||||
|
T__timestamp Oid = 1115
|
||||||
|
T__date Oid = 1182
|
||||||
|
T__time Oid = 1183
|
||||||
|
T_timestamptz Oid = 1184
|
||||||
|
T__timestamptz Oid = 1185
|
||||||
|
T_interval Oid = 1186
|
||||||
|
T__interval Oid = 1187
|
||||||
|
T__numeric Oid = 1231
|
||||||
|
T_pg_database Oid = 1248
|
||||||
|
T__cstring Oid = 1263
|
||||||
|
T_timetz Oid = 1266
|
||||||
|
T__timetz Oid = 1270
|
||||||
|
T_bit Oid = 1560
|
||||||
|
T__bit Oid = 1561
|
||||||
|
T_varbit Oid = 1562
|
||||||
|
T__varbit Oid = 1563
|
||||||
|
T_numeric Oid = 1700
|
||||||
|
T_refcursor Oid = 1790
|
||||||
|
T__refcursor Oid = 2201
|
||||||
|
T_regprocedure Oid = 2202
|
||||||
|
T_regoper Oid = 2203
|
||||||
|
T_regoperator Oid = 2204
|
||||||
|
T_regclass Oid = 2205
|
||||||
|
T_regtype Oid = 2206
|
||||||
|
T__regprocedure Oid = 2207
|
||||||
|
T__regoper Oid = 2208
|
||||||
|
T__regoperator Oid = 2209
|
||||||
|
T__regclass Oid = 2210
|
||||||
|
T__regtype Oid = 2211
|
||||||
|
T_record Oid = 2249
|
||||||
|
T_cstring Oid = 2275
|
||||||
|
T_any Oid = 2276
|
||||||
|
T_anyarray Oid = 2277
|
||||||
|
T_void Oid = 2278
|
||||||
|
T_trigger Oid = 2279
|
||||||
|
T_language_handler Oid = 2280
|
||||||
|
T_internal Oid = 2281
|
||||||
|
T_opaque Oid = 2282
|
||||||
|
T_anyelement Oid = 2283
|
||||||
|
T__record Oid = 2287
|
||||||
|
T_anynonarray Oid = 2776
|
||||||
|
T_pg_authid Oid = 2842
|
||||||
|
T_pg_auth_members Oid = 2843
|
||||||
|
T__txid_snapshot Oid = 2949
|
||||||
|
T_uuid Oid = 2950
|
||||||
|
T__uuid Oid = 2951
|
||||||
|
T_txid_snapshot Oid = 2970
|
||||||
|
T_fdw_handler Oid = 3115
|
||||||
|
T_pg_lsn Oid = 3220
|
||||||
|
T__pg_lsn Oid = 3221
|
||||||
|
T_tsm_handler Oid = 3310
|
||||||
|
T_anyenum Oid = 3500
|
||||||
|
T_tsvector Oid = 3614
|
||||||
|
T_tsquery Oid = 3615
|
||||||
|
T_gtsvector Oid = 3642
|
||||||
|
T__tsvector Oid = 3643
|
||||||
|
T__gtsvector Oid = 3644
|
||||||
|
T__tsquery Oid = 3645
|
||||||
|
T_regconfig Oid = 3734
|
||||||
|
T__regconfig Oid = 3735
|
||||||
|
T_regdictionary Oid = 3769
|
||||||
|
T__regdictionary Oid = 3770
|
||||||
|
T_jsonb Oid = 3802
|
||||||
|
T__jsonb Oid = 3807
|
||||||
|
T_anyrange Oid = 3831
|
||||||
|
T_event_trigger Oid = 3838
|
||||||
|
T_int4range Oid = 3904
|
||||||
|
T__int4range Oid = 3905
|
||||||
|
T_numrange Oid = 3906
|
||||||
|
T__numrange Oid = 3907
|
||||||
|
T_tsrange Oid = 3908
|
||||||
|
T__tsrange Oid = 3909
|
||||||
|
T_tstzrange Oid = 3910
|
||||||
|
T__tstzrange Oid = 3911
|
||||||
|
T_daterange Oid = 3912
|
||||||
|
T__daterange Oid = 3913
|
||||||
|
T_int8range Oid = 3926
|
||||||
|
T__int8range Oid = 3927
|
||||||
|
T_pg_shseclabel Oid = 4066
|
||||||
|
T_regnamespace Oid = 4089
|
||||||
|
T__regnamespace Oid = 4090
|
||||||
|
T_regrole Oid = 4096
|
||||||
|
T__regrole Oid = 4097
|
||||||
|
)
|
||||||
|
|
||||||
|
var TypeName = map[Oid]string{
|
||||||
|
T_bool: "BOOL",
|
||||||
|
T_bytea: "BYTEA",
|
||||||
|
T_char: "CHAR",
|
||||||
|
T_name: "NAME",
|
||||||
|
T_int8: "INT8",
|
||||||
|
T_int2: "INT2",
|
||||||
|
T_int2vector: "INT2VECTOR",
|
||||||
|
T_int4: "INT4",
|
||||||
|
T_regproc: "REGPROC",
|
||||||
|
T_text: "TEXT",
|
||||||
|
T_oid: "OID",
|
||||||
|
T_tid: "TID",
|
||||||
|
T_xid: "XID",
|
||||||
|
T_cid: "CID",
|
||||||
|
T_oidvector: "OIDVECTOR",
|
||||||
|
T_pg_ddl_command: "PG_DDL_COMMAND",
|
||||||
|
T_pg_type: "PG_TYPE",
|
||||||
|
T_pg_attribute: "PG_ATTRIBUTE",
|
||||||
|
T_pg_proc: "PG_PROC",
|
||||||
|
T_pg_class: "PG_CLASS",
|
||||||
|
T_json: "JSON",
|
||||||
|
T_xml: "XML",
|
||||||
|
T__xml: "_XML",
|
||||||
|
T_pg_node_tree: "PG_NODE_TREE",
|
||||||
|
T__json: "_JSON",
|
||||||
|
T_smgr: "SMGR",
|
||||||
|
T_index_am_handler: "INDEX_AM_HANDLER",
|
||||||
|
T_point: "POINT",
|
||||||
|
T_lseg: "LSEG",
|
||||||
|
T_path: "PATH",
|
||||||
|
T_box: "BOX",
|
||||||
|
T_polygon: "POLYGON",
|
||||||
|
T_line: "LINE",
|
||||||
|
T__line: "_LINE",
|
||||||
|
T_cidr: "CIDR",
|
||||||
|
T__cidr: "_CIDR",
|
||||||
|
T_float4: "FLOAT4",
|
||||||
|
T_float8: "FLOAT8",
|
||||||
|
T_abstime: "ABSTIME",
|
||||||
|
T_reltime: "RELTIME",
|
||||||
|
T_tinterval: "TINTERVAL",
|
||||||
|
T_unknown: "UNKNOWN",
|
||||||
|
T_circle: "CIRCLE",
|
||||||
|
T__circle: "_CIRCLE",
|
||||||
|
T_money: "MONEY",
|
||||||
|
T__money: "_MONEY",
|
||||||
|
T_macaddr: "MACADDR",
|
||||||
|
T_inet: "INET",
|
||||||
|
T__bool: "_BOOL",
|
||||||
|
T__bytea: "_BYTEA",
|
||||||
|
T__char: "_CHAR",
|
||||||
|
T__name: "_NAME",
|
||||||
|
T__int2: "_INT2",
|
||||||
|
T__int2vector: "_INT2VECTOR",
|
||||||
|
T__int4: "_INT4",
|
||||||
|
T__regproc: "_REGPROC",
|
||||||
|
T__text: "_TEXT",
|
||||||
|
T__tid: "_TID",
|
||||||
|
T__xid: "_XID",
|
||||||
|
T__cid: "_CID",
|
||||||
|
T__oidvector: "_OIDVECTOR",
|
||||||
|
T__bpchar: "_BPCHAR",
|
||||||
|
T__varchar: "_VARCHAR",
|
||||||
|
T__int8: "_INT8",
|
||||||
|
T__point: "_POINT",
|
||||||
|
T__lseg: "_LSEG",
|
||||||
|
T__path: "_PATH",
|
||||||
|
T__box: "_BOX",
|
||||||
|
T__float4: "_FLOAT4",
|
||||||
|
T__float8: "_FLOAT8",
|
||||||
|
T__abstime: "_ABSTIME",
|
||||||
|
T__reltime: "_RELTIME",
|
||||||
|
T__tinterval: "_TINTERVAL",
|
||||||
|
T__polygon: "_POLYGON",
|
||||||
|
T__oid: "_OID",
|
||||||
|
T_aclitem: "ACLITEM",
|
||||||
|
T__aclitem: "_ACLITEM",
|
||||||
|
T__macaddr: "_MACADDR",
|
||||||
|
T__inet: "_INET",
|
||||||
|
T_bpchar: "BPCHAR",
|
||||||
|
T_varchar: "VARCHAR",
|
||||||
|
T_date: "DATE",
|
||||||
|
T_time: "TIME",
|
||||||
|
T_timestamp: "TIMESTAMP",
|
||||||
|
T__timestamp: "_TIMESTAMP",
|
||||||
|
T__date: "_DATE",
|
||||||
|
T__time: "_TIME",
|
||||||
|
T_timestamptz: "TIMESTAMPTZ",
|
||||||
|
T__timestamptz: "_TIMESTAMPTZ",
|
||||||
|
T_interval: "INTERVAL",
|
||||||
|
T__interval: "_INTERVAL",
|
||||||
|
T__numeric: "_NUMERIC",
|
||||||
|
T_pg_database: "PG_DATABASE",
|
||||||
|
T__cstring: "_CSTRING",
|
||||||
|
T_timetz: "TIMETZ",
|
||||||
|
T__timetz: "_TIMETZ",
|
||||||
|
T_bit: "BIT",
|
||||||
|
T__bit: "_BIT",
|
||||||
|
T_varbit: "VARBIT",
|
||||||
|
T__varbit: "_VARBIT",
|
||||||
|
T_numeric: "NUMERIC",
|
||||||
|
T_refcursor: "REFCURSOR",
|
||||||
|
T__refcursor: "_REFCURSOR",
|
||||||
|
T_regprocedure: "REGPROCEDURE",
|
||||||
|
T_regoper: "REGOPER",
|
||||||
|
T_regoperator: "REGOPERATOR",
|
||||||
|
T_regclass: "REGCLASS",
|
||||||
|
T_regtype: "REGTYPE",
|
||||||
|
T__regprocedure: "_REGPROCEDURE",
|
||||||
|
T__regoper: "_REGOPER",
|
||||||
|
T__regoperator: "_REGOPERATOR",
|
||||||
|
T__regclass: "_REGCLASS",
|
||||||
|
T__regtype: "_REGTYPE",
|
||||||
|
T_record: "RECORD",
|
||||||
|
T_cstring: "CSTRING",
|
||||||
|
T_any: "ANY",
|
||||||
|
T_anyarray: "ANYARRAY",
|
||||||
|
T_void: "VOID",
|
||||||
|
T_trigger: "TRIGGER",
|
||||||
|
T_language_handler: "LANGUAGE_HANDLER",
|
||||||
|
T_internal: "INTERNAL",
|
||||||
|
T_opaque: "OPAQUE",
|
||||||
|
T_anyelement: "ANYELEMENT",
|
||||||
|
T__record: "_RECORD",
|
||||||
|
T_anynonarray: "ANYNONARRAY",
|
||||||
|
T_pg_authid: "PG_AUTHID",
|
||||||
|
T_pg_auth_members: "PG_AUTH_MEMBERS",
|
||||||
|
T__txid_snapshot: "_TXID_SNAPSHOT",
|
||||||
|
T_uuid: "UUID",
|
||||||
|
T__uuid: "_UUID",
|
||||||
|
T_txid_snapshot: "TXID_SNAPSHOT",
|
||||||
|
T_fdw_handler: "FDW_HANDLER",
|
||||||
|
T_pg_lsn: "PG_LSN",
|
||||||
|
T__pg_lsn: "_PG_LSN",
|
||||||
|
T_tsm_handler: "TSM_HANDLER",
|
||||||
|
T_anyenum: "ANYENUM",
|
||||||
|
T_tsvector: "TSVECTOR",
|
||||||
|
T_tsquery: "TSQUERY",
|
||||||
|
T_gtsvector: "GTSVECTOR",
|
||||||
|
T__tsvector: "_TSVECTOR",
|
||||||
|
T__gtsvector: "_GTSVECTOR",
|
||||||
|
T__tsquery: "_TSQUERY",
|
||||||
|
T_regconfig: "REGCONFIG",
|
||||||
|
T__regconfig: "_REGCONFIG",
|
||||||
|
T_regdictionary: "REGDICTIONARY",
|
||||||
|
T__regdictionary: "_REGDICTIONARY",
|
||||||
|
T_jsonb: "JSONB",
|
||||||
|
T__jsonb: "_JSONB",
|
||||||
|
T_anyrange: "ANYRANGE",
|
||||||
|
T_event_trigger: "EVENT_TRIGGER",
|
||||||
|
T_int4range: "INT4RANGE",
|
||||||
|
T__int4range: "_INT4RANGE",
|
||||||
|
T_numrange: "NUMRANGE",
|
||||||
|
T__numrange: "_NUMRANGE",
|
||||||
|
T_tsrange: "TSRANGE",
|
||||||
|
T__tsrange: "_TSRANGE",
|
||||||
|
T_tstzrange: "TSTZRANGE",
|
||||||
|
T__tstzrange: "_TSTZRANGE",
|
||||||
|
T_daterange: "DATERANGE",
|
||||||
|
T__daterange: "_DATERANGE",
|
||||||
|
T_int8range: "INT8RANGE",
|
||||||
|
T__int8range: "_INT8RANGE",
|
||||||
|
T_pg_shseclabel: "PG_SHSECLABEL",
|
||||||
|
T_regnamespace: "REGNAMESPACE",
|
||||||
|
T__regnamespace: "_REGNAMESPACE",
|
||||||
|
T_regrole: "REGROLE",
|
||||||
|
T__regrole: "_REGROLE",
|
||||||
|
}
|
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerSize = 4
|
||||||
|
|
||||||
|
type fieldDesc struct {
|
||||||
|
// The object ID of the data type.
|
||||||
|
OID oid.Oid
|
||||||
|
// The data type size (see pg_type.typlen).
|
||||||
|
// Note that negative values denote variable-width types.
|
||||||
|
Len int
|
||||||
|
// The type modifier (see pg_attribute.atttypmod).
|
||||||
|
// The meaning of the modifier is type-specific.
|
||||||
|
Mod int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Type() reflect.Type {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_int8:
|
||||||
|
return reflect.TypeOf(int64(0))
|
||||||
|
case oid.T_int4:
|
||||||
|
return reflect.TypeOf(int32(0))
|
||||||
|
case oid.T_int2:
|
||||||
|
return reflect.TypeOf(int16(0))
|
||||||
|
case oid.T_varchar, oid.T_text:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case oid.T_bool:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz:
|
||||||
|
return reflect.TypeOf(time.Time{})
|
||||||
|
case oid.T_bytea:
|
||||||
|
return reflect.TypeOf([]byte(nil))
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(new(interface{})).Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Name() string {
|
||||||
|
return oid.TypeName[fd.OID]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Length() (length int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_text, oid.T_bytea:
|
||||||
|
return math.MaxInt64, true
|
||||||
|
case oid.T_varchar, oid.T_bpchar:
|
||||||
|
return int64(fd.Mod - headerSize), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_numeric, oid.T__numeric:
|
||||||
|
mod := fd.Mod - headerSize
|
||||||
|
precision = int64((mod >> 16) & 0xffff)
|
||||||
|
scale = int64(mod & 0xffff)
|
||||||
|
return precision, scale, true
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeScanType returns the value type that can be used to scan types into.
|
||||||
|
func (rs *rows) ColumnTypeScanType(index int) reflect.Type {
|
||||||
|
return rs.colTyps[index].Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeDatabaseTypeName return the database system type name.
|
||||||
|
func (rs *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||||
|
return rs.colTyps[index].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeLength returns the length of the column type if the column is a
|
||||||
|
// variable length type. If the column is not a variable length type ok
|
||||||
|
// should return false.
|
||||||
|
func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) {
|
||||||
|
return rs.colTyps[index].Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypePrecisionScale should return the precision and scale for decimal
|
||||||
|
// types. If not applicable, ok should be false.
|
||||||
|
func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
|
||||||
|
return rs.colTyps[index].PrecisionScale()
|
||||||
|
}
|
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Package scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc5802
|
||||||
|
//
|
||||||
|
package scram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||||
|
//
|
||||||
|
// A Client may be used within a SASL conversation with logic resembling:
|
||||||
|
//
|
||||||
|
// var in []byte
|
||||||
|
// var client = scram.NewClient(sha1.New, user, pass)
|
||||||
|
// for client.Step(in) {
|
||||||
|
// out := client.Out()
|
||||||
|
// // send out to server
|
||||||
|
// in := serverOut
|
||||||
|
// }
|
||||||
|
// if client.Err() != nil {
|
||||||
|
// // auth failed
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Client struct {
|
||||||
|
newHash func() hash.Hash
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
step int
|
||||||
|
out bytes.Buffer
|
||||||
|
err error
|
||||||
|
|
||||||
|
clientNonce []byte
|
||||||
|
serverNonce []byte
|
||||||
|
saltedPass []byte
|
||||||
|
authMsg bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||||
|
//
|
||||||
|
// For SCRAM-SHA-256, for example, use:
|
||||||
|
//
|
||||||
|
// client := scram.NewClient(sha256.New, user, pass)
|
||||||
|
//
|
||||||
|
func NewClient(newHash func() hash.Hash, user, pass string) *Client {
|
||||||
|
c := &Client{
|
||||||
|
newHash: newHash,
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
}
|
||||||
|
c.out.Grow(256)
|
||||||
|
c.authMsg.Grow(256)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns the data to be sent to the server in the current step.
|
||||||
|
func (c *Client) Out() []byte {
|
||||||
|
if c.out.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error that occurred, or nil if there were no errors.
|
||||||
|
func (c *Client) Err() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the client nonce to the provided value.
|
||||||
|
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||||
|
func (c *Client) SetNonce(nonce []byte) {
|
||||||
|
c.clientNonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
|
||||||
|
|
||||||
|
// Step processes the incoming data from the server and makes the
|
||||||
|
// next round of data for the server available via Client.Out.
|
||||||
|
// Step returns false if there are no errors and more data is
|
||||||
|
// still expected.
|
||||||
|
func (c *Client) Step(in []byte) bool {
|
||||||
|
c.out.Reset()
|
||||||
|
if c.step > 2 || c.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.step++
|
||||||
|
switch c.step {
|
||||||
|
case 1:
|
||||||
|
c.err = c.step1(in)
|
||||||
|
case 2:
|
||||||
|
c.err = c.step2(in)
|
||||||
|
case 3:
|
||||||
|
c.err = c.step3(in)
|
||||||
|
}
|
||||||
|
return c.step > 2 || c.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step1(in []byte) error {
|
||||||
|
if len(c.clientNonce) == 0 {
|
||||||
|
const nonceLen = 16
|
||||||
|
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
|
||||||
|
if _, err := rand.Read(buf[:nonceLen]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err)
|
||||||
|
}
|
||||||
|
c.clientNonce = buf[nonceLen:]
|
||||||
|
b64.Encode(c.clientNonce, buf[:nonceLen])
|
||||||
|
}
|
||||||
|
c.authMsg.WriteString("n=")
|
||||||
|
escaper.WriteString(&c.authMsg, c.user)
|
||||||
|
c.authMsg.WriteString(",r=")
|
||||||
|
c.authMsg.Write(c.clientNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("n,,")
|
||||||
|
c.out.Write(c.authMsg.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func (c *Client) step2(in []byte) error {
|
||||||
|
c.authMsg.WriteByte(',')
|
||||||
|
c.authMsg.Write(in)
|
||||||
|
|
||||||
|
fields := bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) != 3 {
|
||||||
|
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
c.serverNonce = fields[0][2:]
|
||||||
|
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
|
||||||
|
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
|
||||||
|
n, err := b64.Decode(salt, fields[1][2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
|
||||||
|
}
|
||||||
|
salt = salt[:n]
|
||||||
|
iterCount, err := strconv.Atoi(string(fields[2][2:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
c.saltPassword(salt, iterCount)
|
||||||
|
|
||||||
|
c.authMsg.WriteString(",c=biws,r=")
|
||||||
|
c.authMsg.Write(c.serverNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("c=biws,r=")
|
||||||
|
c.out.Write(c.serverNonce)
|
||||||
|
c.out.WriteString(",p=")
|
||||||
|
c.out.Write(c.clientProof())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step3(in []byte) error {
|
||||||
|
var isv, ise bool
|
||||||
|
var fields = bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) == 1 {
|
||||||
|
isv = bytes.HasPrefix(fields[0], []byte("v="))
|
||||||
|
ise = bytes.HasPrefix(fields[0], []byte("e="))
|
||||||
|
}
|
||||||
|
if ise {
|
||||||
|
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
|
||||||
|
} else if !isv {
|
||||||
|
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
|
||||||
|
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) saltPassword(salt []byte, iterCount int) {
|
||||||
|
mac := hmac.New(c.newHash, []byte(c.pass))
|
||||||
|
mac.Write(salt)
|
||||||
|
mac.Write([]byte{0, 0, 0, 1})
|
||||||
|
ui := mac.Sum(nil)
|
||||||
|
hi := make([]byte, len(ui))
|
||||||
|
copy(hi, ui)
|
||||||
|
for i := 1; i < iterCount; i++ {
|
||||||
|
mac.Reset()
|
||||||
|
mac.Write(ui)
|
||||||
|
mac.Sum(ui[:0])
|
||||||
|
for j, b := range ui {
|
||||||
|
hi[j] ^= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.saltedPass = hi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientProof() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Client Key"))
|
||||||
|
clientKey := mac.Sum(nil)
|
||||||
|
hash := c.newHash()
|
||||||
|
hash.Write(clientKey)
|
||||||
|
storedKey := hash.Sum(nil)
|
||||||
|
mac = hmac.New(c.newHash, storedKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
clientProof := mac.Sum(nil)
|
||||||
|
for i, b := range clientKey {
|
||||||
|
clientProof[i] ^= b
|
||||||
|
}
|
||||||
|
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
|
||||||
|
b64.Encode(clientProof64, clientProof)
|
||||||
|
return clientProof64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) serverSignature() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Server Key"))
|
||||||
|
serverKey := mac.Sum(nil)
|
||||||
|
|
||||||
|
mac = hmac.New(c.newHash, serverKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
serverSignature := mac.Sum(nil)
|
||||||
|
|
||||||
|
encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
|
||||||
|
b64.Encode(encoded, serverSignature)
|
||||||
|
return encoded
|
||||||
|
}
|
193
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
193
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
|
||||||
|
// related settings. The function is nil when no upgrade should take place.
|
||||||
|
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
|
||||||
|
verifyCaOnly := false
|
||||||
|
tlsConf := tls.Config{}
|
||||||
|
switch mode := o["sslmode"]; mode {
|
||||||
|
// "require" is the default.
|
||||||
|
case "", "require":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
|
||||||
|
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
|
||||||
|
//
|
||||||
|
// Note: For backwards compatibility with earlier versions of
|
||||||
|
// PostgreSQL, if a root CA file exists, the behavior of
|
||||||
|
// sslmode=require will be the same as that of verify-ca, meaning the
|
||||||
|
// server certificate is validated against the CA. Relying on this
|
||||||
|
// behavior is discouraged, and applications that need certificate
|
||||||
|
// validation should always use verify-ca or verify-full.
|
||||||
|
if sslrootcert, ok := o["sslrootcert"]; ok {
|
||||||
|
if _, err := os.Stat(sslrootcert); err == nil {
|
||||||
|
verifyCaOnly = true
|
||||||
|
} else {
|
||||||
|
delete(o, "sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "verify-ca":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
verifyCaOnly = true
|
||||||
|
case "verify-full":
|
||||||
|
tlsConf.ServerName = o["host"]
|
||||||
|
case "disable":
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sslClientCertificates(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = sslCertificateAuthority(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||||
|
// the default configuration of older versions has it enabled. Redshift
|
||||||
|
// also initiates renegotiations and cannot be reconfigured.
|
||||||
|
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
|
||||||
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
|
client := tls.Client(conn, &tlsConf)
|
||||||
|
if verifyCaOnly {
|
||||||
|
err := sslVerifyCertificateAuthority(client, &tlsConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslClientCertificates adds the certificate specified in the "sslcert" and
|
||||||
|
// "sslkey" settings, or if they aren't set, from the .postgresql directory
|
||||||
|
// in the user's home directory. The configured files must exist and have
|
||||||
|
// the correct permissions.
|
||||||
|
func sslClientCertificates(tlsConf *tls.Config, o values) error {
|
||||||
|
sslinline := o["sslinline"]
|
||||||
|
if sslinline == "true" {
|
||||||
|
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// user.Current() might fail when cross-compiling. We have to ignore the
|
||||||
|
// error and continue without home directory defaults, since we wouldn't
|
||||||
|
// know from where to load them.
|
||||||
|
user, _ := user.Current()
|
||||||
|
|
||||||
|
// In libpq, the client certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
|
||||||
|
sslcert := o["sslcert"]
|
||||||
|
if len(sslcert) == 0 && user != nil {
|
||||||
|
sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045
|
||||||
|
if len(sslcert) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054
|
||||||
|
if _, err := os.Stat(sslcert); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In libpq, the ssl key is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222
|
||||||
|
sslkey := o["sslkey"]
|
||||||
|
if len(sslkey) == 0 && user != nil {
|
||||||
|
sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sslkey) > 0 {
|
||||||
|
if err := sslKeyPermissions(sslkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
|
||||||
|
func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
|
||||||
|
// In libpq, the root certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
|
||||||
|
if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 {
|
||||||
|
tlsConf.RootCAs = x509.NewCertPool()
|
||||||
|
|
||||||
|
sslinline := o["sslinline"]
|
||||||
|
|
||||||
|
var cert []byte
|
||||||
|
if sslinline == "true" {
|
||||||
|
cert = []byte(sslrootcert)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
cert, err = ioutil.ReadFile(sslrootcert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
|
||||||
|
return fmterrorf("couldn't parse pem in sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
|
||||||
|
// verifies the presented certificate against the CA, i.e. the one specified in
|
||||||
|
// sslrootcert or the system CA if sslrootcert was not specified.
|
||||||
|
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certs := client.ConnectionState().PeerCertificates
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: client.ConnectionState().ServerName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
Roots: tlsConf.RootCAs,
|
||||||
|
}
|
||||||
|
for i, cert := range certs {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
_, err = certs[0].Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
20
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
20
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(sslkey string) error {
|
||||||
|
info, err := os.Stat(sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Mode().Perm()&0077 != 0 {
|
||||||
|
return ErrSSLKeyHasWorldPermissions
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
9
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
9
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(string) error { return nil }
|
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
nurl "net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
|
||||||
|
// connection string to sql.Open() is now supported:
|
||||||
|
//
|
||||||
|
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
//
|
||||||
|
// It remains exported here for backwards-compatibility.
|
||||||
|
//
|
||||||
|
// ParseURL converts a url to a connection string for driver.Open.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// converts to:
|
||||||
|
//
|
||||||
|
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// A minimal example:
|
||||||
|
//
|
||||||
|
// "postgres://"
|
||||||
|
//
|
||||||
|
// This will be blank, causing driver.Open to use all of the defaults
|
||||||
|
func ParseURL(url string) (string, error) {
|
||||||
|
u, err := nurl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||||
|
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvs []string
|
||||||
|
escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`)
|
||||||
|
accrue := func(k, v string) {
|
||||||
|
if v != "" {
|
||||||
|
kvs = append(kvs, k+"='"+escaper.Replace(v)+"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
v := u.User.Username()
|
||||||
|
accrue("user", v)
|
||||||
|
|
||||||
|
v, _ = u.User.Password()
|
||||||
|
accrue("password", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
accrue("host", u.Host)
|
||||||
|
} else {
|
||||||
|
accrue("host", host)
|
||||||
|
accrue("port", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Path != "" {
|
||||||
|
accrue("dbname", u.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
for k := range q {
|
||||||
|
accrue(k, q.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(kvs) // Makes testing easier (not a performance concern)
|
||||||
|
return strings.Join(kvs, " "), nil
|
||||||
|
}
|
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris rumprun
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return u.Username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := os.Getenv("USER")
|
||||||
|
if name != "" {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform Windows user name lookup identically to libpq.
|
||||||
|
//
|
||||||
|
// The PostgreSQL code makes use of the legacy Win32 function
|
||||||
|
// GetUserName, and that function has not been imported into stock Go.
|
||||||
|
// GetUserNameEx is available though, the difference being that a
|
||||||
|
// wider range of names are available. To get the output to be the
|
||||||
|
// same as GetUserName, only the base (or last) component of the
|
||||||
|
// result is returned.
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
pw_name := make([]uint16, 128)
|
||||||
|
pwname_size := uint32(len(pw_name)) - 1
|
||||||
|
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
||||||
|
s := syscall.UTF16ToString(pw_name)
|
||||||
|
u := filepath.Base(s)
|
||||||
|
return u, nil
|
||||||
|
}
|
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format.
|
||||||
|
func decodeUUIDBinary(src []byte) ([]byte, error) {
|
||||||
|
if len(src) != 16 {
|
||||||
|
return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, 36)
|
||||||
|
dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-'
|
||||||
|
hex.Encode(dst[0:], src[0:4])
|
||||||
|
hex.Encode(dst[9:], src[4:6])
|
||||||
|
hex.Encode(dst[14:], src[6:8])
|
||||||
|
hex.Encode(dst[19:], src[8:10])
|
||||||
|
hex.Encode(dst[24:], src[10:16])
|
||||||
|
|
||||||
|
return dst, nil
|
||||||
|
}
|
31
vendor/github.com/sideshow/apns2/.gitignore
generated
vendored
Normal file
31
vendor/github.com/sideshow/apns2/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
/*.p12
|
||||||
|
/*.pem
|
||||||
|
/*.cer
|
||||||
|
/*.p8
|
||||||
|
|
||||||
|
.DS_Store
|
29
vendor/github.com/sideshow/apns2/.travis.yml
generated
vendored
Normal file
29
vendor/github.com/sideshow/apns2/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get gopkg.in/alecthomas/kingpin.v2
|
||||||
|
- go get github.com/axw/gocov/gocov
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get github.com/stretchr/testify/assert
|
||||||
|
- go get github.com/kardianos/govendor
|
||||||
|
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||||
|
|
||||||
|
install:
|
||||||
|
- $HOME/gopath/bin/govendor sync
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/govendor test -race -v +local
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
22
vendor/github.com/sideshow/apns2/LICENSE
generated
vendored
Normal file
22
vendor/github.com/sideshow/apns2/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Adam Jones
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
210
vendor/github.com/sideshow/apns2/README.md
generated
vendored
Normal file
210
vendor/github.com/sideshow/apns2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
# APNS/2
|
||||||
|
|
||||||
|
APNS/2 is a go package designed for simple, flexible and fast Apple Push Notifications on iOS, OSX and Safari using the new HTTP/2 Push provider API.
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/sideshow/apns2.svg?branch=master)](https://travis-ci.org/sideshow/apns2) [![Coverage Status](https://coveralls.io/repos/sideshow/apns2/badge.svg?branch=master&service=github)](https://coveralls.io/github/sideshow/apns2?branch=master) [![GoDoc](https://godoc.org/github.com/sideshow/apns2?status.svg)](https://godoc.org/github.com/sideshow/apns2)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Uses new Apple APNs HTTP/2 connection
|
||||||
|
- Fast - See [notes on speed](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed)
|
||||||
|
- Works with go 1.7 and later
|
||||||
|
- Supports new Apple Token Based Authentication (JWT)
|
||||||
|
- Supports new iOS 10 features such as Collapse IDs, Subtitles and Mutable Notifications
|
||||||
|
- Supports persistent connections to APNs
|
||||||
|
- Supports VoIP/PushKit notifications (iOS 8 and later)
|
||||||
|
- Modular & easy to use
|
||||||
|
- Tested and working in APNs production environment
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
- Make sure you have [Go](https://golang.org/doc/install) installed and have set your [GOPATH](https://golang.org/doc/code.html#GOPATH).
|
||||||
|
- Install apns2:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/sideshow/apns2
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are running the test suite you will also need to install testify:
|
||||||
|
```sh
|
||||||
|
go get -u github.com/stretchr/testify
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sideshow/apns2"
|
||||||
|
"github.com/sideshow/apns2/certificate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
cert, err := certificate.FromP12File("../cert.p12", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Cert Error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &apns2.Notification{}
|
||||||
|
notification.DeviceToken = "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"
|
||||||
|
notification.Topic = "com.sideshow.Apns2"
|
||||||
|
notification.Payload = []byte(`{"aps":{"alert":"Hello!"}}`) // See Payload section below
|
||||||
|
|
||||||
|
client := apns2.NewClient(cert).Production()
|
||||||
|
res, err := client.Push(notification)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## JWT Token Example
|
||||||
|
|
||||||
|
Instead of using a `.p12` or `.pem` certificate as above, you can optionally use
|
||||||
|
APNs JWT _Provider Authentication Tokens_. First you will need a signing key (`.p8` file), Key ID and Team ID [from Apple](http://help.apple.com/xcode/mac/current/#/dev54d690a66). Once you have these details, you can create a new client:
|
||||||
|
|
||||||
|
```go
|
||||||
|
authKey, err := token.AuthKeyFromFile("../AuthKey_XXX.p8")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("token error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &token.Token{
|
||||||
|
AuthKey: authKey,
|
||||||
|
// KeyID from developer account (Certificates, Identifiers & Profiles -> Keys)
|
||||||
|
KeyID: "ABC123DEFG",
|
||||||
|
// TeamID from developer account (View Account -> Membership)
|
||||||
|
TeamID: "DEF123GHIJ",
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
client := apns2.NewTokenClient(token)
|
||||||
|
res, err := client.Push(notification)
|
||||||
|
```
|
||||||
|
|
||||||
|
- You can use one APNs signing key to authenticate tokens for multiple apps.
|
||||||
|
- A signing key works for both the development and production environments.
|
||||||
|
- A signing key doesn’t expire but can be revoked.
|
||||||
|
|
||||||
|
## Notification
|
||||||
|
|
||||||
|
At a minimum, a _Notification_ needs a _DeviceToken_, a _Topic_ and a _Payload_.
|
||||||
|
|
||||||
|
```go
|
||||||
|
notification := &apns2.Notification{
|
||||||
|
DeviceToken: "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7",
|
||||||
|
Topic: "com.sideshow.Apns2",
|
||||||
|
Payload: []byte(`{"aps":{"alert":"Hello!"}}`),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also set an optional _ApnsID_, _Expiration_ or _Priority_.
|
||||||
|
|
||||||
|
```go
|
||||||
|
notification.ApnsID = "40636A2C-C093-493E-936A-2A4333C06DEA"
|
||||||
|
notification.Expiration = time.Now()
|
||||||
|
notification.Priority = apns2.PriorityLow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Payload
|
||||||
|
|
||||||
|
You can use raw bytes for the `notification.Payload` as above, or you can use the payload builder package which makes it easy to construct APNs payloads.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// {"aps":{"alert":"hello","badge":1},"key":"val"}
|
||||||
|
|
||||||
|
payload := payload.NewPayload().Alert("hello").Badge(1).Custom("key", "val")
|
||||||
|
|
||||||
|
notification.Payload = payload
|
||||||
|
client.Push(notification)
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to the [payload](https://godoc.org/github.com/sideshow/apns2/payload) docs for more info.
|
||||||
|
|
||||||
|
## Response, Error handling
|
||||||
|
|
||||||
|
APNS/2 draws the distinction between a valid response from Apple indicating whether or not the _Notification_ was sent or not, and an unrecoverable or unexpected _Error_;
|
||||||
|
|
||||||
|
- An `Error` is returned if a non-recoverable error occurs, i.e. if there is a problem with the underlying _http.Client_ connection or _Certificate_, the payload was not sent, or a valid _Response_ was not received.
|
||||||
|
- A `Response` is returned if the payload was successfully sent to Apple and a documented response was received. This struct will contain more information about whether or not the push notification succeeded, its _apns-id_ and if applicable, more information around why it did not succeed.
|
||||||
|
|
||||||
|
To check if a `Notification` was successfully sent;
|
||||||
|
|
||||||
|
```go
|
||||||
|
res, err := client.Push(notification)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("There was an error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Sent() {
|
||||||
|
log.Println("Sent:", res.ApnsID)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Not Sent: %v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context & Timeouts
|
||||||
|
|
||||||
|
For better control over request cancellations and timeouts APNS/2 supports
|
||||||
|
contexts. Using a context can be helpful if you want to cancel all pushes when
|
||||||
|
the parent process is cancelled, or need finer grained control over individual
|
||||||
|
push timeouts. See the [Google post](https://blog.golang.org/context) for more
|
||||||
|
information on contexts.
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
|
||||||
|
res, err := client.PushWithContext(ctx, notification)
|
||||||
|
defer cancel()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Speed & Performance
|
||||||
|
|
||||||
|
Also see the wiki page on [APNS HTTP 2 Push Speed](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed).
|
||||||
|
|
||||||
|
For best performance, you should hold on to an `apns2.Client` instance and not re-create it every push. The underlying TLS connection itself can take a few seconds to connect and negotiate, so if you are setting up an `apns2.Client` and tearing it down every push, then this will greatly affect performance. (Apple suggest keeping the connection open all the time).
|
||||||
|
|
||||||
|
You should also limit the amount of `apns2.Client` instances. The underlying transport has a http connection pool itself, so a single client instance will be enough for most users (One instance can potentially do 4,000+ pushes per second). If you need more than this then one instance per CPU core is a good starting point.
|
||||||
|
|
||||||
|
Speed is greatly affected by the location of your server and the quality of your network connection. If you're just testing locally, behind a proxy or if your server is outside USA then you're not going to get great performance. With a good server located in AWS, you should be able to get [decent throughput](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed).
|
||||||
|
|
||||||
|
## Command line tool
|
||||||
|
|
||||||
|
APNS/2 has a command line tool that can be installed with `go get github.com/sideshow/apns2/apns2`. Usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
apns2 --help
|
||||||
|
usage: apns2 --certificate-path=CERTIFICATE-PATH --topic=TOPIC [<flags>]
|
||||||
|
|
||||||
|
Listens to STDIN to send notifications and writes APNS response code and reason to STDOUT.
|
||||||
|
|
||||||
|
The expected format is: <DeviceToken> <APNS Payload>
|
||||||
|
Example: aff0c63d9eaa63ad161bafee732d5bc2c31f66d552054718ff19ce314371e5d0 {"aps": {"alert": "hi"}}
|
||||||
|
Flags:
|
||||||
|
--help Show context-sensitive help (also try --help-long and --help-man).
|
||||||
|
-c, --certificate-path=CERTIFICATE-PATH
|
||||||
|
Path to certificate file.
|
||||||
|
-t, --topic=TOPIC The topic of the remote notification, which is typically the bundle ID for your app
|
||||||
|
-m, --mode="production" APNS server to send notifications to. `production` or `development`. Defaults to `production`
|
||||||
|
--version Show application version.
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Adam Jones
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
220
vendor/github.com/sideshow/apns2/client.go
generated
vendored
Normal file
220
vendor/github.com/sideshow/apns2/client.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
// Package apns2 is a go Apple Push Notification Service (APNs) provider that
|
||||||
|
// allows you to send remote notifications to your iOS, tvOS, and OS X
|
||||||
|
// apps, using the new APNs HTTP/2 network protocol.
|
||||||
|
package apns2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sideshow/apns2/token"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apple HTTP/2 Development & Production urls
|
||||||
|
const (
|
||||||
|
HostDevelopment = "https://api.sandbox.push.apple.com"
|
||||||
|
HostProduction = "https://api.push.apple.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultHost is a mutable var for testing purposes
|
||||||
|
var DefaultHost = HostDevelopment
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TLSDialTimeout is the maximum amount of time a dial will wait for a connect
|
||||||
|
// to complete.
|
||||||
|
TLSDialTimeout = 20 * time.Second
|
||||||
|
// HTTPClientTimeout specifies a time limit for requests made by the
|
||||||
|
// HTTPClient. The timeout includes connection time, any redirects,
|
||||||
|
// and reading the response body.
|
||||||
|
HTTPClientTimeout = 60 * time.Second
|
||||||
|
// TCPKeepAlive specifies the keep-alive period for an active network
|
||||||
|
// connection. If zero, keep-alives are not enabled.
|
||||||
|
TCPKeepAlive = 60 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialTLS is the default dial function for creating TLS connections for
|
||||||
|
// non-proxied HTTPS requests.
|
||||||
|
var DialTLS = func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Timeout: TLSDialTimeout,
|
||||||
|
KeepAlive: TCPKeepAlive,
|
||||||
|
}
|
||||||
|
return tls.DialWithDialer(dialer, network, addr, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents a connection with the APNs
|
||||||
|
type Client struct {
|
||||||
|
Host string
|
||||||
|
Certificate tls.Certificate
|
||||||
|
Token *token.Token
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type connectionCloser interface {
|
||||||
|
CloseIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new Client with an underlying http.Client configured with
|
||||||
|
// the correct APNs HTTP/2 transport settings. It does not connect to the APNs
|
||||||
|
// until the first Notification is sent via the Push method.
|
||||||
|
//
|
||||||
|
// As per the Apple APNs Provider API, you should keep a handle on this client
|
||||||
|
// so that you can keep your connections with APNs open across multiple
|
||||||
|
// notifications; don’t repeatedly open and close connections. APNs treats rapid
|
||||||
|
// connection and disconnection as a denial-of-service attack.
|
||||||
|
//
|
||||||
|
// If your use case involves multiple long-lived connections, consider using
|
||||||
|
// the ClientManager, which manages clients for you.
|
||||||
|
func NewClient(certificate tls.Certificate) *Client {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{certificate},
|
||||||
|
}
|
||||||
|
if len(certificate.Certificate) > 0 {
|
||||||
|
tlsConfig.BuildNameToCertificate()
|
||||||
|
}
|
||||||
|
transport := &http2.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
DialTLS: DialTLS,
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: HTTPClientTimeout,
|
||||||
|
},
|
||||||
|
Certificate: certificate,
|
||||||
|
Host: DefaultHost,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenClient returns a new Client with an underlying http.Client configured
|
||||||
|
// with the correct APNs HTTP/2 transport settings. It does not connect to the APNs
|
||||||
|
// until the first Notification is sent via the Push method.
|
||||||
|
//
|
||||||
|
// As per the Apple APNs Provider API, you should keep a handle on this client
|
||||||
|
// so that you can keep your connections with APNs open across multiple
|
||||||
|
// notifications; don’t repeatedly open and close connections. APNs treats rapid
|
||||||
|
// connection and disconnection as a denial-of-service attack.
|
||||||
|
func NewTokenClient(token *token.Token) *Client {
|
||||||
|
transport := &http2.Transport{
|
||||||
|
DialTLS: DialTLS,
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
Token: token,
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: HTTPClientTimeout,
|
||||||
|
},
|
||||||
|
Host: DefaultHost,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Development sets the Client to use the APNs development push endpoint.
|
||||||
|
func (c *Client) Development() *Client {
|
||||||
|
c.Host = HostDevelopment
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Production sets the Client to use the APNs production push endpoint.
|
||||||
|
func (c *Client) Production() *Client {
|
||||||
|
c.Host = HostProduction
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push sends a Notification to the APNs gateway. If the underlying http.Client
|
||||||
|
// is not currently connected, this method will attempt to reconnect
|
||||||
|
// transparently before sending the notification. It will return a Response
|
||||||
|
// indicating whether the notification was accepted or rejected by the APNs
|
||||||
|
// gateway, or an error if something goes wrong.
|
||||||
|
//
|
||||||
|
// Use PushWithContext if you need better cancellation and timeout control.
|
||||||
|
func (c *Client) Push(n *Notification) (*Response, error) {
|
||||||
|
return c.PushWithContext(nil, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushWithContext sends a Notification to the APNs gateway. Context carries a
|
||||||
|
// deadline and a cancellation signal and allows you to close long running
|
||||||
|
// requests when the context timeout is exceeded. Context can be nil, for
|
||||||
|
// backwards compatibility.
|
||||||
|
//
|
||||||
|
// If the underlying http.Client is not currently connected, this method will
|
||||||
|
// attempt to reconnect transparently before sending the notification. It will
|
||||||
|
// return a Response indicating whether the notification was accepted or
|
||||||
|
// rejected by the APNs gateway, or an error if something goes wrong.
|
||||||
|
func (c *Client) PushWithContext(ctx Context, n *Notification) (*Response, error) {
|
||||||
|
payload, err := json.Marshal(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%v/3/device/%v", c.Host, n.DeviceToken)
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Token != nil {
|
||||||
|
c.setTokenHeader(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeaders(req, n)
|
||||||
|
|
||||||
|
httpRes, err := c.requestWithContext(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer httpRes.Body.Close()
|
||||||
|
|
||||||
|
response := &Response{}
|
||||||
|
response.StatusCode = httpRes.StatusCode
|
||||||
|
response.ApnsID = httpRes.Header.Get("apns-id")
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(httpRes.Body)
|
||||||
|
if err := decoder.Decode(&response); err != nil && err != io.EOF {
|
||||||
|
return &Response{}, err
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseIdleConnections closes any underlying connections which were previously
|
||||||
|
// connected from previous requests but are now sitting idle. It will not
|
||||||
|
// interrupt any connections currently in use.
|
||||||
|
func (c *Client) CloseIdleConnections() {
|
||||||
|
c.HTTPClient.Transport.(connectionCloser).CloseIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setTokenHeader(r *http.Request) {
|
||||||
|
c.Token.GenerateIfExpired()
|
||||||
|
r.Header.Set("authorization", fmt.Sprintf("bearer %v", c.Token.Bearer))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setHeaders(r *http.Request, n *Notification) {
|
||||||
|
r.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if n.Topic != "" {
|
||||||
|
r.Header.Set("apns-topic", n.Topic)
|
||||||
|
}
|
||||||
|
if n.ApnsID != "" {
|
||||||
|
r.Header.Set("apns-id", n.ApnsID)
|
||||||
|
}
|
||||||
|
if n.CollapseID != "" {
|
||||||
|
r.Header.Set("apns-collapse-id", n.CollapseID)
|
||||||
|
}
|
||||||
|
if n.Priority > 0 {
|
||||||
|
r.Header.Set("apns-priority", fmt.Sprintf("%v", n.Priority))
|
||||||
|
}
|
||||||
|
if !n.Expiration.IsZero() {
|
||||||
|
r.Header.Set("apns-expiration", fmt.Sprintf("%v", n.Expiration.Unix()))
|
||||||
|
}
|
||||||
|
if n.PushType != "" {
|
||||||
|
r.Header.Set("apns-push-type", string(n.PushType))
|
||||||
|
} else {
|
||||||
|
r.Header.Set("apns-push-type", string(PushTypeAlert))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
vendor/github.com/sideshow/apns2/client_go17.go
generated
vendored
Normal file
23
vendor/github.com/sideshow/apns2/client_go17.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package apns2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancellation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context interface {
|
||||||
|
context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) requestWithContext(ctx Context, req *http.Request) (*http.Response, error) {
|
||||||
|
if ctx != nil {
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
}
|
||||||
|
return c.HTTPClient.Do(req)
|
||||||
|
}
|
162
vendor/github.com/sideshow/apns2/client_manager.go
generated
vendored
Normal file
162
vendor/github.com/sideshow/apns2/client_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package apns2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/tls"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type managerItem struct {
|
||||||
|
key [sha1.Size]byte
|
||||||
|
client *Client
|
||||||
|
lastUsed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientManager is a way to manage multiple connections to the APNs.
|
||||||
|
type ClientManager struct {
|
||||||
|
// MaxSize is the maximum number of clients allowed in the manager. When
|
||||||
|
// this limit is reached, the least recently used client is evicted. Set
|
||||||
|
// zero for no limit.
|
||||||
|
MaxSize int
|
||||||
|
|
||||||
|
// MaxAge is the maximum age of clients in the manager. Upon retrieval, if
|
||||||
|
// a client has remained unused in the manager for this duration or longer,
|
||||||
|
// it is evicted and nil is returned. Set zero to disable this
|
||||||
|
// functionality.
|
||||||
|
MaxAge time.Duration
|
||||||
|
|
||||||
|
// Factory is the function which constructs clients if not found in the
|
||||||
|
// manager.
|
||||||
|
Factory func(certificate tls.Certificate) *Client
|
||||||
|
|
||||||
|
cache map[[sha1.Size]byte]*list.Element
|
||||||
|
ll *list.List
|
||||||
|
mu sync.Mutex
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientManager returns a new ClientManager for prolonged, concurrent usage
|
||||||
|
// of multiple APNs clients. ClientManager is flexible enough to work best for
|
||||||
|
// your use case. When a client is not found in the manager, Get will return
|
||||||
|
// the result of calling Factory, which can be a Client or nil.
|
||||||
|
//
|
||||||
|
// Having multiple clients per certificate in the manager is not allowed.
|
||||||
|
//
|
||||||
|
// By default, MaxSize is 64, MaxAge is 10 minutes, and Factory always returns
|
||||||
|
// a Client with default options.
|
||||||
|
func NewClientManager() *ClientManager {
|
||||||
|
manager := &ClientManager{
|
||||||
|
MaxSize: 64,
|
||||||
|
MaxAge: 10 * time.Minute,
|
||||||
|
Factory: NewClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.initInternals()
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a Client to the manager. You can use this to individually configure
|
||||||
|
// Clients in the manager.
|
||||||
|
func (m *ClientManager) Add(client *Client) {
|
||||||
|
m.initInternals()
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
key := cacheKey(client.Certificate)
|
||||||
|
now := time.Now()
|
||||||
|
if ele, hit := m.cache[key]; hit {
|
||||||
|
item := ele.Value.(*managerItem)
|
||||||
|
item.client = client
|
||||||
|
item.lastUsed = now
|
||||||
|
m.ll.MoveToFront(ele)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ele := m.ll.PushFront(&managerItem{key, client, now})
|
||||||
|
m.cache[key] = ele
|
||||||
|
if m.MaxSize != 0 && m.ll.Len() > m.MaxSize {
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.removeOldest()
|
||||||
|
m.mu.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a Client from the manager. If a Client is not found in the manager
|
||||||
|
// or if a Client has remained in the manager longer than MaxAge, Get will call
|
||||||
|
// the ClientManager's Factory function, store the result in the manager if
|
||||||
|
// non-nil, and return it.
|
||||||
|
func (m *ClientManager) Get(certificate tls.Certificate) *Client {
|
||||||
|
m.initInternals()
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
key := cacheKey(certificate)
|
||||||
|
now := time.Now()
|
||||||
|
if ele, hit := m.cache[key]; hit {
|
||||||
|
item := ele.Value.(*managerItem)
|
||||||
|
if m.MaxAge != 0 && item.lastUsed.Before(now.Add(-m.MaxAge)) {
|
||||||
|
c := m.Factory(certificate)
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
item.client = c
|
||||||
|
}
|
||||||
|
item.lastUsed = now
|
||||||
|
m.ll.MoveToFront(ele)
|
||||||
|
return item.client
|
||||||
|
}
|
||||||
|
|
||||||
|
c := m.Factory(certificate)
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.Add(c)
|
||||||
|
m.mu.Lock()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the current size of the ClientManager.
|
||||||
|
func (m *ClientManager) Len() int {
|
||||||
|
if m.cache == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return m.ll.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientManager) initInternals() {
|
||||||
|
m.once.Do(func() {
|
||||||
|
m.cache = map[[sha1.Size]byte]*list.Element{}
|
||||||
|
m.ll = list.New()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientManager) removeOldest() {
|
||||||
|
m.mu.Lock()
|
||||||
|
ele := m.ll.Back()
|
||||||
|
m.mu.Unlock()
|
||||||
|
if ele != nil {
|
||||||
|
m.removeElement(ele)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientManager) removeElement(e *list.Element) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.ll.Remove(e)
|
||||||
|
delete(m.cache, e.Value.(*managerItem).key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheKey(certificate tls.Certificate) [sha1.Size]byte {
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
for _, cert := range certificate.Certificate {
|
||||||
|
data = append(data, cert...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sha1.Sum(data)
|
||||||
|
}
|
137
vendor/github.com/sideshow/apns2/notification.go
generated
vendored
Normal file
137
vendor/github.com/sideshow/apns2/notification.go
generated
vendored
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package apns2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EPushType defines the value for the apns-push-type header
|
||||||
|
type EPushType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PushTypeAlert is used for notifications that trigger a user interaction —
|
||||||
|
// for example, an alert, badge, or sound. If you set this push type, the
|
||||||
|
// apns-topic header field must use your app’s bundle ID as the topic. The
|
||||||
|
// alert push type is required on watchOS 6 and later. It is recommended on
|
||||||
|
// macOS, iOS, tvOS, and iPadOS.
|
||||||
|
PushTypeAlert EPushType = "alert"
|
||||||
|
|
||||||
|
// PushTypeBackground is used for notifications that deliver content in the
|
||||||
|
// background, and don’t trigger any user interactions. If you set this push
|
||||||
|
// type, the apns-topic header field must use your app’s bundle ID as the
|
||||||
|
// topic. The background push type is required on watchOS 6 and later. It is
|
||||||
|
// recommended on macOS, iOS, tvOS, and iPadOS.
|
||||||
|
PushTypeBackground EPushType = "background"
|
||||||
|
|
||||||
|
// PushTypeVOIP is used for notifications that provide information about an
|
||||||
|
// incoming Voice-over-IP (VoIP) call. If you set this push type, the
|
||||||
|
// apns-topic header field must use your app’s bundle ID with .voip appended
|
||||||
|
// to the end. If you’re using certificate-based authentication, you must
|
||||||
|
// also register the certificate for VoIP services. The voip push type is
|
||||||
|
// not available on watchOS. It is recommended on macOS, iOS, tvOS, and
|
||||||
|
// iPadOS.
|
||||||
|
PushTypeVOIP EPushType = "voip"
|
||||||
|
|
||||||
|
// PushTypeComplication is used for notifications that contain update
|
||||||
|
// information for a watchOS app’s complications. If you set this push type,
|
||||||
|
// the apns-topic header field must use your app’s bundle ID with
|
||||||
|
// .complication appended to the end. If you’re using certificate-based
|
||||||
|
// authentication, you must also register the certificate for WatchKit
|
||||||
|
// services. The complication push type is recommended for watchOS and iOS.
|
||||||
|
// It is not available on macOS, tvOS, and iPadOS.
|
||||||
|
PushTypeComplication EPushType = "complication"
|
||||||
|
|
||||||
|
// PushTypeFileProvider is used to signal changes to a File Provider
|
||||||
|
// extension. If you set this push type, the apns-topic header field must
|
||||||
|
// use your app’s bundle ID with .pushkit.fileprovider appended to the end.
|
||||||
|
// The fileprovider push type is not available on watchOS. It is recommended
|
||||||
|
// on macOS, iOS, tvOS, and iPadOS.
|
||||||
|
PushTypeFileProvider EPushType = "fileprovider"
|
||||||
|
|
||||||
|
// PushTypeMDM is used for notifications that tell managed devices to
|
||||||
|
// contact the MDM server. If you set this push type, you must use the topic
|
||||||
|
// from the UID attribute in the subject of your MDM push certificate.
|
||||||
|
PushTypeMDM EPushType = "mdm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PriorityLow will tell APNs to send the push message at a time that takes
|
||||||
|
// into account power considerations for the device. Notifications with this
|
||||||
|
// priority might be grouped and delivered in bursts. They are throttled,
|
||||||
|
// and in some cases are not delivered.
|
||||||
|
PriorityLow = 5
|
||||||
|
|
||||||
|
// PriorityHigh will tell APNs to send the push message immediately.
|
||||||
|
// Notifications with this priority must trigger an alert, sound, or badge
|
||||||
|
// on the target device. It is an error to use this priority for a push
|
||||||
|
// notification that contains only the content-available key.
|
||||||
|
PriorityHigh = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notification represents the the data and metadata for a APNs Remote Notification.
|
||||||
|
type Notification struct {
|
||||||
|
|
||||||
|
// An optional canonical UUID that identifies the notification. The
|
||||||
|
// canonical form is 32 lowercase hexadecimal digits, displayed in five
|
||||||
|
// groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as
|
||||||
|
// follows:
|
||||||
|
//
|
||||||
|
// 123e4567-e89b-12d3-a456-42665544000
|
||||||
|
//
|
||||||
|
// If you don't set this, a new UUID is created by APNs and returned in the
|
||||||
|
// response.
|
||||||
|
ApnsID string
|
||||||
|
|
||||||
|
// A string which allows multiple notifications with the same collapse
|
||||||
|
// identifier to be displayed to the user as a single notification. The
|
||||||
|
// value should not exceed 64 bytes.
|
||||||
|
CollapseID string
|
||||||
|
|
||||||
|
// A string containing hexadecimal bytes of the device token for the target
|
||||||
|
// device.
|
||||||
|
DeviceToken string
|
||||||
|
|
||||||
|
// The topic of the remote notification, which is typically the bundle ID
|
||||||
|
// for your app. The certificate you create in the Apple Developer Member
|
||||||
|
// Center must include the capability for this topic. If your certificate
|
||||||
|
// includes multiple topics, you must specify a value for this header. If
|
||||||
|
// you omit this header and your APNs certificate does not specify multiple
|
||||||
|
// topics, the APNs server uses the certificate’s Subject as the default
|
||||||
|
// topic.
|
||||||
|
Topic string
|
||||||
|
|
||||||
|
// An optional time at which the notification is no longer valid and can be
|
||||||
|
// discarded by APNs. If this value is in the past, APNs treats the
|
||||||
|
// notification as if it expires immediately and does not store the
|
||||||
|
// notification or attempt to redeliver it. If this value is left as the
|
||||||
|
// default (ie, Expiration.IsZero()) an expiration header will not added to
|
||||||
|
// the http request.
|
||||||
|
Expiration time.Time
|
||||||
|
|
||||||
|
// The priority of the notification. Specify ether apns.PriorityHigh (10) or
|
||||||
|
// apns.PriorityLow (5) If you don't set this, the APNs server will set the
|
||||||
|
// priority to 10.
|
||||||
|
Priority int
|
||||||
|
|
||||||
|
// A byte array containing the JSON-encoded payload of this push notification.
|
||||||
|
// Refer to "The Remote Notification Payload" section in the Apple Local and
|
||||||
|
// Remote Notification Programming Guide for more info.
|
||||||
|
Payload interface{}
|
||||||
|
|
||||||
|
// The pushtype of the push notification. If this values is left as the
|
||||||
|
// default an apns-push-type header with value 'alert' will be added to the
|
||||||
|
// http request.
|
||||||
|
PushType EPushType
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts the notification payload to JSON.
|
||||||
|
func (n *Notification) MarshalJSON() ([]byte, error) {
|
||||||
|
switch n.Payload.(type) {
|
||||||
|
case string:
|
||||||
|
return []byte(n.Payload.(string)), nil
|
||||||
|
case []byte:
|
||||||
|
return n.Payload.([]byte), nil
|
||||||
|
default:
|
||||||
|
return json.Marshal(n.Payload)
|
||||||
|
}
|
||||||
|
}
|
348
vendor/github.com/sideshow/apns2/payload/builder.go
generated
vendored
Normal file
348
vendor/github.com/sideshow/apns2/payload/builder.go
generated
vendored
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
// Package payload is a helper package which contains a payload
|
||||||
|
// builder to make constructing notification payloads easier.
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// Payload represents a notification which holds the content that will be
|
||||||
|
// marshalled as JSON.
|
||||||
|
type Payload struct {
|
||||||
|
content map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type aps struct {
|
||||||
|
Alert interface{} `json:"alert,omitempty"`
|
||||||
|
Badge interface{} `json:"badge,omitempty"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
ContentAvailable int `json:"content-available,omitempty"`
|
||||||
|
MutableContent int `json:"mutable-content,omitempty"`
|
||||||
|
Sound interface{} `json:"sound,omitempty"`
|
||||||
|
ThreadID string `json:"thread-id,omitempty"`
|
||||||
|
URLArgs []string `json:"url-args,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type alert struct {
|
||||||
|
Action string `json:"action,omitempty"`
|
||||||
|
ActionLocKey string `json:"action-loc-key,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
LaunchImage string `json:"launch-image,omitempty"`
|
||||||
|
LocArgs []string `json:"loc-args,omitempty"`
|
||||||
|
LocKey string `json:"loc-key,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Subtitle string `json:"subtitle,omitempty"`
|
||||||
|
TitleLocArgs []string `json:"title-loc-args,omitempty"`
|
||||||
|
TitleLocKey string `json:"title-loc-key,omitempty"`
|
||||||
|
SummaryArg string `json:"summary-arg,omitempty"`
|
||||||
|
SummaryArgCount int `json:"summary-arg-count,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sound struct {
|
||||||
|
Critical int `json:"critical,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Volume float32 `json:"volume,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPayload returns a new Payload struct
|
||||||
|
func NewPayload() *Payload {
|
||||||
|
return &Payload{
|
||||||
|
map[string]interface{}{
|
||||||
|
"aps": &aps{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert sets the aps alert on the payload.
|
||||||
|
// This will display a notification alert message to the user.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":alert}}`
|
||||||
|
func (p *Payload) Alert(alert interface{}) *Payload {
|
||||||
|
p.aps().Alert = alert
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge sets the aps badge on the payload.
|
||||||
|
// This will display a numeric badge on the app icon.
|
||||||
|
//
|
||||||
|
// {"aps":{"badge":b}}
|
||||||
|
func (p *Payload) Badge(b int) *Payload {
|
||||||
|
p.aps().Badge = b
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZeroBadge sets the aps badge on the payload to 0.
|
||||||
|
// This will clear the badge on the app icon.
|
||||||
|
//
|
||||||
|
// {"aps":{"badge":0}}
|
||||||
|
func (p *Payload) ZeroBadge() *Payload {
|
||||||
|
p.aps().Badge = 0
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsetBadge removes the badge attribute from the payload.
|
||||||
|
// This will leave the badge on the app icon unchanged.
|
||||||
|
// If you wish to clear the app icon badge, use ZeroBadge() instead.
|
||||||
|
//
|
||||||
|
// {"aps":{}}
|
||||||
|
func (p *Payload) UnsetBadge() *Payload {
|
||||||
|
p.aps().Badge = nil
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sound sets the aps sound on the payload.
|
||||||
|
// This will play a sound from the app bundle, or the default sound otherwise.
|
||||||
|
//
|
||||||
|
// {"aps":{"sound":sound}}
|
||||||
|
func (p *Payload) Sound(sound interface{}) *Payload {
|
||||||
|
p.aps().Sound = sound
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentAvailable sets the aps content-available on the payload to 1.
|
||||||
|
// This will indicate to the app that there is new content available to download
|
||||||
|
// and launch the app in the background.
|
||||||
|
//
|
||||||
|
// {"aps":{"content-available":1}}
|
||||||
|
func (p *Payload) ContentAvailable() *Payload {
|
||||||
|
p.aps().ContentAvailable = 1
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutableContent sets the aps mutable-content on the payload to 1.
|
||||||
|
// This will indicate to the to the system to call your Notification Service
|
||||||
|
// extension to mutate or replace the notification's content.
|
||||||
|
//
|
||||||
|
// {"aps":{"mutable-content":1}}
|
||||||
|
func (p *Payload) MutableContent() *Payload {
|
||||||
|
p.aps().MutableContent = 1
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom payload
|
||||||
|
|
||||||
|
// Custom sets a custom key and value on the payload.
|
||||||
|
// This will add custom key/value data to the notification payload at root level.
|
||||||
|
//
|
||||||
|
// {"aps":{}, key:value}
|
||||||
|
func (p *Payload) Custom(key string, val interface{}) *Payload {
|
||||||
|
p.content[key] = val
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert dictionary
|
||||||
|
|
||||||
|
// AlertTitle sets the aps alert title on the payload.
|
||||||
|
// This will display a short string describing the purpose of the notification.
|
||||||
|
// Apple Watch & Safari display this string as part of the notification interface.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"title":title}}}
|
||||||
|
func (p *Payload) AlertTitle(title string) *Payload {
|
||||||
|
p.aps().alert().Title = title
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertTitleLocKey sets the aps alert title localization key on the payload.
|
||||||
|
// This is the key to a title string in the Localizable.strings file for the
|
||||||
|
// current localization. See Localized Formatted Strings in Apple documentation
|
||||||
|
// for more information.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"title-loc-key":key}}}
|
||||||
|
func (p *Payload) AlertTitleLocKey(key string) *Payload {
|
||||||
|
p.aps().alert().TitleLocKey = key
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertTitleLocArgs sets the aps alert title localization args on the payload.
|
||||||
|
// These are the variable string values to appear in place of the format
|
||||||
|
// specifiers in title-loc-key. See Localized Formatted Strings in Apple
|
||||||
|
// documentation for more information.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"title-loc-args":args}}}
|
||||||
|
func (p *Payload) AlertTitleLocArgs(args []string) *Payload {
|
||||||
|
p.aps().alert().TitleLocArgs = args
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertSubtitle sets the aps alert subtitle on the payload.
|
||||||
|
// This will display a short string describing the purpose of the notification.
|
||||||
|
// Apple Watch & Safari display this string as part of the notification interface.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"subtitle":"subtitle"}}}
|
||||||
|
func (p *Payload) AlertSubtitle(subtitle string) *Payload {
|
||||||
|
p.aps().alert().Subtitle = subtitle
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertBody sets the aps alert body on the payload.
|
||||||
|
// This is the text of the alert message.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"body":body}}}
|
||||||
|
func (p *Payload) AlertBody(body string) *Payload {
|
||||||
|
p.aps().alert().Body = body
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertLaunchImage sets the aps launch image on the payload.
|
||||||
|
// This is the filename of an image file in the app bundle. The image is used
|
||||||
|
// as the launch image when users tap the action button or move the action
|
||||||
|
// slider.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"launch-image":image}}}
|
||||||
|
func (p *Payload) AlertLaunchImage(image string) *Payload {
|
||||||
|
p.aps().alert().LaunchImage = image
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertLocArgs sets the aps alert localization args on the payload.
|
||||||
|
// These are the variable string values to appear in place of the format
|
||||||
|
// specifiers in loc-key. See Localized Formatted Strings in Apple
|
||||||
|
// documentation for more information.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"loc-args":args}}}
|
||||||
|
func (p *Payload) AlertLocArgs(args []string) *Payload {
|
||||||
|
p.aps().alert().LocArgs = args
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertLocKey sets the aps alert localization key on the payload.
|
||||||
|
// This is the key to an alert-message string in the Localizable.strings file
|
||||||
|
// for the current localization. See Localized Formatted Strings in Apple
|
||||||
|
// documentation for more information.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"loc-key":key}}}
|
||||||
|
func (p *Payload) AlertLocKey(key string) *Payload {
|
||||||
|
p.aps().alert().LocKey = key
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertAction sets the aps alert action on the payload.
|
||||||
|
// This is the label of the action button, if the user sets the notifications
|
||||||
|
// to appear as alerts. This label should be succinct, such as “Details” or
|
||||||
|
// “Read more”. If omitted, the default value is “Show”.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"action":action}}}
|
||||||
|
func (p *Payload) AlertAction(action string) *Payload {
|
||||||
|
p.aps().alert().Action = action
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertActionLocKey sets the aps alert action localization key on the payload.
|
||||||
|
// This is the the string used as a key to get a localized string in the current
|
||||||
|
// localization to use for the notfication right button’s title instead of
|
||||||
|
// “View”. See Localized Formatted Strings in Apple documentation for more
|
||||||
|
// information.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"action-loc-key":key}}}
|
||||||
|
func (p *Payload) AlertActionLocKey(key string) *Payload {
|
||||||
|
p.aps().alert().ActionLocKey = key
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertSummaryArg sets the aps alert summary arg key on the payload.
|
||||||
|
// This is the string that is used as a key to fill in an argument
|
||||||
|
// at the bottom of a notification to provide more context, such as
|
||||||
|
// a name associated with the sender of the notification.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"summary-arg":key}}}
|
||||||
|
func (p *Payload) AlertSummaryArg(key string) *Payload {
|
||||||
|
p.aps().alert().SummaryArg = key
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertSummaryArgCount sets the aps alert summary arg count key on the payload.
|
||||||
|
// This integer sets a custom "weight" on the notification, effectively
|
||||||
|
// allowing a notification to be viewed internally as two. For example if
|
||||||
|
// a notification encompasses 3 messages, you can set it to 3.
|
||||||
|
//
|
||||||
|
// {"aps":{"alert":{"summary-arg-count":key}}}
|
||||||
|
func (p *Payload) AlertSummaryArgCount(key int) *Payload {
|
||||||
|
p.aps().alert().SummaryArgCount = key
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
|
// Category sets the aps category on the payload.
|
||||||
|
// This is a string value that represents the identifier property of the
|
||||||
|
// UIMutableUserNotificationCategory object you created to define custom actions.
|
||||||
|
//
|
||||||
|
// {"aps":{"category":category}}
|
||||||
|
func (p *Payload) Category(category string) *Payload {
|
||||||
|
p.aps().Category = category
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mdm sets the mdm on the payload.
|
||||||
|
// This is for Apple Mobile Device Management (mdm) payloads.
|
||||||
|
//
|
||||||
|
// {"aps":{}:"mdm":mdm}
|
||||||
|
func (p *Payload) Mdm(mdm string) *Payload {
|
||||||
|
p.content["mdm"] = mdm
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThreadID sets the aps thread id on the payload.
|
||||||
|
// This is for the purpose of updating the contents of a View Controller in a
|
||||||
|
// Notification Content app extension when a new notification arrives. If a
|
||||||
|
// new notification arrives whose thread-id value matches the thread-id of the
|
||||||
|
// notification already being displayed, the didReceiveNotification method
|
||||||
|
// is called.
|
||||||
|
//
|
||||||
|
// {"aps":{"thread-id":id}}
|
||||||
|
func (p *Payload) ThreadID(threadID string) *Payload {
|
||||||
|
p.aps().ThreadID = threadID
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLArgs sets the aps category on the payload.
|
||||||
|
// This specifies an array of values that are paired with the placeholders
|
||||||
|
// inside the urlFormatString value of your website.json file.
|
||||||
|
// See Apple Notification Programming Guide for Websites.
|
||||||
|
//
|
||||||
|
// {"aps":{"url-args":urlArgs}}
|
||||||
|
func (p *Payload) URLArgs(urlArgs []string) *Payload {
|
||||||
|
p.aps().URLArgs = urlArgs
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoundName sets the name value on the aps sound dictionary.
|
||||||
|
// This function makes the notification a critical alert, which should be pre-approved by Apple.
|
||||||
|
// See: https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/
|
||||||
|
//
|
||||||
|
// {"aps":{"sound":{"critical":1,"name":name,"volume":1.0}}}
|
||||||
|
func (p *Payload) SoundName(name string) *Payload {
|
||||||
|
p.aps().sound().Name = name
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoundVolume sets the volume value on the aps sound dictionary.
|
||||||
|
// This function makes the notification a critical alert, which should be pre-approved by Apple.
|
||||||
|
// See: https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/
|
||||||
|
//
|
||||||
|
// {"aps":{"sound":{"critical":1,"name":"default","volume":volume}}}
|
||||||
|
func (p *Payload) SoundVolume(volume float32) *Payload {
|
||||||
|
p.aps().sound().Volume = volume
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoded version of the Payload
|
||||||
|
func (p *Payload) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(p.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Payload) aps() *aps {
|
||||||
|
return p.content["aps"].(*aps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aps) alert() *alert {
|
||||||
|
if _, ok := a.Alert.(*alert); !ok {
|
||||||
|
a.Alert = &alert{}
|
||||||
|
}
|
||||||
|
return a.Alert.(*alert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aps) sound() *sound {
|
||||||
|
if _, ok := a.Sound.(*sound); !ok {
|
||||||
|
a.Sound = &sound{Critical: 1, Name: "default", Volume: 1.0}
|
||||||
|
}
|
||||||
|
return a.Sound.(*sound)
|
||||||
|
}
|
153
vendor/github.com/sideshow/apns2/response.go
generated
vendored
Normal file
153
vendor/github.com/sideshow/apns2/response.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package apns2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusSent is a 200 response.
|
||||||
|
const StatusSent = http.StatusOK
|
||||||
|
|
||||||
|
// The possible Reason error codes returned from APNs.
|
||||||
|
// From table 8-6 in the Apple Local and Remote Notification Programming Guide.
|
||||||
|
const (
|
||||||
|
// 400 The collapse identifier exceeds the maximum allowed size
|
||||||
|
ReasonBadCollapseID = "BadCollapseId"
|
||||||
|
|
||||||
|
// 400 The specified device token was bad. Verify that the request contains a
|
||||||
|
// valid token and that the token matches the environment.
|
||||||
|
ReasonBadDeviceToken = "BadDeviceToken"
|
||||||
|
|
||||||
|
// 400 The apns-expiration value is bad.
|
||||||
|
ReasonBadExpirationDate = "BadExpirationDate"
|
||||||
|
|
||||||
|
// 400 The apns-id value is bad.
|
||||||
|
ReasonBadMessageID = "BadMessageId"
|
||||||
|
|
||||||
|
// 400 The apns-priority value is bad.
|
||||||
|
ReasonBadPriority = "BadPriority"
|
||||||
|
|
||||||
|
// 400 The apns-topic was invalid.
|
||||||
|
ReasonBadTopic = "BadTopic"
|
||||||
|
|
||||||
|
// 400 The device token does not match the specified topic.
|
||||||
|
ReasonDeviceTokenNotForTopic = "DeviceTokenNotForTopic"
|
||||||
|
|
||||||
|
// 400 One or more headers were repeated.
|
||||||
|
ReasonDuplicateHeaders = "DuplicateHeaders"
|
||||||
|
|
||||||
|
// 400 Idle time out.
|
||||||
|
ReasonIdleTimeout = "IdleTimeout"
|
||||||
|
|
||||||
|
// 400 The device token is not specified in the request :path. Verify that the
|
||||||
|
// :path header contains the device token.
|
||||||
|
ReasonMissingDeviceToken = "MissingDeviceToken"
|
||||||
|
|
||||||
|
// 400 The apns-topic header of the request was not specified and was
|
||||||
|
// required. The apns-topic header is mandatory when the client is connected
|
||||||
|
// using a certificate that supports multiple topics.
|
||||||
|
ReasonMissingTopic = "MissingTopic"
|
||||||
|
|
||||||
|
// 400 The message payload was empty.
|
||||||
|
ReasonPayloadEmpty = "PayloadEmpty"
|
||||||
|
|
||||||
|
// 400 Pushing to this topic is not allowed.
|
||||||
|
ReasonTopicDisallowed = "TopicDisallowed"
|
||||||
|
|
||||||
|
// 403 The certificate was bad.
|
||||||
|
ReasonBadCertificate = "BadCertificate"
|
||||||
|
|
||||||
|
// 403 The client certificate was for the wrong environment.
|
||||||
|
ReasonBadCertificateEnvironment = "BadCertificateEnvironment"
|
||||||
|
|
||||||
|
// 403 The provider token is stale and a new token should be generated.
|
||||||
|
ReasonExpiredProviderToken = "ExpiredProviderToken"
|
||||||
|
|
||||||
|
// 403 The specified action is not allowed.
|
||||||
|
ReasonForbidden = "Forbidden"
|
||||||
|
|
||||||
|
// 403 The provider token is not valid or the token signature could not be
|
||||||
|
// verified.
|
||||||
|
ReasonInvalidProviderToken = "InvalidProviderToken"
|
||||||
|
|
||||||
|
// 403 No provider certificate was used to connect to APNs and Authorization
|
||||||
|
// header was missing or no provider token was specified.
|
||||||
|
ReasonMissingProviderToken = "MissingProviderToken"
|
||||||
|
|
||||||
|
// 404 The request contained a bad :path value.
|
||||||
|
ReasonBadPath = "BadPath"
|
||||||
|
|
||||||
|
// 405 The specified :method was not POST.
|
||||||
|
ReasonMethodNotAllowed = "MethodNotAllowed"
|
||||||
|
|
||||||
|
// 410 The device token is inactive for the specified topic.
|
||||||
|
ReasonUnregistered = "Unregistered"
|
||||||
|
|
||||||
|
// 413 The message payload was too large. See Creating the Remote Notification
|
||||||
|
// Payload in the Apple Local and Remote Notification Programming Guide for
|
||||||
|
// details on maximum payload size.
|
||||||
|
ReasonPayloadTooLarge = "PayloadTooLarge"
|
||||||
|
|
||||||
|
// 429 The provider token is being updated too often.
|
||||||
|
ReasonTooManyProviderTokenUpdates = "TooManyProviderTokenUpdates"
|
||||||
|
|
||||||
|
// 429 Too many requests were made consecutively to the same device token.
|
||||||
|
ReasonTooManyRequests = "TooManyRequests"
|
||||||
|
|
||||||
|
// 500 An internal server error occurred.
|
||||||
|
ReasonInternalServerError = "InternalServerError"
|
||||||
|
|
||||||
|
// 503 The service is unavailable.
|
||||||
|
ReasonServiceUnavailable = "ServiceUnavailable"
|
||||||
|
|
||||||
|
// 503 The server is shutting down.
|
||||||
|
ReasonShutdown = "Shutdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Response represents a result from the APNs gateway indicating whether a
|
||||||
|
// notification was accepted or rejected and (if applicable) the metadata
|
||||||
|
// surrounding the rejection.
|
||||||
|
type Response struct {
|
||||||
|
|
||||||
|
// The HTTP status code returned by APNs.
|
||||||
|
// A 200 value indicates that the notification was successfully sent.
|
||||||
|
// For a list of other possible status codes, see table 6-4 in the Apple Local
|
||||||
|
// and Remote Notification Programming Guide.
|
||||||
|
StatusCode int
|
||||||
|
|
||||||
|
// The APNs error string indicating the reason for the notification failure (if
|
||||||
|
// any). The error code is specified as a string. For a list of possible
|
||||||
|
// values, see the Reason constants above.
|
||||||
|
// If the notification was accepted, this value will be "".
|
||||||
|
Reason string
|
||||||
|
|
||||||
|
// The APNs ApnsID value from the Notification. If you didn't set an ApnsID on the
|
||||||
|
// Notification, this will be a new unique UUID which has been created by APNs.
|
||||||
|
ApnsID string
|
||||||
|
|
||||||
|
// If the value of StatusCode is 410, this is the last time at which APNs
|
||||||
|
// confirmed that the device token was no longer valid for the topic.
|
||||||
|
Timestamp Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent returns whether or not the notification was successfully sent.
|
||||||
|
// This is the same as checking if the StatusCode == 200.
|
||||||
|
func (c *Response) Sent() bool {
|
||||||
|
return c.StatusCode == StatusSent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time represents a device uninstall time
|
||||||
|
type Time struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON converts an epoch date into a Time struct.
|
||||||
|
func (t *Time) UnmarshalJSON(b []byte) error {
|
||||||
|
ts, err := strconv.ParseInt(string(b), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Time = time.Unix(ts/1000, 0)
|
||||||
|
return nil
|
||||||
|
}
|
108
vendor/github.com/sideshow/apns2/token/token.go
generated
vendored
Normal file
108
vendor/github.com/sideshow/apns2/token/token.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TokenTimeout is the period of time in seconds that a token is valid for.
|
||||||
|
// If the timestamp for token issue is not within the last hour, APNs
|
||||||
|
// rejects subsequent push messages. This is set to under an hour so that
|
||||||
|
// we generate a new token before the existing one expires.
|
||||||
|
TokenTimeout = 3000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Possible errors when parsing a .p8 file.
|
||||||
|
var (
|
||||||
|
ErrAuthKeyNotPem = errors.New("token: AuthKey must be a valid .p8 PEM file")
|
||||||
|
ErrAuthKeyNotECDSA = errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
|
||||||
|
ErrAuthKeyNil = errors.New("token: AuthKey was nil")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token represents an Apple Provider Authentication Token (JSON Web Token).
|
||||||
|
type Token struct {
|
||||||
|
sync.Mutex
|
||||||
|
AuthKey *ecdsa.PrivateKey
|
||||||
|
KeyID string
|
||||||
|
TeamID string
|
||||||
|
IssuedAt int64
|
||||||
|
Bearer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthKeyFromFile loads a .p8 certificate from a local file and returns a
|
||||||
|
// *ecdsa.PrivateKey.
|
||||||
|
func AuthKeyFromFile(filename string) (*ecdsa.PrivateKey, error) {
|
||||||
|
bytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return AuthKeyFromBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthKeyFromBytes loads a .p8 certificate from an in memory byte array and
|
||||||
|
// returns an *ecdsa.PrivateKey.
|
||||||
|
func AuthKeyFromBytes(bytes []byte) (*ecdsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode(bytes)
|
||||||
|
if block == nil {
|
||||||
|
return nil, ErrAuthKeyNotPem
|
||||||
|
}
|
||||||
|
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch pk := key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return pk, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrAuthKeyNotECDSA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateIfExpired checks to see if the token is about to expire and
|
||||||
|
// generates a new token.
|
||||||
|
func (t *Token) GenerateIfExpired() {
|
||||||
|
t.Lock()
|
||||||
|
defer t.Unlock()
|
||||||
|
if t.Expired() {
|
||||||
|
t.Generate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired checks to see if the token has expired.
|
||||||
|
func (t *Token) Expired() bool {
|
||||||
|
return time.Now().Unix() >= (t.IssuedAt + TokenTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate creates a new token.
|
||||||
|
func (t *Token) Generate() (bool, error) {
|
||||||
|
if t.AuthKey == nil {
|
||||||
|
return false, ErrAuthKeyNil
|
||||||
|
}
|
||||||
|
issuedAt := time.Now().Unix()
|
||||||
|
jwtToken := &jwt.Token{
|
||||||
|
Header: map[string]interface{}{
|
||||||
|
"alg": "ES256",
|
||||||
|
"kid": t.KeyID,
|
||||||
|
},
|
||||||
|
Claims: jwt.MapClaims{
|
||||||
|
"iss": t.TeamID,
|
||||||
|
"iat": issuedAt,
|
||||||
|
},
|
||||||
|
Method: jwt.SigningMethodES256,
|
||||||
|
}
|
||||||
|
bearer, err := jwtToken.SignedString(t.AuthKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
t.IssuedAt = issuedAt
|
||||||
|
t.Bearer = bearer
|
||||||
|
return true, nil
|
||||||
|
}
|
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
50
vendor/golang.org/x/net/http/httpguts/guts.go
generated
vendored
Normal file
50
vendor/golang.org/x/net/http/httpguts/guts.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// 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 httpguts provides functions implementing various details
|
||||||
|
// of the HTTP specification.
|
||||||
|
//
|
||||||
|
// This package is shared by the standard library (which vendors it)
|
||||||
|
// and x/net/http2. It comes with no API stability promise.
|
||||||
|
package httpguts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/textproto"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidTrailerHeader reports whether name is a valid header field name to appear
|
||||||
|
// in trailers.
|
||||||
|
// See RFC 7230, Section 4.1.2
|
||||||
|
func ValidTrailerHeader(name string) bool {
|
||||||
|
name = textproto.CanonicalMIMEHeaderKey(name)
|
||||||
|
if strings.HasPrefix(name, "If-") || badTrailer[name] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var badTrailer = map[string]bool{
|
||||||
|
"Authorization": true,
|
||||||
|
"Cache-Control": true,
|
||||||
|
"Connection": true,
|
||||||
|
"Content-Encoding": true,
|
||||||
|
"Content-Length": true,
|
||||||
|
"Content-Range": true,
|
||||||
|
"Content-Type": true,
|
||||||
|
"Expect": true,
|
||||||
|
"Host": true,
|
||||||
|
"Keep-Alive": true,
|
||||||
|
"Max-Forwards": true,
|
||||||
|
"Pragma": true,
|
||||||
|
"Proxy-Authenticate": true,
|
||||||
|
"Proxy-Authorization": true,
|
||||||
|
"Proxy-Connection": true,
|
||||||
|
"Range": true,
|
||||||
|
"Realm": true,
|
||||||
|
"Te": true,
|
||||||
|
"Trailer": true,
|
||||||
|
"Transfer-Encoding": true,
|
||||||
|
"Www-Authenticate": true,
|
||||||
|
}
|
346
vendor/golang.org/x/net/http/httpguts/httplex.go
generated
vendored
Normal file
346
vendor/golang.org/x/net/http/httpguts/httplex.go
generated
vendored
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
// Copyright 2016 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 httpguts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
)
|
||||||
|
|
||||||
|
var isTokenTable = [127]bool{
|
||||||
|
'!': true,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': true,
|
||||||
|
'\'': true,
|
||||||
|
'*': true,
|
||||||
|
'+': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'W': true,
|
||||||
|
'V': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': true,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'|': true,
|
||||||
|
'~': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTokenRune(r rune) bool {
|
||||||
|
i := int(r)
|
||||||
|
return i < len(isTokenTable) && isTokenTable[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNotToken(r rune) bool {
|
||||||
|
return !IsTokenRune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderValuesContainsToken reports whether any string in values
|
||||||
|
// contains the provided token, ASCII case-insensitively.
|
||||||
|
func HeaderValuesContainsToken(values []string, token string) bool {
|
||||||
|
for _, v := range values {
|
||||||
|
if headerValueContainsToken(v, token) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOWS reports whether b is an optional whitespace byte, as defined
|
||||||
|
// by RFC 7230 section 3.2.3.
|
||||||
|
func isOWS(b byte) bool { return b == ' ' || b == '\t' }
|
||||||
|
|
||||||
|
// trimOWS returns x with all optional whitespace removes from the
|
||||||
|
// beginning and end.
|
||||||
|
func trimOWS(x string) string {
|
||||||
|
// TODO: consider using strings.Trim(x, " \t") instead,
|
||||||
|
// if and when it's fast enough. See issue 10292.
|
||||||
|
// But this ASCII-only code will probably always beat UTF-8
|
||||||
|
// aware code.
|
||||||
|
for len(x) > 0 && isOWS(x[0]) {
|
||||||
|
x = x[1:]
|
||||||
|
}
|
||||||
|
for len(x) > 0 && isOWS(x[len(x)-1]) {
|
||||||
|
x = x[:len(x)-1]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerValueContainsToken reports whether v (assumed to be a
|
||||||
|
// 0#element, in the ABNF extension described in RFC 7230 section 7)
|
||||||
|
// contains token amongst its comma-separated tokens, ASCII
|
||||||
|
// case-insensitively.
|
||||||
|
func headerValueContainsToken(v string, token string) bool {
|
||||||
|
v = trimOWS(v)
|
||||||
|
if comma := strings.IndexByte(v, ','); comma != -1 {
|
||||||
|
return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token)
|
||||||
|
}
|
||||||
|
return tokenEqual(v, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lowerASCII returns the ASCII lowercase version of b.
|
||||||
|
func lowerASCII(b byte) byte {
|
||||||
|
if 'A' <= b && b <= 'Z' {
|
||||||
|
return b + ('a' - 'A')
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively.
|
||||||
|
func tokenEqual(t1, t2 string) bool {
|
||||||
|
if len(t1) != len(t2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, b := range t1 {
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
// No UTF-8 or non-ASCII allowed in tokens.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lowerASCII(byte(b)) != lowerASCII(t2[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLWS reports whether b is linear white space, according
|
||||||
|
// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
|
||||||
|
// LWS = [CRLF] 1*( SP | HT )
|
||||||
|
func isLWS(b byte) bool { return b == ' ' || b == '\t' }
|
||||||
|
|
||||||
|
// isCTL reports whether b is a control byte, according
|
||||||
|
// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
|
||||||
|
// CTL = <any US-ASCII control character
|
||||||
|
// (octets 0 - 31) and DEL (127)>
|
||||||
|
func isCTL(b byte) bool {
|
||||||
|
const del = 0x7f // a CTL
|
||||||
|
return b < ' ' || b == del
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidHeaderFieldName reports whether v is a valid HTTP/1.x header name.
|
||||||
|
// HTTP/2 imposes the additional restriction that uppercase ASCII
|
||||||
|
// letters are not allowed.
|
||||||
|
//
|
||||||
|
// RFC 7230 says:
|
||||||
|
// header-field = field-name ":" OWS field-value OWS
|
||||||
|
// field-name = token
|
||||||
|
// token = 1*tchar
|
||||||
|
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
|
||||||
|
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
||||||
|
func ValidHeaderFieldName(v string) bool {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range v {
|
||||||
|
if !IsTokenRune(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidHostHeader reports whether h is a valid host header.
|
||||||
|
func ValidHostHeader(h string) bool {
|
||||||
|
// The latest spec is actually this:
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc7230#section-5.4
|
||||||
|
// Host = uri-host [ ":" port ]
|
||||||
|
//
|
||||||
|
// Where uri-host is:
|
||||||
|
// http://tools.ietf.org/html/rfc3986#section-3.2.2
|
||||||
|
//
|
||||||
|
// But we're going to be much more lenient for now and just
|
||||||
|
// search for any byte that's not a valid byte in any of those
|
||||||
|
// expressions.
|
||||||
|
for i := 0; i < len(h); i++ {
|
||||||
|
if !validHostByte[h[i]] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// See the validHostHeader comment.
|
||||||
|
var validHostByte = [256]bool{
|
||||||
|
'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true,
|
||||||
|
'8': true, '9': true,
|
||||||
|
|
||||||
|
'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true,
|
||||||
|
'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true,
|
||||||
|
'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
|
||||||
|
'y': true, 'z': true,
|
||||||
|
|
||||||
|
'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true,
|
||||||
|
'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true,
|
||||||
|
'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
|
||||||
|
'Y': true, 'Z': true,
|
||||||
|
|
||||||
|
'!': true, // sub-delims
|
||||||
|
'$': true, // sub-delims
|
||||||
|
'%': true, // pct-encoded (and used in IPv6 zones)
|
||||||
|
'&': true, // sub-delims
|
||||||
|
'(': true, // sub-delims
|
||||||
|
')': true, // sub-delims
|
||||||
|
'*': true, // sub-delims
|
||||||
|
'+': true, // sub-delims
|
||||||
|
',': true, // sub-delims
|
||||||
|
'-': true, // unreserved
|
||||||
|
'.': true, // unreserved
|
||||||
|
':': true, // IPv6address + Host expression's optional port
|
||||||
|
';': true, // sub-delims
|
||||||
|
'=': true, // sub-delims
|
||||||
|
'[': true,
|
||||||
|
'\'': true, // sub-delims
|
||||||
|
']': true,
|
||||||
|
'_': true, // unreserved
|
||||||
|
'~': true, // unreserved
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidHeaderFieldValue reports whether v is a valid "field-value" according to
|
||||||
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 :
|
||||||
|
//
|
||||||
|
// message-header = field-name ":" [ field-value ]
|
||||||
|
// field-value = *( field-content | LWS )
|
||||||
|
// field-content = <the OCTETs making up the field-value
|
||||||
|
// and consisting of either *TEXT or combinations
|
||||||
|
// of token, separators, and quoted-string>
|
||||||
|
//
|
||||||
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 :
|
||||||
|
//
|
||||||
|
// TEXT = <any OCTET except CTLs,
|
||||||
|
// but including LWS>
|
||||||
|
// LWS = [CRLF] 1*( SP | HT )
|
||||||
|
// CTL = <any US-ASCII control character
|
||||||
|
// (octets 0 - 31) and DEL (127)>
|
||||||
|
//
|
||||||
|
// RFC 7230 says:
|
||||||
|
// field-value = *( field-content / obs-fold )
|
||||||
|
// obj-fold = N/A to http2, and deprecated
|
||||||
|
// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||||
|
// field-vchar = VCHAR / obs-text
|
||||||
|
// obs-text = %x80-FF
|
||||||
|
// VCHAR = "any visible [USASCII] character"
|
||||||
|
//
|
||||||
|
// http2 further says: "Similarly, HTTP/2 allows header field values
|
||||||
|
// that are not valid. While most of the values that can be encoded
|
||||||
|
// will not alter header field parsing, carriage return (CR, ASCII
|
||||||
|
// 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII
|
||||||
|
// 0x0) might be exploited by an attacker if they are translated
|
||||||
|
// verbatim. Any request or response that contains a character not
|
||||||
|
// permitted in a header field value MUST be treated as malformed
|
||||||
|
// (Section 8.1.2.6). Valid characters are defined by the
|
||||||
|
// field-content ABNF rule in Section 3.2 of [RFC7230]."
|
||||||
|
//
|
||||||
|
// This function does not (yet?) properly handle the rejection of
|
||||||
|
// strings that begin or end with SP or HTAB.
|
||||||
|
func ValidHeaderFieldValue(v string) bool {
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
b := v[i]
|
||||||
|
if isCTL(b) && !isLWS(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isASCII(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PunycodeHostPort returns the IDNA Punycode version
|
||||||
|
// of the provided "host" or "host:port" string.
|
||||||
|
func PunycodeHostPort(v string) (string, error) {
|
||||||
|
if isASCII(v) {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(v)
|
||||||
|
if err != nil {
|
||||||
|
// The input 'v' argument was just a "host" argument,
|
||||||
|
// without a port. This error should not be returned
|
||||||
|
// to the caller.
|
||||||
|
host = v
|
||||||
|
port = ""
|
||||||
|
}
|
||||||
|
host, err = idna.ToASCII(host)
|
||||||
|
if err != nil {
|
||||||
|
// Non-UTF-8? Not representable in Punycode, in any
|
||||||
|
// case.
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if port == "" {
|
||||||
|
return host, nil
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, port), nil
|
||||||
|
}
|
2
vendor/golang.org/x/net/http2/.gitignore
generated
vendored
Normal file
2
vendor/golang.org/x/net/http2/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*~
|
||||||
|
h2i/h2i
|
51
vendor/golang.org/x/net/http2/Dockerfile
generated
vendored
Normal file
51
vendor/golang.org/x/net/http2/Dockerfile
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#
|
||||||
|
# This Dockerfile builds a recent curl with HTTP/2 client support, using
|
||||||
|
# a recent nghttp2 build.
|
||||||
|
#
|
||||||
|
# See the Makefile for how to tag it. If Docker and that image is found, the
|
||||||
|
# Go tests use this curl binary for integration tests.
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM ubuntu:trusty
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get upgrade -y && \
|
||||||
|
apt-get install -y git-core build-essential wget
|
||||||
|
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
autotools-dev libtool pkg-config zlib1g-dev \
|
||||||
|
libcunit1-dev libssl-dev libxml2-dev libevent-dev \
|
||||||
|
automake autoconf
|
||||||
|
|
||||||
|
# The list of packages nghttp2 recommends for h2load:
|
||||||
|
RUN apt-get install -y --no-install-recommends make binutils \
|
||||||
|
autoconf automake autotools-dev \
|
||||||
|
libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \
|
||||||
|
libev-dev libevent-dev libjansson-dev libjemalloc-dev \
|
||||||
|
cython python3.4-dev python-setuptools
|
||||||
|
|
||||||
|
# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
|
||||||
|
ENV NGHTTP2_VER 895da9a
|
||||||
|
RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
|
||||||
|
|
||||||
|
WORKDIR /root/nghttp2
|
||||||
|
RUN git reset --hard $NGHTTP2_VER
|
||||||
|
RUN autoreconf -i
|
||||||
|
RUN automake
|
||||||
|
RUN autoconf
|
||||||
|
RUN ./configure
|
||||||
|
RUN make
|
||||||
|
RUN make install
|
||||||
|
|
||||||
|
WORKDIR /root
|
||||||
|
RUN wget http://curl.haxx.se/download/curl-7.45.0.tar.gz
|
||||||
|
RUN tar -zxvf curl-7.45.0.tar.gz
|
||||||
|
WORKDIR /root/curl-7.45.0
|
||||||
|
RUN ./configure --with-ssl --with-nghttp2=/usr/local
|
||||||
|
RUN make
|
||||||
|
RUN make install
|
||||||
|
RUN ldconfig
|
||||||
|
|
||||||
|
CMD ["-h"]
|
||||||
|
ENTRYPOINT ["/usr/local/bin/curl"]
|
||||||
|
|
3
vendor/golang.org/x/net/http2/Makefile
generated
vendored
Normal file
3
vendor/golang.org/x/net/http2/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
curlimage:
|
||||||
|
docker build -t gohttp2/curl .
|
||||||
|
|
20
vendor/golang.org/x/net/http2/README
generated
vendored
Normal file
20
vendor/golang.org/x/net/http2/README
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
This is a work-in-progress HTTP/2 implementation for Go.
|
||||||
|
|
||||||
|
It will eventually live in the Go standard library and won't require
|
||||||
|
any changes to your code to use. It will just be automatic.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
* The server support is pretty good. A few things are missing
|
||||||
|
but are being worked on.
|
||||||
|
* The client work has just started but shares a lot of code
|
||||||
|
is coming along much quicker.
|
||||||
|
|
||||||
|
Docs are at https://godoc.org/golang.org/x/net/http2
|
||||||
|
|
||||||
|
Demo test server at https://http2.golang.org/
|
||||||
|
|
||||||
|
Help & bug reports welcome!
|
||||||
|
|
||||||
|
Contributing: https://golang.org/doc/contribute.html
|
||||||
|
Bugs: https://golang.org/issue/new?title=x/net/http2:+
|
641
vendor/golang.org/x/net/http2/ciphers.go
generated
vendored
Normal file
641
vendor/golang.org/x/net/http2/ciphers.go
generated
vendored
Normal file
|
@ -0,0 +1,641 @@
|
||||||
|
// Copyright 2017 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 http2
|
||||||
|
|
||||||
|
// A list of the possible cipher suite ids. Taken from
|
||||||
|
// https://www.iana.org/assignments/tls-parameters/tls-parameters.txt
|
||||||
|
|
||||||
|
const (
|
||||||
|
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000
|
||||||
|
cipher_TLS_RSA_WITH_NULL_MD5 uint16 = 0x0001
|
||||||
|
cipher_TLS_RSA_WITH_NULL_SHA uint16 = 0x0002
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0003
|
||||||
|
cipher_TLS_RSA_WITH_RC4_128_MD5 uint16 = 0x0004
|
||||||
|
cipher_TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x0006
|
||||||
|
cipher_TLS_RSA_WITH_IDEA_CBC_SHA uint16 = 0x0007
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0008
|
||||||
|
cipher_TLS_RSA_WITH_DES_CBC_SHA uint16 = 0x0009
|
||||||
|
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000A
|
||||||
|
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000B
|
||||||
|
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA uint16 = 0x000C
|
||||||
|
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x000D
|
||||||
|
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000E
|
||||||
|
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA uint16 = 0x000F
|
||||||
|
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0010
|
||||||
|
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0011
|
||||||
|
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA uint16 = 0x0012
|
||||||
|
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x0013
|
||||||
|
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0014
|
||||||
|
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA uint16 = 0x0015
|
||||||
|
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0016
|
||||||
|
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0017
|
||||||
|
cipher_TLS_DH_anon_WITH_RC4_128_MD5 uint16 = 0x0018
|
||||||
|
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0019
|
||||||
|
cipher_TLS_DH_anon_WITH_DES_CBC_SHA uint16 = 0x001A
|
||||||
|
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0x001B
|
||||||
|
// Reserved uint16 = 0x001C-1D
|
||||||
|
cipher_TLS_KRB5_WITH_DES_CBC_SHA uint16 = 0x001E
|
||||||
|
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA uint16 = 0x001F
|
||||||
|
cipher_TLS_KRB5_WITH_RC4_128_SHA uint16 = 0x0020
|
||||||
|
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA uint16 = 0x0021
|
||||||
|
cipher_TLS_KRB5_WITH_DES_CBC_MD5 uint16 = 0x0022
|
||||||
|
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5 uint16 = 0x0023
|
||||||
|
cipher_TLS_KRB5_WITH_RC4_128_MD5 uint16 = 0x0024
|
||||||
|
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5 uint16 = 0x0025
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA uint16 = 0x0026
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA uint16 = 0x0027
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA uint16 = 0x0028
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 uint16 = 0x0029
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x002A
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5 uint16 = 0x002B
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA uint16 = 0x002C
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA uint16 = 0x002D
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA uint16 = 0x002E
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002F
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0030
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0031
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0032
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA uint16 = 0x0034
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0036
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0037
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0038
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA uint16 = 0x003A
|
||||||
|
cipher_TLS_RSA_WITH_NULL_SHA256 uint16 = 0x003B
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003C
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003D
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x003E
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003F
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x0040
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0041
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0042
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0043
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0044
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0045
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0046
|
||||||
|
// Reserved uint16 = 0x0047-4F
|
||||||
|
// Reserved uint16 = 0x0050-58
|
||||||
|
// Reserved uint16 = 0x0059-5C
|
||||||
|
// Unassigned uint16 = 0x005D-5F
|
||||||
|
// Reserved uint16 = 0x0060-66
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x0068
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x0069
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x006A
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006B
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256 uint16 = 0x006C
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256 uint16 = 0x006D
|
||||||
|
// Unassigned uint16 = 0x006E-83
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0084
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0085
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0086
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0087
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0088
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0089
|
||||||
|
cipher_TLS_PSK_WITH_RC4_128_SHA uint16 = 0x008A
|
||||||
|
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008B
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CBC_SHA uint16 = 0x008C
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CBC_SHA uint16 = 0x008D
|
||||||
|
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA uint16 = 0x008E
|
||||||
|
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008F
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0090
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0091
|
||||||
|
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA uint16 = 0x0092
|
||||||
|
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x0093
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0094
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0095
|
||||||
|
cipher_TLS_RSA_WITH_SEED_CBC_SHA uint16 = 0x0096
|
||||||
|
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA uint16 = 0x0097
|
||||||
|
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA uint16 = 0x0098
|
||||||
|
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA uint16 = 0x0099
|
||||||
|
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA uint16 = 0x009A
|
||||||
|
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA uint16 = 0x009B
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009C
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009D
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009E
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009F
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x00A0
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x00A1
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A2
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A3
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A4
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A5
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256 uint16 = 0x00A6
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384 uint16 = 0x00A7
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00A8
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00A9
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AA
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AB
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AC
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AD
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00AE
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00AF
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA256 uint16 = 0x00B0
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA384 uint16 = 0x00B1
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B2
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B3
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA256 uint16 = 0x00B4
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA384 uint16 = 0x00B5
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B6
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B7
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA256 uint16 = 0x00B8
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA384 uint16 = 0x00B9
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BA
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BB
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BC
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BD
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BE
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BF
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C0
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C1
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C2
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C3
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C4
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C5
|
||||||
|
// Unassigned uint16 = 0x00C6-FE
|
||||||
|
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV uint16 = 0x00FF
|
||||||
|
// Unassigned uint16 = 0x01-55,*
|
||||||
|
cipher_TLS_FALLBACK_SCSV uint16 = 0x5600
|
||||||
|
// Unassigned uint16 = 0x5601 - 0xC000
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA uint16 = 0xC001
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA uint16 = 0xC002
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC003
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC004
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC005
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA uint16 = 0xC006
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xC007
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC008
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC009
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC00A
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_NULL_SHA uint16 = 0xC00B
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA uint16 = 0xC00C
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC00D
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC00E
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC00F
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA uint16 = 0xC010
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xC011
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC012
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC013
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC014
|
||||||
|
cipher_TLS_ECDH_anon_WITH_NULL_SHA uint16 = 0xC015
|
||||||
|
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA uint16 = 0xC016
|
||||||
|
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0xC017
|
||||||
|
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA uint16 = 0xC018
|
||||||
|
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA uint16 = 0xC019
|
||||||
|
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01A
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01B
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01C
|
||||||
|
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA uint16 = 0xC01D
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC01E
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA uint16 = 0xC01F
|
||||||
|
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA uint16 = 0xC020
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC021
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA uint16 = 0xC022
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC023
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC024
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC025
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC026
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC027
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC028
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC029
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC02A
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02B
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02C
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02D
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02E
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02F
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC030
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC031
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC032
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA uint16 = 0xC033
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0xC034
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0xC035
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0xC036
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0xC037
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0xC038
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA uint16 = 0xC039
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256 uint16 = 0xC03A
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384 uint16 = 0xC03B
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03C
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03D
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03E
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03F
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC040
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC041
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC042
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC043
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC044
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC045
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC046
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC047
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC048
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC049
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04A
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04B
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04C
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04D
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04E
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04F
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC050
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC051
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC052
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC053
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC054
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC055
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC056
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC057
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC058
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC059
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05A
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05B
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05C
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05D
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05E
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05F
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC060
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC061
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC062
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC063
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC064
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC065
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC066
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC067
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC068
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC069
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06A
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06B
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06C
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06D
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06E
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06F
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC070
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC071
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC072
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC073
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC074
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC075
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC076
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC077
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC078
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC079
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07A
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07B
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07C
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07D
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07E
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07F
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC080
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC081
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC082
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC083
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC084
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC085
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC086
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC087
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC088
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC089
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08A
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08B
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08C
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08D
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08E
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08F
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC090
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC091
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC092
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC093
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC094
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC095
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC096
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC097
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC098
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC099
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC09A
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC09B
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CCM uint16 = 0xC09C
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CCM uint16 = 0xC09D
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CCM uint16 = 0xC09E
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CCM uint16 = 0xC09F
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A0
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A1
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A2
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A3
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CCM uint16 = 0xC0A4
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CCM uint16 = 0xC0A5
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_CCM uint16 = 0xC0A6
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_CCM uint16 = 0xC0A7
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CCM_8 uint16 = 0xC0A8
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CCM_8 uint16 = 0xC0A9
|
||||||
|
cipher_TLS_PSK_DHE_WITH_AES_128_CCM_8 uint16 = 0xC0AA
|
||||||
|
cipher_TLS_PSK_DHE_WITH_AES_256_CCM_8 uint16 = 0xC0AB
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM uint16 = 0xC0AC
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM uint16 = 0xC0AD
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 uint16 = 0xC0AE
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 uint16 = 0xC0AF
|
||||||
|
// Unassigned uint16 = 0xC0B0-FF
|
||||||
|
// Unassigned uint16 = 0xC1-CB,*
|
||||||
|
// Unassigned uint16 = 0xCC00-A7
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA8
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA9
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAA
|
||||||
|
cipher_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAB
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAC
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAD
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAE
|
||||||
|
)
|
||||||
|
|
||||||
|
// isBadCipher reports whether the cipher is blacklisted by the HTTP/2 spec.
|
||||||
|
// References:
|
||||||
|
// https://tools.ietf.org/html/rfc7540#appendix-A
|
||||||
|
// Reject cipher suites from Appendix A.
|
||||||
|
// "This list includes those cipher suites that do not
|
||||||
|
// offer an ephemeral key exchange and those that are
|
||||||
|
// based on the TLS null, stream or block cipher type"
|
||||||
|
func isBadCipher(cipher uint16) bool {
|
||||||
|
switch cipher {
|
||||||
|
case cipher_TLS_NULL_WITH_NULL_NULL,
|
||||||
|
cipher_TLS_RSA_WITH_NULL_MD5,
|
||||||
|
cipher_TLS_RSA_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5,
|
||||||
|
cipher_TLS_RSA_WITH_RC4_128_MD5,
|
||||||
|
cipher_TLS_RSA_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||||
|
cipher_TLS_RSA_WITH_IDEA_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,
|
||||||
|
cipher_TLS_DH_anon_WITH_RC4_128_MD5,
|
||||||
|
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_KRB5_WITH_DES_CBC_SHA,
|
||||||
|
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_KRB5_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA,
|
||||||
|
cipher_TLS_KRB5_WITH_DES_CBC_MD5,
|
||||||
|
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5,
|
||||||
|
cipher_TLS_KRB5_WITH_RC4_128_MD5,
|
||||||
|
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||||
|
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5,
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_NULL_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA,
|
||||||
|
cipher_TLS_PSK_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA,
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_NULL_SHA384,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA256,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_NULL_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_NULL_SHA384,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256,
|
||||||
|
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_anon_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384,
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||||
|
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CCM,
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CCM,
|
||||||
|
cipher_TLS_RSA_WITH_AES_128_CCM_8,
|
||||||
|
cipher_TLS_RSA_WITH_AES_256_CCM_8,
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CCM,
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CCM,
|
||||||
|
cipher_TLS_PSK_WITH_AES_128_CCM_8,
|
||||||
|
cipher_TLS_PSK_WITH_AES_256_CCM_8:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
278
vendor/golang.org/x/net/http2/client_conn_pool.go
generated
vendored
Normal file
278
vendor/golang.org/x/net/http2/client_conn_pool.go
generated
vendored
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// Transport code's client connection pooling.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||||
|
type ClientConnPool interface {
|
||||||
|
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||||
|
MarkDead(*ClientConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||||
|
// implementations which can close their idle connections.
|
||||||
|
type clientConnPoolIdleCloser interface {
|
||||||
|
ClientConnPool
|
||||||
|
closeIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
|
||||||
|
_ clientConnPoolIdleCloser = noDialClientConnPool{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: use singleflight for dialing and addConnCalls?
|
||||||
|
type clientConnPool struct {
|
||||||
|
t *Transport
|
||||||
|
|
||||||
|
mu sync.Mutex // TODO: maybe switch to RWMutex
|
||||||
|
// TODO: add support for sharing conns based on cert names
|
||||||
|
// (e.g. share conn for googleapis.com and appspot.com)
|
||||||
|
conns map[string][]*ClientConn // key is host:port
|
||||||
|
dialing map[string]*dialCall // currently in-flight dials
|
||||||
|
keys map[*ClientConn][]string
|
||||||
|
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||||
|
return p.getClientConn(req, addr, dialOnMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dialOnMiss = true
|
||||||
|
noDialOnMiss = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// shouldTraceGetConn reports whether getClientConn should call any
|
||||||
|
// ClientTrace.GetConn hook associated with the http.Request.
|
||||||
|
//
|
||||||
|
// This complexity is needed to avoid double calls of the GetConn hook
|
||||||
|
// during the back-and-forth between net/http and x/net/http2 (when the
|
||||||
|
// net/http.Transport is upgraded to also speak http2), as well as support
|
||||||
|
// the case where x/net/http2 is being used directly.
|
||||||
|
func (p *clientConnPool) shouldTraceGetConn(st clientConnIdleState) bool {
|
||||||
|
// If our Transport wasn't made via ConfigureTransport, always
|
||||||
|
// trace the GetConn hook if provided, because that means the
|
||||||
|
// http2 package is being used directly and it's the one
|
||||||
|
// dialing, as opposed to net/http.
|
||||||
|
if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Otherwise, only use the GetConn hook if this connection has
|
||||||
|
// been used previously for other requests. For fresh
|
||||||
|
// connections, the net/http package does the dialing.
|
||||||
|
return !st.freshConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
|
||||||
|
if isConnectionCloseRequest(req) && dialOnMiss {
|
||||||
|
// It gets its own connection.
|
||||||
|
traceGetConn(req, addr)
|
||||||
|
const singleUse = true
|
||||||
|
cc, err := p.t.dialClientConn(addr, singleUse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, cc := range p.conns[addr] {
|
||||||
|
if st := cc.idleState(); st.canTakeNewRequest {
|
||||||
|
if p.shouldTraceGetConn(st) {
|
||||||
|
traceGetConn(req, addr)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !dialOnMiss {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil, ErrNoCachedConn
|
||||||
|
}
|
||||||
|
traceGetConn(req, addr)
|
||||||
|
call := p.getStartDialLocked(addr)
|
||||||
|
p.mu.Unlock()
|
||||||
|
<-call.done
|
||||||
|
return call.res, call.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialCall is an in-flight Transport dial call to a host.
|
||||||
|
type dialCall struct {
|
||||||
|
_ incomparable
|
||||||
|
p *clientConnPool
|
||||||
|
done chan struct{} // closed when done
|
||||||
|
res *ClientConn // valid after done is closed
|
||||||
|
err error // valid after done is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires p.mu is held.
|
||||||
|
func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
|
||||||
|
if call, ok := p.dialing[addr]; ok {
|
||||||
|
// A dial is already in-flight. Don't start another.
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
call := &dialCall{p: p, done: make(chan struct{})}
|
||||||
|
if p.dialing == nil {
|
||||||
|
p.dialing = make(map[string]*dialCall)
|
||||||
|
}
|
||||||
|
p.dialing[addr] = call
|
||||||
|
go call.dial(addr)
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
|
||||||
|
// run in its own goroutine.
|
||||||
|
func (c *dialCall) dial(addr string) {
|
||||||
|
const singleUse = false // shared conn
|
||||||
|
c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
|
||||||
|
close(c.done)
|
||||||
|
|
||||||
|
c.p.mu.Lock()
|
||||||
|
delete(c.p.dialing, addr)
|
||||||
|
if c.err == nil {
|
||||||
|
c.p.addConnLocked(addr, c.res)
|
||||||
|
}
|
||||||
|
c.p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
|
||||||
|
// already exist. It coalesces concurrent calls with the same key.
|
||||||
|
// This is used by the http1 Transport code when it creates a new connection. Because
|
||||||
|
// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
|
||||||
|
// the protocol), it can get into a situation where it has multiple TLS connections.
|
||||||
|
// This code decides which ones live or die.
|
||||||
|
// The return value used is whether c was used.
|
||||||
|
// c is never closed.
|
||||||
|
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, cc := range p.conns[key] {
|
||||||
|
if cc.CanTakeNewRequest() {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
call, dup := p.addConnCalls[key]
|
||||||
|
if !dup {
|
||||||
|
if p.addConnCalls == nil {
|
||||||
|
p.addConnCalls = make(map[string]*addConnCall)
|
||||||
|
}
|
||||||
|
call = &addConnCall{
|
||||||
|
p: p,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
p.addConnCalls[key] = call
|
||||||
|
go call.run(t, key, c)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
<-call.done
|
||||||
|
if call.err != nil {
|
||||||
|
return false, call.err
|
||||||
|
}
|
||||||
|
return !dup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type addConnCall struct {
|
||||||
|
_ incomparable
|
||||||
|
p *clientConnPool
|
||||||
|
done chan struct{} // closed when done
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||||
|
cc, err := t.NewClientConn(tc)
|
||||||
|
|
||||||
|
p := c.p
|
||||||
|
p.mu.Lock()
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
} else {
|
||||||
|
p.addConnLocked(key, cc)
|
||||||
|
}
|
||||||
|
delete(p.addConnCalls, key)
|
||||||
|
p.mu.Unlock()
|
||||||
|
close(c.done)
|
||||||
|
}
|
||||||
|
|
||||||
|
// p.mu must be held
|
||||||
|
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||||
|
for _, v := range p.conns[key] {
|
||||||
|
if v == cc {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.conns == nil {
|
||||||
|
p.conns = make(map[string][]*ClientConn)
|
||||||
|
}
|
||||||
|
if p.keys == nil {
|
||||||
|
p.keys = make(map[*ClientConn][]string)
|
||||||
|
}
|
||||||
|
p.conns[key] = append(p.conns[key], cc)
|
||||||
|
p.keys[cc] = append(p.keys[cc], key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) MarkDead(cc *ClientConn) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
for _, key := range p.keys[cc] {
|
||||||
|
vv, ok := p.conns[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newList := filterOutClientConn(vv, cc)
|
||||||
|
if len(newList) > 0 {
|
||||||
|
p.conns[key] = newList
|
||||||
|
} else {
|
||||||
|
delete(p.conns, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(p.keys, cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) closeIdleConnections() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
// TODO: don't close a cc if it was just added to the pool
|
||||||
|
// milliseconds ago and has never been used. There's currently
|
||||||
|
// a small race window with the HTTP/1 Transport's integration
|
||||||
|
// where it can add an idle conn just before using it, and
|
||||||
|
// somebody else can concurrently call CloseIdleConns and
|
||||||
|
// break some caller's RoundTrip.
|
||||||
|
for _, vv := range p.conns {
|
||||||
|
for _, cc := range vv {
|
||||||
|
cc.closeIfIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
|
||||||
|
out := in[:0]
|
||||||
|
for _, v := range in {
|
||||||
|
if v != exclude {
|
||||||
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we filtered it out, zero out the last item to prevent
|
||||||
|
// the GC from seeing it.
|
||||||
|
if len(in) != len(out) {
|
||||||
|
in[len(in)-1] = nil
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// noDialClientConnPool is an implementation of http2.ClientConnPool
|
||||||
|
// which never dials. We let the HTTP/1.1 client dial and use its TLS
|
||||||
|
// connection instead.
|
||||||
|
type noDialClientConnPool struct{ *clientConnPool }
|
||||||
|
|
||||||
|
func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||||
|
return p.getClientConn(req, addr, noDialOnMiss)
|
||||||
|
}
|
146
vendor/golang.org/x/net/http2/databuffer.go
generated
vendored
Normal file
146
vendor/golang.org/x/net/http2/databuffer.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// Copyright 2014 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 http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Buffer chunks are allocated from a pool to reduce pressure on GC.
|
||||||
|
// The maximum wasted space per dataBuffer is 2x the largest size class,
|
||||||
|
// which happens when the dataBuffer has multiple chunks and there is
|
||||||
|
// one unread byte in both the first and last chunks. We use a few size
|
||||||
|
// classes to minimize overheads for servers that typically receive very
|
||||||
|
// small request bodies.
|
||||||
|
//
|
||||||
|
// TODO: Benchmark to determine if the pools are necessary. The GC may have
|
||||||
|
// improved enough that we can instead allocate chunks like this:
|
||||||
|
// make([]byte, max(16<<10, expectedBytesRemaining))
|
||||||
|
var (
|
||||||
|
dataChunkSizeClasses = []int{
|
||||||
|
1 << 10,
|
||||||
|
2 << 10,
|
||||||
|
4 << 10,
|
||||||
|
8 << 10,
|
||||||
|
16 << 10,
|
||||||
|
}
|
||||||
|
dataChunkPools = [...]sync.Pool{
|
||||||
|
{New: func() interface{} { return make([]byte, 1<<10) }},
|
||||||
|
{New: func() interface{} { return make([]byte, 2<<10) }},
|
||||||
|
{New: func() interface{} { return make([]byte, 4<<10) }},
|
||||||
|
{New: func() interface{} { return make([]byte, 8<<10) }},
|
||||||
|
{New: func() interface{} { return make([]byte, 16<<10) }},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDataBufferChunk(size int64) []byte {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(dataChunkSizeClasses)-1; i++ {
|
||||||
|
if size <= int64(dataChunkSizeClasses[i]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dataChunkPools[i].Get().([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putDataBufferChunk(p []byte) {
|
||||||
|
for i, n := range dataChunkSizeClasses {
|
||||||
|
if len(p) == n {
|
||||||
|
dataChunkPools[i].Put(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataBuffer is an io.ReadWriter backed by a list of data chunks.
|
||||||
|
// Each dataBuffer is used to read DATA frames on a single stream.
|
||||||
|
// The buffer is divided into chunks so the server can limit the
|
||||||
|
// total memory used by a single connection without limiting the
|
||||||
|
// request body size on any single stream.
|
||||||
|
type dataBuffer struct {
|
||||||
|
chunks [][]byte
|
||||||
|
r int // next byte to read is chunks[0][r]
|
||||||
|
w int // next byte to write is chunks[len(chunks)-1][w]
|
||||||
|
size int // total buffered bytes
|
||||||
|
expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errReadEmpty = errors.New("read from empty dataBuffer")
|
||||||
|
|
||||||
|
// Read copies bytes from the buffer into p.
|
||||||
|
// It is an error to read when no data is available.
|
||||||
|
func (b *dataBuffer) Read(p []byte) (int, error) {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0, errReadEmpty
|
||||||
|
}
|
||||||
|
var ntotal int
|
||||||
|
for len(p) > 0 && b.size > 0 {
|
||||||
|
readFrom := b.bytesFromFirstChunk()
|
||||||
|
n := copy(p, readFrom)
|
||||||
|
p = p[n:]
|
||||||
|
ntotal += n
|
||||||
|
b.r += n
|
||||||
|
b.size -= n
|
||||||
|
// If the first chunk has been consumed, advance to the next chunk.
|
||||||
|
if b.r == len(b.chunks[0]) {
|
||||||
|
putDataBufferChunk(b.chunks[0])
|
||||||
|
end := len(b.chunks) - 1
|
||||||
|
copy(b.chunks[:end], b.chunks[1:])
|
||||||
|
b.chunks[end] = nil
|
||||||
|
b.chunks = b.chunks[:end]
|
||||||
|
b.r = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ntotal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *dataBuffer) bytesFromFirstChunk() []byte {
|
||||||
|
if len(b.chunks) == 1 {
|
||||||
|
return b.chunks[0][b.r:b.w]
|
||||||
|
}
|
||||||
|
return b.chunks[0][b.r:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of bytes of the unread portion of the buffer.
|
||||||
|
func (b *dataBuffer) Len() int {
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write appends p to the buffer.
|
||||||
|
func (b *dataBuffer) Write(p []byte) (int, error) {
|
||||||
|
ntotal := len(p)
|
||||||
|
for len(p) > 0 {
|
||||||
|
// If the last chunk is empty, allocate a new chunk. Try to allocate
|
||||||
|
// enough to fully copy p plus any additional bytes we expect to
|
||||||
|
// receive. However, this may allocate less than len(p).
|
||||||
|
want := int64(len(p))
|
||||||
|
if b.expected > want {
|
||||||
|
want = b.expected
|
||||||
|
}
|
||||||
|
chunk := b.lastChunkOrAlloc(want)
|
||||||
|
n := copy(chunk[b.w:], p)
|
||||||
|
p = p[n:]
|
||||||
|
b.w += n
|
||||||
|
b.size += n
|
||||||
|
b.expected -= int64(n)
|
||||||
|
}
|
||||||
|
return ntotal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte {
|
||||||
|
if len(b.chunks) != 0 {
|
||||||
|
last := b.chunks[len(b.chunks)-1]
|
||||||
|
if b.w < len(last) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk := getDataBufferChunk(want)
|
||||||
|
b.chunks = append(b.chunks, chunk)
|
||||||
|
b.w = 0
|
||||||
|
return chunk
|
||||||
|
}
|
133
vendor/golang.org/x/net/http2/errors.go
generated
vendored
Normal file
133
vendor/golang.org/x/net/http2/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2014 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 http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec.
|
||||||
|
type ErrCode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrCodeNo ErrCode = 0x0
|
||||||
|
ErrCodeProtocol ErrCode = 0x1
|
||||||
|
ErrCodeInternal ErrCode = 0x2
|
||||||
|
ErrCodeFlowControl ErrCode = 0x3
|
||||||
|
ErrCodeSettingsTimeout ErrCode = 0x4
|
||||||
|
ErrCodeStreamClosed ErrCode = 0x5
|
||||||
|
ErrCodeFrameSize ErrCode = 0x6
|
||||||
|
ErrCodeRefusedStream ErrCode = 0x7
|
||||||
|
ErrCodeCancel ErrCode = 0x8
|
||||||
|
ErrCodeCompression ErrCode = 0x9
|
||||||
|
ErrCodeConnect ErrCode = 0xa
|
||||||
|
ErrCodeEnhanceYourCalm ErrCode = 0xb
|
||||||
|
ErrCodeInadequateSecurity ErrCode = 0xc
|
||||||
|
ErrCodeHTTP11Required ErrCode = 0xd
|
||||||
|
)
|
||||||
|
|
||||||
|
var errCodeName = map[ErrCode]string{
|
||||||
|
ErrCodeNo: "NO_ERROR",
|
||||||
|
ErrCodeProtocol: "PROTOCOL_ERROR",
|
||||||
|
ErrCodeInternal: "INTERNAL_ERROR",
|
||||||
|
ErrCodeFlowControl: "FLOW_CONTROL_ERROR",
|
||||||
|
ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT",
|
||||||
|
ErrCodeStreamClosed: "STREAM_CLOSED",
|
||||||
|
ErrCodeFrameSize: "FRAME_SIZE_ERROR",
|
||||||
|
ErrCodeRefusedStream: "REFUSED_STREAM",
|
||||||
|
ErrCodeCancel: "CANCEL",
|
||||||
|
ErrCodeCompression: "COMPRESSION_ERROR",
|
||||||
|
ErrCodeConnect: "CONNECT_ERROR",
|
||||||
|
ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM",
|
||||||
|
ErrCodeInadequateSecurity: "INADEQUATE_SECURITY",
|
||||||
|
ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrCode) String() string {
|
||||||
|
if s, ok := errCodeName[e]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown error code 0x%x", uint32(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectionError is an error that results in the termination of the
|
||||||
|
// entire connection.
|
||||||
|
type ConnectionError ErrCode
|
||||||
|
|
||||||
|
func (e ConnectionError) Error() string { return fmt.Sprintf("connection error: %s", ErrCode(e)) }
|
||||||
|
|
||||||
|
// StreamError is an error that only affects one stream within an
|
||||||
|
// HTTP/2 connection.
|
||||||
|
type StreamError struct {
|
||||||
|
StreamID uint32
|
||||||
|
Code ErrCode
|
||||||
|
Cause error // optional additional detail
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamError(id uint32, code ErrCode) StreamError {
|
||||||
|
return StreamError{StreamID: id, Code: code}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e StreamError) Error() string {
|
||||||
|
if e.Cause != nil {
|
||||||
|
return fmt.Sprintf("stream error: stream ID %d; %v; %v", e.StreamID, e.Code, e.Cause)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6.9.1 The Flow Control Window
|
||||||
|
// "If a sender receives a WINDOW_UPDATE that causes a flow control
|
||||||
|
// window to exceed this maximum it MUST terminate either the stream
|
||||||
|
// or the connection, as appropriate. For streams, [...]; for the
|
||||||
|
// connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code."
|
||||||
|
type goAwayFlowError struct{}
|
||||||
|
|
||||||
|
func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" }
|
||||||
|
|
||||||
|
// connError represents an HTTP/2 ConnectionError error code, along
|
||||||
|
// with a string (for debugging) explaining why.
|
||||||
|
//
|
||||||
|
// Errors of this type are only returned by the frame parser functions
|
||||||
|
// and converted into ConnectionError(Code), after stashing away
|
||||||
|
// the Reason into the Framer's errDetail field, accessible via
|
||||||
|
// the (*Framer).ErrorDetail method.
|
||||||
|
type connError struct {
|
||||||
|
Code ErrCode // the ConnectionError error code
|
||||||
|
Reason string // additional reason
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e connError) Error() string {
|
||||||
|
return fmt.Sprintf("http2: connection error: %v: %v", e.Code, e.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pseudoHeaderError string
|
||||||
|
|
||||||
|
func (e pseudoHeaderError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid pseudo-header %q", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
type duplicatePseudoHeaderError string
|
||||||
|
|
||||||
|
func (e duplicatePseudoHeaderError) Error() string {
|
||||||
|
return fmt.Sprintf("duplicate pseudo-header %q", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerFieldNameError string
|
||||||
|
|
||||||
|
func (e headerFieldNameError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid header field name %q", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerFieldValueError string
|
||||||
|
|
||||||
|
func (e headerFieldValueError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid header field value %q", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errMixPseudoHeaderTypes = errors.New("mix of request and response pseudo headers")
|
||||||
|
errPseudoAfterRegular = errors.New("pseudo header field after regular")
|
||||||
|
)
|
52
vendor/golang.org/x/net/http2/flow.go
generated
vendored
Normal file
52
vendor/golang.org/x/net/http2/flow.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// Flow control
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
// flow is the flow control window's size.
|
||||||
|
type flow struct {
|
||||||
|
_ incomparable
|
||||||
|
|
||||||
|
// n is the number of DATA bytes we're allowed to send.
|
||||||
|
// A flow is kept both on a conn and a per-stream.
|
||||||
|
n int32
|
||||||
|
|
||||||
|
// conn points to the shared connection-level flow that is
|
||||||
|
// shared by all streams on that conn. It is nil for the flow
|
||||||
|
// that's on the conn directly.
|
||||||
|
conn *flow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flow) setConnFlow(cf *flow) { f.conn = cf }
|
||||||
|
|
||||||
|
func (f *flow) available() int32 {
|
||||||
|
n := f.n
|
||||||
|
if f.conn != nil && f.conn.n < n {
|
||||||
|
n = f.conn.n
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flow) take(n int32) {
|
||||||
|
if n > f.available() {
|
||||||
|
panic("internal error: took too much")
|
||||||
|
}
|
||||||
|
f.n -= n
|
||||||
|
if f.conn != nil {
|
||||||
|
f.conn.n -= n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds n bytes (positive or negative) to the flow control window.
|
||||||
|
// It returns false if the sum would exceed 2^31-1.
|
||||||
|
func (f *flow) add(n int32) bool {
|
||||||
|
sum := f.n + n
|
||||||
|
if (sum > n) == (f.n > 0) {
|
||||||
|
f.n = sum
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
1614
vendor/golang.org/x/net/http2/frame.go
generated
vendored
Normal file
1614
vendor/golang.org/x/net/http2/frame.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
29
vendor/golang.org/x/net/http2/go111.go
generated
vendored
Normal file
29
vendor/golang.org/x/net/http2/go111.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.11
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptrace"
|
||||||
|
"net/textproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
|
||||||
|
return trace != nil && trace.WroteHeaderField != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
|
||||||
|
if trace != nil && trace.WroteHeaderField != nil {
|
||||||
|
trace.WroteHeaderField(k, []string{v})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
|
||||||
|
if trace != nil {
|
||||||
|
return trace.Got1xxResponse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
170
vendor/golang.org/x/net/http2/gotrack.go
generated
vendored
Normal file
170
vendor/golang.org/x/net/http2/gotrack.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// Defensive debug-only utility to track that functions run on the
|
||||||
|
// goroutine that they're supposed to.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
|
||||||
|
|
||||||
|
type goroutineLock uint64
|
||||||
|
|
||||||
|
func newGoroutineLock() goroutineLock {
|
||||||
|
if !DebugGoroutines {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return goroutineLock(curGoroutineID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g goroutineLock) check() {
|
||||||
|
if !DebugGoroutines {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if curGoroutineID() != uint64(g) {
|
||||||
|
panic("running on the wrong goroutine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g goroutineLock) checkNotOn() {
|
||||||
|
if !DebugGoroutines {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if curGoroutineID() == uint64(g) {
|
||||||
|
panic("running on the wrong goroutine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var goroutineSpace = []byte("goroutine ")
|
||||||
|
|
||||||
|
func curGoroutineID() uint64 {
|
||||||
|
bp := littleBuf.Get().(*[]byte)
|
||||||
|
defer littleBuf.Put(bp)
|
||||||
|
b := *bp
|
||||||
|
b = b[:runtime.Stack(b, false)]
|
||||||
|
// Parse the 4707 out of "goroutine 4707 ["
|
||||||
|
b = bytes.TrimPrefix(b, goroutineSpace)
|
||||||
|
i := bytes.IndexByte(b, ' ')
|
||||||
|
if i < 0 {
|
||||||
|
panic(fmt.Sprintf("No space found in %q", b))
|
||||||
|
}
|
||||||
|
b = b[:i]
|
||||||
|
n, err := parseUintBytes(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
var littleBuf = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buf := make([]byte, 64)
|
||||||
|
return &buf
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUintBytes is like strconv.ParseUint, but using a []byte.
|
||||||
|
func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
|
||||||
|
var cutoff, maxVal uint64
|
||||||
|
|
||||||
|
if bitSize == 0 {
|
||||||
|
bitSize = int(strconv.IntSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
s0 := s
|
||||||
|
switch {
|
||||||
|
case len(s) < 1:
|
||||||
|
err = strconv.ErrSyntax
|
||||||
|
goto Error
|
||||||
|
|
||||||
|
case 2 <= base && base <= 36:
|
||||||
|
// valid base; nothing to do
|
||||||
|
|
||||||
|
case base == 0:
|
||||||
|
// Look for octal, hex prefix.
|
||||||
|
switch {
|
||||||
|
case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
|
||||||
|
base = 16
|
||||||
|
s = s[2:]
|
||||||
|
if len(s) < 1 {
|
||||||
|
err = strconv.ErrSyntax
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
case s[0] == '0':
|
||||||
|
base = 8
|
||||||
|
default:
|
||||||
|
base = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = errors.New("invalid base " + strconv.Itoa(base))
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
cutoff = cutoff64(base)
|
||||||
|
maxVal = 1<<uint(bitSize) - 1
|
||||||
|
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
var v byte
|
||||||
|
d := s[i]
|
||||||
|
switch {
|
||||||
|
case '0' <= d && d <= '9':
|
||||||
|
v = d - '0'
|
||||||
|
case 'a' <= d && d <= 'z':
|
||||||
|
v = d - 'a' + 10
|
||||||
|
case 'A' <= d && d <= 'Z':
|
||||||
|
v = d - 'A' + 10
|
||||||
|
default:
|
||||||
|
n = 0
|
||||||
|
err = strconv.ErrSyntax
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
if int(v) >= base {
|
||||||
|
n = 0
|
||||||
|
err = strconv.ErrSyntax
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if n >= cutoff {
|
||||||
|
// n*base overflows
|
||||||
|
n = 1<<64 - 1
|
||||||
|
err = strconv.ErrRange
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
n *= uint64(base)
|
||||||
|
|
||||||
|
n1 := n + uint64(v)
|
||||||
|
if n1 < n || n1 > maxVal {
|
||||||
|
// n+v overflows
|
||||||
|
n = 1<<64 - 1
|
||||||
|
err = strconv.ErrRange
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
n = n1
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
|
||||||
|
Error:
|
||||||
|
return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the first number n such that n*base >= 1<<64.
|
||||||
|
func cutoff64(base int) uint64 {
|
||||||
|
if base < 2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (1<<64-1)/uint64(base) + 1
|
||||||
|
}
|
88
vendor/golang.org/x/net/http2/headermap.go
generated
vendored
Normal file
88
vendor/golang.org/x/net/http2/headermap.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2014 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 http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commonBuildOnce sync.Once
|
||||||
|
commonLowerHeader map[string]string // Go-Canonical-Case -> lower-case
|
||||||
|
commonCanonHeader map[string]string // lower-case -> Go-Canonical-Case
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildCommonHeaderMapsOnce() {
|
||||||
|
commonBuildOnce.Do(buildCommonHeaderMaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCommonHeaderMaps() {
|
||||||
|
common := []string{
|
||||||
|
"accept",
|
||||||
|
"accept-charset",
|
||||||
|
"accept-encoding",
|
||||||
|
"accept-language",
|
||||||
|
"accept-ranges",
|
||||||
|
"age",
|
||||||
|
"access-control-allow-origin",
|
||||||
|
"allow",
|
||||||
|
"authorization",
|
||||||
|
"cache-control",
|
||||||
|
"content-disposition",
|
||||||
|
"content-encoding",
|
||||||
|
"content-language",
|
||||||
|
"content-length",
|
||||||
|
"content-location",
|
||||||
|
"content-range",
|
||||||
|
"content-type",
|
||||||
|
"cookie",
|
||||||
|
"date",
|
||||||
|
"etag",
|
||||||
|
"expect",
|
||||||
|
"expires",
|
||||||
|
"from",
|
||||||
|
"host",
|
||||||
|
"if-match",
|
||||||
|
"if-modified-since",
|
||||||
|
"if-none-match",
|
||||||
|
"if-unmodified-since",
|
||||||
|
"last-modified",
|
||||||
|
"link",
|
||||||
|
"location",
|
||||||
|
"max-forwards",
|
||||||
|
"proxy-authenticate",
|
||||||
|
"proxy-authorization",
|
||||||
|
"range",
|
||||||
|
"referer",
|
||||||
|
"refresh",
|
||||||
|
"retry-after",
|
||||||
|
"server",
|
||||||
|
"set-cookie",
|
||||||
|
"strict-transport-security",
|
||||||
|
"trailer",
|
||||||
|
"transfer-encoding",
|
||||||
|
"user-agent",
|
||||||
|
"vary",
|
||||||
|
"via",
|
||||||
|
"www-authenticate",
|
||||||
|
}
|
||||||
|
commonLowerHeader = make(map[string]string, len(common))
|
||||||
|
commonCanonHeader = make(map[string]string, len(common))
|
||||||
|
for _, v := range common {
|
||||||
|
chk := http.CanonicalHeaderKey(v)
|
||||||
|
commonLowerHeader[chk] = v
|
||||||
|
commonCanonHeader[v] = chk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowerHeader(v string) string {
|
||||||
|
buildCommonHeaderMapsOnce()
|
||||||
|
if s, ok := commonLowerHeader[v]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strings.ToLower(v)
|
||||||
|
}
|
240
vendor/golang.org/x/net/http2/hpack/encode.go
generated
vendored
Normal file
240
vendor/golang.org/x/net/http2/hpack/encode.go
generated
vendored
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2014 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 hpack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
uint32Max = ^uint32(0)
|
||||||
|
initialHeaderTableSize = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
type Encoder struct {
|
||||||
|
dynTab dynamicTable
|
||||||
|
// minSize is the minimum table size set by
|
||||||
|
// SetMaxDynamicTableSize after the previous Header Table Size
|
||||||
|
// Update.
|
||||||
|
minSize uint32
|
||||||
|
// maxSizeLimit is the maximum table size this encoder
|
||||||
|
// supports. This will protect the encoder from too large
|
||||||
|
// size.
|
||||||
|
maxSizeLimit uint32
|
||||||
|
// tableSizeUpdate indicates whether "Header Table Size
|
||||||
|
// Update" is required.
|
||||||
|
tableSizeUpdate bool
|
||||||
|
w io.Writer
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new Encoder which performs HPACK encoding. An
|
||||||
|
// encoded data is written to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
e := &Encoder{
|
||||||
|
minSize: uint32Max,
|
||||||
|
maxSizeLimit: initialHeaderTableSize,
|
||||||
|
tableSizeUpdate: false,
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
e.dynTab.table.init()
|
||||||
|
e.dynTab.setMaxSize(initialHeaderTableSize)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteField encodes f into a single Write to e's underlying Writer.
|
||||||
|
// This function may also produce bytes for "Header Table Size Update"
|
||||||
|
// if necessary. If produced, it is done before encoding f.
|
||||||
|
func (e *Encoder) WriteField(f HeaderField) error {
|
||||||
|
e.buf = e.buf[:0]
|
||||||
|
|
||||||
|
if e.tableSizeUpdate {
|
||||||
|
e.tableSizeUpdate = false
|
||||||
|
if e.minSize < e.dynTab.maxSize {
|
||||||
|
e.buf = appendTableSize(e.buf, e.minSize)
|
||||||
|
}
|
||||||
|
e.minSize = uint32Max
|
||||||
|
e.buf = appendTableSize(e.buf, e.dynTab.maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, nameValueMatch := e.searchTable(f)
|
||||||
|
if nameValueMatch {
|
||||||
|
e.buf = appendIndexed(e.buf, idx)
|
||||||
|
} else {
|
||||||
|
indexing := e.shouldIndex(f)
|
||||||
|
if indexing {
|
||||||
|
e.dynTab.add(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx == 0 {
|
||||||
|
e.buf = appendNewName(e.buf, f, indexing)
|
||||||
|
} else {
|
||||||
|
e.buf = appendIndexedName(e.buf, f, idx, indexing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := e.w.Write(e.buf)
|
||||||
|
if err == nil && n != len(e.buf) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchTable searches f in both stable and dynamic header tables.
|
||||||
|
// The static header table is searched first. Only when there is no
|
||||||
|
// exact match for both name and value, the dynamic header table is
|
||||||
|
// then searched. If there is no match, i is 0. If both name and value
|
||||||
|
// match, i is the matched index and nameValueMatch becomes true. If
|
||||||
|
// only name matches, i points to that index and nameValueMatch
|
||||||
|
// becomes false.
|
||||||
|
func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
|
||||||
|
i, nameValueMatch = staticTable.search(f)
|
||||||
|
if nameValueMatch {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
|
||||||
|
j, nameValueMatch := e.dynTab.table.search(f)
|
||||||
|
if nameValueMatch || (i == 0 && j != 0) {
|
||||||
|
return j + uint64(staticTable.len()), nameValueMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxDynamicTableSize changes the dynamic header table size to v.
|
||||||
|
// The actual size is bounded by the value passed to
|
||||||
|
// SetMaxDynamicTableSizeLimit.
|
||||||
|
func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
|
||||||
|
if v > e.maxSizeLimit {
|
||||||
|
v = e.maxSizeLimit
|
||||||
|
}
|
||||||
|
if v < e.minSize {
|
||||||
|
e.minSize = v
|
||||||
|
}
|
||||||
|
e.tableSizeUpdate = true
|
||||||
|
e.dynTab.setMaxSize(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxDynamicTableSizeLimit changes the maximum value that can be
|
||||||
|
// specified in SetMaxDynamicTableSize to v. By default, it is set to
|
||||||
|
// 4096, which is the same size of the default dynamic header table
|
||||||
|
// size described in HPACK specification. If the current maximum
|
||||||
|
// dynamic header table size is strictly greater than v, "Header Table
|
||||||
|
// Size Update" will be done in the next WriteField call and the
|
||||||
|
// maximum dynamic header table size is truncated to v.
|
||||||
|
func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) {
|
||||||
|
e.maxSizeLimit = v
|
||||||
|
if e.dynTab.maxSize > v {
|
||||||
|
e.tableSizeUpdate = true
|
||||||
|
e.dynTab.setMaxSize(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldIndex reports whether f should be indexed.
|
||||||
|
func (e *Encoder) shouldIndex(f HeaderField) bool {
|
||||||
|
return !f.Sensitive && f.Size() <= e.dynTab.maxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendIndexed appends index i, as encoded in "Indexed Header Field"
|
||||||
|
// representation, to dst and returns the extended buffer.
|
||||||
|
func appendIndexed(dst []byte, i uint64) []byte {
|
||||||
|
first := len(dst)
|
||||||
|
dst = appendVarInt(dst, 7, i)
|
||||||
|
dst[first] |= 0x80
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendNewName appends f, as encoded in one of "Literal Header field
|
||||||
|
// - New Name" representation variants, to dst and returns the
|
||||||
|
// extended buffer.
|
||||||
|
//
|
||||||
|
// If f.Sensitive is true, "Never Indexed" representation is used. If
|
||||||
|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
|
||||||
|
// representation is used.
|
||||||
|
func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
|
||||||
|
dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
|
||||||
|
dst = appendHpackString(dst, f.Name)
|
||||||
|
return appendHpackString(dst, f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendIndexedName appends f and index i referring indexed name
|
||||||
|
// entry, as encoded in one of "Literal Header field - Indexed Name"
|
||||||
|
// representation variants, to dst and returns the extended buffer.
|
||||||
|
//
|
||||||
|
// If f.Sensitive is true, "Never Indexed" representation is used. If
|
||||||
|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
|
||||||
|
// representation is used.
|
||||||
|
func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte {
|
||||||
|
first := len(dst)
|
||||||
|
var n byte
|
||||||
|
if indexing {
|
||||||
|
n = 6
|
||||||
|
} else {
|
||||||
|
n = 4
|
||||||
|
}
|
||||||
|
dst = appendVarInt(dst, n, i)
|
||||||
|
dst[first] |= encodeTypeByte(indexing, f.Sensitive)
|
||||||
|
return appendHpackString(dst, f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendTableSize appends v, as encoded in "Header Table Size Update"
|
||||||
|
// representation, to dst and returns the extended buffer.
|
||||||
|
func appendTableSize(dst []byte, v uint32) []byte {
|
||||||
|
first := len(dst)
|
||||||
|
dst = appendVarInt(dst, 5, uint64(v))
|
||||||
|
dst[first] |= 0x20
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendVarInt appends i, as encoded in variable integer form using n
|
||||||
|
// bit prefix, to dst and returns the extended buffer.
|
||||||
|
//
|
||||||
|
// See
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#integer.representation
|
||||||
|
func appendVarInt(dst []byte, n byte, i uint64) []byte {
|
||||||
|
k := uint64((1 << n) - 1)
|
||||||
|
if i < k {
|
||||||
|
return append(dst, byte(i))
|
||||||
|
}
|
||||||
|
dst = append(dst, byte(k))
|
||||||
|
i -= k
|
||||||
|
for ; i >= 128; i >>= 7 {
|
||||||
|
dst = append(dst, byte(0x80|(i&0x7f)))
|
||||||
|
}
|
||||||
|
return append(dst, byte(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendHpackString appends s, as encoded in "String Literal"
|
||||||
|
// representation, to dst and returns the extended buffer.
|
||||||
|
//
|
||||||
|
// s will be encoded in Huffman codes only when it produces strictly
|
||||||
|
// shorter byte string.
|
||||||
|
func appendHpackString(dst []byte, s string) []byte {
|
||||||
|
huffmanLength := HuffmanEncodeLength(s)
|
||||||
|
if huffmanLength < uint64(len(s)) {
|
||||||
|
first := len(dst)
|
||||||
|
dst = appendVarInt(dst, 7, huffmanLength)
|
||||||
|
dst = AppendHuffmanString(dst, s)
|
||||||
|
dst[first] |= 0x80
|
||||||
|
} else {
|
||||||
|
dst = appendVarInt(dst, 7, uint64(len(s)))
|
||||||
|
dst = append(dst, s...)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeTypeByte returns type byte. If sensitive is true, type byte
|
||||||
|
// for "Never Indexed" representation is returned. If sensitive is
|
||||||
|
// false and indexing is true, type byte for "Incremental Indexing"
|
||||||
|
// representation is returned. Otherwise, type byte for "Without
|
||||||
|
// Indexing" is returned.
|
||||||
|
func encodeTypeByte(indexing, sensitive bool) byte {
|
||||||
|
if sensitive {
|
||||||
|
return 0x10
|
||||||
|
}
|
||||||
|
if indexing {
|
||||||
|
return 0x40
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
504
vendor/golang.org/x/net/http2/hpack/hpack.go
generated
vendored
Normal file
504
vendor/golang.org/x/net/http2/hpack/hpack.go
generated
vendored
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
// Copyright 2014 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 hpack implements HPACK, a compression format for
|
||||||
|
// efficiently representing HTTP header fields in the context of HTTP/2.
|
||||||
|
//
|
||||||
|
// See http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
|
||||||
|
package hpack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A DecodingError is something the spec defines as a decoding error.
|
||||||
|
type DecodingError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (de DecodingError) Error() string {
|
||||||
|
return fmt.Sprintf("decoding error: %v", de.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InvalidIndexError is returned when an encoder references a table
|
||||||
|
// entry before the static table or after the end of the dynamic table.
|
||||||
|
type InvalidIndexError int
|
||||||
|
|
||||||
|
func (e InvalidIndexError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid indexed representation index %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A HeaderField is a name-value pair. Both the name and value are
|
||||||
|
// treated as opaque sequences of octets.
|
||||||
|
type HeaderField struct {
|
||||||
|
Name, Value string
|
||||||
|
|
||||||
|
// Sensitive means that this header field should never be
|
||||||
|
// indexed.
|
||||||
|
Sensitive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPseudo reports whether the header field is an http2 pseudo header.
|
||||||
|
// That is, it reports whether it starts with a colon.
|
||||||
|
// It is not otherwise guaranteed to be a valid pseudo header field,
|
||||||
|
// though.
|
||||||
|
func (hf HeaderField) IsPseudo() bool {
|
||||||
|
return len(hf.Name) != 0 && hf.Name[0] == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hf HeaderField) String() string {
|
||||||
|
var suffix string
|
||||||
|
if hf.Sensitive {
|
||||||
|
suffix = " (sensitive)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("header field %q = %q%s", hf.Name, hf.Value, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of an entry per RFC 7541 section 4.1.
|
||||||
|
func (hf HeaderField) Size() uint32 {
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.4.1
|
||||||
|
// "The size of the dynamic table is the sum of the size of
|
||||||
|
// its entries. The size of an entry is the sum of its name's
|
||||||
|
// length in octets (as defined in Section 5.2), its value's
|
||||||
|
// length in octets (see Section 5.2), plus 32. The size of
|
||||||
|
// an entry is calculated using the length of the name and
|
||||||
|
// value without any Huffman encoding applied."
|
||||||
|
|
||||||
|
// This can overflow if somebody makes a large HeaderField
|
||||||
|
// Name and/or Value by hand, but we don't care, because that
|
||||||
|
// won't happen on the wire because the encoding doesn't allow
|
||||||
|
// it.
|
||||||
|
return uint32(len(hf.Name) + len(hf.Value) + 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Decoder is the decoding context for incremental processing of
|
||||||
|
// header blocks.
|
||||||
|
type Decoder struct {
|
||||||
|
dynTab dynamicTable
|
||||||
|
emit func(f HeaderField)
|
||||||
|
|
||||||
|
emitEnabled bool // whether calls to emit are enabled
|
||||||
|
maxStrLen int // 0 means unlimited
|
||||||
|
|
||||||
|
// buf is the unparsed buffer. It's only written to
|
||||||
|
// saveBuf if it was truncated in the middle of a header
|
||||||
|
// block. Because it's usually not owned, we can only
|
||||||
|
// process it under Write.
|
||||||
|
buf []byte // not owned; only valid during Write
|
||||||
|
|
||||||
|
// saveBuf is previous data passed to Write which we weren't able
|
||||||
|
// to fully parse before. Unlike buf, we own this data.
|
||||||
|
saveBuf bytes.Buffer
|
||||||
|
|
||||||
|
firstField bool // processing the first field of the header block
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder with the provided maximum dynamic
|
||||||
|
// table size. The emitFunc will be called for each valid field
|
||||||
|
// parsed, in the same goroutine as calls to Write, before Write returns.
|
||||||
|
func NewDecoder(maxDynamicTableSize uint32, emitFunc func(f HeaderField)) *Decoder {
|
||||||
|
d := &Decoder{
|
||||||
|
emit: emitFunc,
|
||||||
|
emitEnabled: true,
|
||||||
|
firstField: true,
|
||||||
|
}
|
||||||
|
d.dynTab.table.init()
|
||||||
|
d.dynTab.allowedMaxSize = maxDynamicTableSize
|
||||||
|
d.dynTab.setMaxSize(maxDynamicTableSize)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrStringLength is returned by Decoder.Write when the max string length
|
||||||
|
// (as configured by Decoder.SetMaxStringLength) would be violated.
|
||||||
|
var ErrStringLength = errors.New("hpack: string too long")
|
||||||
|
|
||||||
|
// SetMaxStringLength sets the maximum size of a HeaderField name or
|
||||||
|
// value string. If a string exceeds this length (even after any
|
||||||
|
// decompression), Write will return ErrStringLength.
|
||||||
|
// A value of 0 means unlimited and is the default from NewDecoder.
|
||||||
|
func (d *Decoder) SetMaxStringLength(n int) {
|
||||||
|
d.maxStrLen = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEmitFunc changes the callback used when new header fields
|
||||||
|
// are decoded.
|
||||||
|
// It must be non-nil. It does not affect EmitEnabled.
|
||||||
|
func (d *Decoder) SetEmitFunc(emitFunc func(f HeaderField)) {
|
||||||
|
d.emit = emitFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEmitEnabled controls whether the emitFunc provided to NewDecoder
|
||||||
|
// should be called. The default is true.
|
||||||
|
//
|
||||||
|
// This facility exists to let servers enforce MAX_HEADER_LIST_SIZE
|
||||||
|
// while still decoding and keeping in-sync with decoder state, but
|
||||||
|
// without doing unnecessary decompression or generating unnecessary
|
||||||
|
// garbage for header fields past the limit.
|
||||||
|
func (d *Decoder) SetEmitEnabled(v bool) { d.emitEnabled = v }
|
||||||
|
|
||||||
|
// EmitEnabled reports whether calls to the emitFunc provided to NewDecoder
|
||||||
|
// are currently enabled. The default is true.
|
||||||
|
func (d *Decoder) EmitEnabled() bool { return d.emitEnabled }
|
||||||
|
|
||||||
|
// TODO: add method *Decoder.Reset(maxSize, emitFunc) to let callers re-use Decoders and their
|
||||||
|
// underlying buffers for garbage reasons.
|
||||||
|
|
||||||
|
func (d *Decoder) SetMaxDynamicTableSize(v uint32) {
|
||||||
|
d.dynTab.setMaxSize(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAllowedMaxDynamicTableSize sets the upper bound that the encoded
|
||||||
|
// stream (via dynamic table size updates) may set the maximum size
|
||||||
|
// to.
|
||||||
|
func (d *Decoder) SetAllowedMaxDynamicTableSize(v uint32) {
|
||||||
|
d.dynTab.allowedMaxSize = v
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicTable struct {
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
|
||||||
|
table headerFieldTable
|
||||||
|
size uint32 // in bytes
|
||||||
|
maxSize uint32 // current maxSize
|
||||||
|
allowedMaxSize uint32 // maxSize may go up to this, inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *dynamicTable) setMaxSize(v uint32) {
|
||||||
|
dt.maxSize = v
|
||||||
|
dt.evict()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *dynamicTable) add(f HeaderField) {
|
||||||
|
dt.table.addEntry(f)
|
||||||
|
dt.size += f.Size()
|
||||||
|
dt.evict()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're too big, evict old stuff.
|
||||||
|
func (dt *dynamicTable) evict() {
|
||||||
|
var n int
|
||||||
|
for dt.size > dt.maxSize && n < dt.table.len() {
|
||||||
|
dt.size -= dt.table.ents[n].Size()
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
dt.table.evictOldest(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) maxTableIndex() int {
|
||||||
|
// This should never overflow. RFC 7540 Section 6.5.2 limits the size of
|
||||||
|
// the dynamic table to 2^32 bytes, where each entry will occupy more than
|
||||||
|
// one byte. Further, the staticTable has a fixed, small length.
|
||||||
|
return d.dynTab.table.len() + staticTable.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
|
||||||
|
// See Section 2.3.3.
|
||||||
|
if i == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i <= uint64(staticTable.len()) {
|
||||||
|
return staticTable.ents[i-1], true
|
||||||
|
}
|
||||||
|
if i > uint64(d.maxTableIndex()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// In the dynamic table, newer entries have lower indices.
|
||||||
|
// However, dt.ents[0] is the oldest entry. Hence, dt.ents is
|
||||||
|
// the reversed dynamic table.
|
||||||
|
dt := d.dynTab.table
|
||||||
|
return dt.ents[dt.len()-(int(i)-staticTable.len())], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes an entire block.
|
||||||
|
//
|
||||||
|
// TODO: remove this method and make it incremental later? This is
|
||||||
|
// easier for debugging now.
|
||||||
|
func (d *Decoder) DecodeFull(p []byte) ([]HeaderField, error) {
|
||||||
|
var hf []HeaderField
|
||||||
|
saveFunc := d.emit
|
||||||
|
defer func() { d.emit = saveFunc }()
|
||||||
|
d.emit = func(f HeaderField) { hf = append(hf, f) }
|
||||||
|
if _, err := d.Write(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := d.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close declares that the decoding is complete and resets the Decoder
|
||||||
|
// to be reused again for a new header block. If there is any remaining
|
||||||
|
// data in the decoder's buffer, Close returns an error.
|
||||||
|
func (d *Decoder) Close() error {
|
||||||
|
if d.saveBuf.Len() > 0 {
|
||||||
|
d.saveBuf.Reset()
|
||||||
|
return DecodingError{errors.New("truncated headers")}
|
||||||
|
}
|
||||||
|
d.firstField = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
// Prevent state machine CPU attacks (making us redo
|
||||||
|
// work up to the point of finding out we don't have
|
||||||
|
// enough data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Only copy the data if we have to. Optimistically assume
|
||||||
|
// that p will contain a complete header block.
|
||||||
|
if d.saveBuf.Len() == 0 {
|
||||||
|
d.buf = p
|
||||||
|
} else {
|
||||||
|
d.saveBuf.Write(p)
|
||||||
|
d.buf = d.saveBuf.Bytes()
|
||||||
|
d.saveBuf.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(d.buf) > 0 {
|
||||||
|
err = d.parseHeaderFieldRepr()
|
||||||
|
if err == errNeedMore {
|
||||||
|
// Extra paranoia, making sure saveBuf won't
|
||||||
|
// get too large. All the varint and string
|
||||||
|
// reading code earlier should already catch
|
||||||
|
// overlong things and return ErrStringLength,
|
||||||
|
// but keep this as a last resort.
|
||||||
|
const varIntOverhead = 8 // conservative
|
||||||
|
if d.maxStrLen != 0 && int64(len(d.buf)) > 2*(int64(d.maxStrLen)+varIntOverhead) {
|
||||||
|
return 0, ErrStringLength
|
||||||
|
}
|
||||||
|
d.saveBuf.Write(d.buf)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
d.firstField = false
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// errNeedMore is an internal sentinel error value that means the
|
||||||
|
// buffer is truncated and we need to read more data before we can
|
||||||
|
// continue parsing.
|
||||||
|
var errNeedMore = errors.New("need more data")
|
||||||
|
|
||||||
|
type indexType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
indexedTrue indexType = iota
|
||||||
|
indexedFalse
|
||||||
|
indexedNever
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v indexType) indexed() bool { return v == indexedTrue }
|
||||||
|
func (v indexType) sensitive() bool { return v == indexedNever }
|
||||||
|
|
||||||
|
// returns errNeedMore if there isn't enough data available.
|
||||||
|
// any other error is fatal.
|
||||||
|
// consumes d.buf iff it returns nil.
|
||||||
|
// precondition: must be called with len(d.buf) > 0
|
||||||
|
func (d *Decoder) parseHeaderFieldRepr() error {
|
||||||
|
b := d.buf[0]
|
||||||
|
switch {
|
||||||
|
case b&128 != 0:
|
||||||
|
// Indexed representation.
|
||||||
|
// High bit set?
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.1
|
||||||
|
return d.parseFieldIndexed()
|
||||||
|
case b&192 == 64:
|
||||||
|
// 6.2.1 Literal Header Field with Incremental Indexing
|
||||||
|
// 0b10xxxxxx: top two bits are 10
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.1
|
||||||
|
return d.parseFieldLiteral(6, indexedTrue)
|
||||||
|
case b&240 == 0:
|
||||||
|
// 6.2.2 Literal Header Field without Indexing
|
||||||
|
// 0b0000xxxx: top four bits are 0000
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.2
|
||||||
|
return d.parseFieldLiteral(4, indexedFalse)
|
||||||
|
case b&240 == 16:
|
||||||
|
// 6.2.3 Literal Header Field never Indexed
|
||||||
|
// 0b0001xxxx: top four bits are 0001
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.3
|
||||||
|
return d.parseFieldLiteral(4, indexedNever)
|
||||||
|
case b&224 == 32:
|
||||||
|
// 6.3 Dynamic Table Size Update
|
||||||
|
// Top three bits are '001'.
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.3
|
||||||
|
return d.parseDynamicTableSizeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodingError{errors.New("invalid encoding")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (same invariants and behavior as parseHeaderFieldRepr)
|
||||||
|
func (d *Decoder) parseFieldIndexed() error {
|
||||||
|
buf := d.buf
|
||||||
|
idx, buf, err := readVarInt(7, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hf, ok := d.at(idx)
|
||||||
|
if !ok {
|
||||||
|
return DecodingError{InvalidIndexError(idx)}
|
||||||
|
}
|
||||||
|
d.buf = buf
|
||||||
|
return d.callEmit(HeaderField{Name: hf.Name, Value: hf.Value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// (same invariants and behavior as parseHeaderFieldRepr)
|
||||||
|
func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
|
||||||
|
buf := d.buf
|
||||||
|
nameIdx, buf, err := readVarInt(n, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hf HeaderField
|
||||||
|
wantStr := d.emitEnabled || it.indexed()
|
||||||
|
if nameIdx > 0 {
|
||||||
|
ihf, ok := d.at(nameIdx)
|
||||||
|
if !ok {
|
||||||
|
return DecodingError{InvalidIndexError(nameIdx)}
|
||||||
|
}
|
||||||
|
hf.Name = ihf.Name
|
||||||
|
} else {
|
||||||
|
hf.Name, buf, err = d.readString(buf, wantStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hf.Value, buf, err = d.readString(buf, wantStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.buf = buf
|
||||||
|
if it.indexed() {
|
||||||
|
d.dynTab.add(hf)
|
||||||
|
}
|
||||||
|
hf.Sensitive = it.sensitive()
|
||||||
|
return d.callEmit(hf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) callEmit(hf HeaderField) error {
|
||||||
|
if d.maxStrLen != 0 {
|
||||||
|
if len(hf.Name) > d.maxStrLen || len(hf.Value) > d.maxStrLen {
|
||||||
|
return ErrStringLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.emitEnabled {
|
||||||
|
d.emit(hf)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// (same invariants and behavior as parseHeaderFieldRepr)
|
||||||
|
func (d *Decoder) parseDynamicTableSizeUpdate() error {
|
||||||
|
// RFC 7541, sec 4.2: This dynamic table size update MUST occur at the
|
||||||
|
// beginning of the first header block following the change to the dynamic table size.
|
||||||
|
if !d.firstField && d.dynTab.size > 0 {
|
||||||
|
return DecodingError{errors.New("dynamic table size update MUST occur at the beginning of a header block")}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := d.buf
|
||||||
|
size, buf, err := readVarInt(5, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if size > uint64(d.dynTab.allowedMaxSize) {
|
||||||
|
return DecodingError{errors.New("dynamic table size update too large")}
|
||||||
|
}
|
||||||
|
d.dynTab.setMaxSize(uint32(size))
|
||||||
|
d.buf = buf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errVarintOverflow = DecodingError{errors.New("varint integer overflow")}
|
||||||
|
|
||||||
|
// readVarInt reads an unsigned variable length integer off the
|
||||||
|
// beginning of p. n is the parameter as described in
|
||||||
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1.
|
||||||
|
//
|
||||||
|
// n must always be between 1 and 8.
|
||||||
|
//
|
||||||
|
// The returned remain buffer is either a smaller suffix of p, or err != nil.
|
||||||
|
// The error is errNeedMore if p doesn't contain a complete integer.
|
||||||
|
func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) {
|
||||||
|
if n < 1 || n > 8 {
|
||||||
|
panic("bad n")
|
||||||
|
}
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, p, errNeedMore
|
||||||
|
}
|
||||||
|
i = uint64(p[0])
|
||||||
|
if n < 8 {
|
||||||
|
i &= (1 << uint64(n)) - 1
|
||||||
|
}
|
||||||
|
if i < (1<<uint64(n))-1 {
|
||||||
|
return i, p[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
origP := p
|
||||||
|
p = p[1:]
|
||||||
|
var m uint64
|
||||||
|
for len(p) > 0 {
|
||||||
|
b := p[0]
|
||||||
|
p = p[1:]
|
||||||
|
i += uint64(b&127) << m
|
||||||
|
if b&128 == 0 {
|
||||||
|
return i, p, nil
|
||||||
|
}
|
||||||
|
m += 7
|
||||||
|
if m >= 63 { // TODO: proper overflow check. making this up.
|
||||||
|
return 0, origP, errVarintOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, origP, errNeedMore
|
||||||
|
}
|
||||||
|
|
||||||
|
// readString decodes an hpack string from p.
|
||||||
|
//
|
||||||
|
// wantStr is whether s will be used. If false, decompression and
|
||||||
|
// []byte->string garbage are skipped if s will be ignored
|
||||||
|
// anyway. This does mean that huffman decoding errors for non-indexed
|
||||||
|
// strings past the MAX_HEADER_LIST_SIZE are ignored, but the server
|
||||||
|
// is returning an error anyway, and because they're not indexed, the error
|
||||||
|
// won't affect the decoding state.
|
||||||
|
func (d *Decoder) readString(p []byte, wantStr bool) (s string, remain []byte, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return "", p, errNeedMore
|
||||||
|
}
|
||||||
|
isHuff := p[0]&128 != 0
|
||||||
|
strLen, p, err := readVarInt(7, p)
|
||||||
|
if err != nil {
|
||||||
|
return "", p, err
|
||||||
|
}
|
||||||
|
if d.maxStrLen != 0 && strLen > uint64(d.maxStrLen) {
|
||||||
|
return "", nil, ErrStringLength
|
||||||
|
}
|
||||||
|
if uint64(len(p)) < strLen {
|
||||||
|
return "", p, errNeedMore
|
||||||
|
}
|
||||||
|
if !isHuff {
|
||||||
|
if wantStr {
|
||||||
|
s = string(p[:strLen])
|
||||||
|
}
|
||||||
|
return s, p[strLen:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantStr {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset() // don't trust others
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
if err := huffmanDecode(buf, d.maxStrLen, p[:strLen]); err != nil {
|
||||||
|
buf.Reset()
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
s = buf.String()
|
||||||
|
buf.Reset() // be nice to GC
|
||||||
|
}
|
||||||
|
return s, p[strLen:], nil
|
||||||
|
}
|
229
vendor/golang.org/x/net/http2/hpack/huffman.go
generated
vendored
Normal file
229
vendor/golang.org/x/net/http2/hpack/huffman.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
// Copyright 2014 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 hpack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} { return new(bytes.Buffer) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// HuffmanDecode decodes the string in v and writes the expanded
|
||||||
|
// result to w, returning the number of bytes written to w and the
|
||||||
|
// Write call's return value. At most one Write call is made.
|
||||||
|
func HuffmanDecode(w io.Writer, v []byte) (int, error) {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
if err := huffmanDecode(buf, 0, v); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// HuffmanDecodeToString decodes the string in v.
|
||||||
|
func HuffmanDecodeToString(v []byte) (string, error) {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
if err := huffmanDecode(buf, 0, v); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidHuffman is returned for errors found decoding
|
||||||
|
// Huffman-encoded strings.
|
||||||
|
var ErrInvalidHuffman = errors.New("hpack: invalid Huffman-encoded data")
|
||||||
|
|
||||||
|
// huffmanDecode decodes v to buf.
|
||||||
|
// If maxLen is greater than 0, attempts to write more to buf than
|
||||||
|
// maxLen bytes will return ErrStringLength.
|
||||||
|
func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error {
|
||||||
|
rootHuffmanNode := getRootHuffmanNode()
|
||||||
|
n := rootHuffmanNode
|
||||||
|
// cur is the bit buffer that has not been fed into n.
|
||||||
|
// cbits is the number of low order bits in cur that are valid.
|
||||||
|
// sbits is the number of bits of the symbol prefix being decoded.
|
||||||
|
cur, cbits, sbits := uint(0), uint8(0), uint8(0)
|
||||||
|
for _, b := range v {
|
||||||
|
cur = cur<<8 | uint(b)
|
||||||
|
cbits += 8
|
||||||
|
sbits += 8
|
||||||
|
for cbits >= 8 {
|
||||||
|
idx := byte(cur >> (cbits - 8))
|
||||||
|
n = n.children[idx]
|
||||||
|
if n == nil {
|
||||||
|
return ErrInvalidHuffman
|
||||||
|
}
|
||||||
|
if n.children == nil {
|
||||||
|
if maxLen != 0 && buf.Len() == maxLen {
|
||||||
|
return ErrStringLength
|
||||||
|
}
|
||||||
|
buf.WriteByte(n.sym)
|
||||||
|
cbits -= n.codeLen
|
||||||
|
n = rootHuffmanNode
|
||||||
|
sbits = cbits
|
||||||
|
} else {
|
||||||
|
cbits -= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cbits > 0 {
|
||||||
|
n = n.children[byte(cur<<(8-cbits))]
|
||||||
|
if n == nil {
|
||||||
|
return ErrInvalidHuffman
|
||||||
|
}
|
||||||
|
if n.children != nil || n.codeLen > cbits {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if maxLen != 0 && buf.Len() == maxLen {
|
||||||
|
return ErrStringLength
|
||||||
|
}
|
||||||
|
buf.WriteByte(n.sym)
|
||||||
|
cbits -= n.codeLen
|
||||||
|
n = rootHuffmanNode
|
||||||
|
sbits = cbits
|
||||||
|
}
|
||||||
|
if sbits > 7 {
|
||||||
|
// Either there was an incomplete symbol, or overlong padding.
|
||||||
|
// Both are decoding errors per RFC 7541 section 5.2.
|
||||||
|
return ErrInvalidHuffman
|
||||||
|
}
|
||||||
|
if mask := uint(1<<cbits - 1); cur&mask != mask {
|
||||||
|
// Trailing bits must be a prefix of EOS per RFC 7541 section 5.2.
|
||||||
|
return ErrInvalidHuffman
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// incomparable is a zero-width, non-comparable type. Adding it to a struct
|
||||||
|
// makes that struct also non-comparable, and generally doesn't add
|
||||||
|
// any size (as long as it's first).
|
||||||
|
type incomparable [0]func()
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
_ incomparable
|
||||||
|
|
||||||
|
// children is non-nil for internal nodes
|
||||||
|
children *[256]*node
|
||||||
|
|
||||||
|
// The following are only valid if children is nil:
|
||||||
|
codeLen uint8 // number of bits that led to the output of sym
|
||||||
|
sym byte // output symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalNode() *node {
|
||||||
|
return &node{children: new([256]*node)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
buildRootOnce sync.Once
|
||||||
|
lazyRootHuffmanNode *node
|
||||||
|
)
|
||||||
|
|
||||||
|
func getRootHuffmanNode() *node {
|
||||||
|
buildRootOnce.Do(buildRootHuffmanNode)
|
||||||
|
return lazyRootHuffmanNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRootHuffmanNode() {
|
||||||
|
if len(huffmanCodes) != 256 {
|
||||||
|
panic("unexpected size")
|
||||||
|
}
|
||||||
|
lazyRootHuffmanNode = newInternalNode()
|
||||||
|
for i, code := range huffmanCodes {
|
||||||
|
addDecoderNode(byte(i), code, huffmanCodeLen[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDecoderNode(sym byte, code uint32, codeLen uint8) {
|
||||||
|
cur := lazyRootHuffmanNode
|
||||||
|
for codeLen > 8 {
|
||||||
|
codeLen -= 8
|
||||||
|
i := uint8(code >> codeLen)
|
||||||
|
if cur.children[i] == nil {
|
||||||
|
cur.children[i] = newInternalNode()
|
||||||
|
}
|
||||||
|
cur = cur.children[i]
|
||||||
|
}
|
||||||
|
shift := 8 - codeLen
|
||||||
|
start, end := int(uint8(code<<shift)), int(1<<shift)
|
||||||
|
for i := start; i < start+end; i++ {
|
||||||
|
cur.children[i] = &node{sym: sym, codeLen: codeLen}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendHuffmanString appends s, as encoded in Huffman codes, to dst
|
||||||
|
// and returns the extended buffer.
|
||||||
|
func AppendHuffmanString(dst []byte, s string) []byte {
|
||||||
|
rembits := uint8(8)
|
||||||
|
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if rembits == 8 {
|
||||||
|
dst = append(dst, 0)
|
||||||
|
}
|
||||||
|
dst, rembits = appendByteToHuffmanCode(dst, rembits, s[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if rembits < 8 {
|
||||||
|
// special EOS symbol
|
||||||
|
code := uint32(0x3fffffff)
|
||||||
|
nbits := uint8(30)
|
||||||
|
|
||||||
|
t := uint8(code >> (nbits - rembits))
|
||||||
|
dst[len(dst)-1] |= t
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// HuffmanEncodeLength returns the number of bytes required to encode
|
||||||
|
// s in Huffman codes. The result is round up to byte boundary.
|
||||||
|
func HuffmanEncodeLength(s string) uint64 {
|
||||||
|
n := uint64(0)
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
n += uint64(huffmanCodeLen[s[i]])
|
||||||
|
}
|
||||||
|
return (n + 7) / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendByteToHuffmanCode appends Huffman code for c to dst and
|
||||||
|
// returns the extended buffer and the remaining bits in the last
|
||||||
|
// element. The appending is not byte aligned and the remaining bits
|
||||||
|
// in the last element of dst is given in rembits.
|
||||||
|
func appendByteToHuffmanCode(dst []byte, rembits uint8, c byte) ([]byte, uint8) {
|
||||||
|
code := huffmanCodes[c]
|
||||||
|
nbits := huffmanCodeLen[c]
|
||||||
|
|
||||||
|
for {
|
||||||
|
if rembits > nbits {
|
||||||
|
t := uint8(code << (rembits - nbits))
|
||||||
|
dst[len(dst)-1] |= t
|
||||||
|
rembits -= nbits
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
t := uint8(code >> (nbits - rembits))
|
||||||
|
dst[len(dst)-1] |= t
|
||||||
|
|
||||||
|
nbits -= rembits
|
||||||
|
rembits = 8
|
||||||
|
|
||||||
|
if nbits == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
dst = append(dst, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst, rembits
|
||||||
|
}
|
479
vendor/golang.org/x/net/http2/hpack/tables.go
generated
vendored
Normal file
479
vendor/golang.org/x/net/http2/hpack/tables.go
generated
vendored
Normal file
|
@ -0,0 +1,479 @@
|
||||||
|
// Copyright 2014 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 hpack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// headerFieldTable implements a list of HeaderFields.
|
||||||
|
// This is used to implement the static and dynamic tables.
|
||||||
|
type headerFieldTable struct {
|
||||||
|
// For static tables, entries are never evicted.
|
||||||
|
//
|
||||||
|
// For dynamic tables, entries are evicted from ents[0] and added to the end.
|
||||||
|
// Each entry has a unique id that starts at one and increments for each
|
||||||
|
// entry that is added. This unique id is stable across evictions, meaning
|
||||||
|
// it can be used as a pointer to a specific entry. As in hpack, unique ids
|
||||||
|
// are 1-based. The unique id for ents[k] is k + evictCount + 1.
|
||||||
|
//
|
||||||
|
// Zero is not a valid unique id.
|
||||||
|
//
|
||||||
|
// evictCount should not overflow in any remotely practical situation. In
|
||||||
|
// practice, we will have one dynamic table per HTTP/2 connection. If we
|
||||||
|
// assume a very powerful server that handles 1M QPS per connection and each
|
||||||
|
// request adds (then evicts) 100 entries from the table, it would still take
|
||||||
|
// 2M years for evictCount to overflow.
|
||||||
|
ents []HeaderField
|
||||||
|
evictCount uint64
|
||||||
|
|
||||||
|
// byName maps a HeaderField name to the unique id of the newest entry with
|
||||||
|
// the same name. See above for a definition of "unique id".
|
||||||
|
byName map[string]uint64
|
||||||
|
|
||||||
|
// byNameValue maps a HeaderField name/value pair to the unique id of the newest
|
||||||
|
// entry with the same name and value. See above for a definition of "unique id".
|
||||||
|
byNameValue map[pairNameValue]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type pairNameValue struct {
|
||||||
|
name, value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *headerFieldTable) init() {
|
||||||
|
t.byName = make(map[string]uint64)
|
||||||
|
t.byNameValue = make(map[pairNameValue]uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// len reports the number of entries in the table.
|
||||||
|
func (t *headerFieldTable) len() int {
|
||||||
|
return len(t.ents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addEntry adds a new entry.
|
||||||
|
func (t *headerFieldTable) addEntry(f HeaderField) {
|
||||||
|
id := uint64(t.len()) + t.evictCount + 1
|
||||||
|
t.byName[f.Name] = id
|
||||||
|
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
||||||
|
t.ents = append(t.ents, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evictOldest evicts the n oldest entries in the table.
|
||||||
|
func (t *headerFieldTable) evictOldest(n int) {
|
||||||
|
if n > t.len() {
|
||||||
|
panic(fmt.Sprintf("evictOldest(%v) on table with %v entries", n, t.len()))
|
||||||
|
}
|
||||||
|
for k := 0; k < n; k++ {
|
||||||
|
f := t.ents[k]
|
||||||
|
id := t.evictCount + uint64(k) + 1
|
||||||
|
if t.byName[f.Name] == id {
|
||||||
|
delete(t.byName, f.Name)
|
||||||
|
}
|
||||||
|
if p := (pairNameValue{f.Name, f.Value}); t.byNameValue[p] == id {
|
||||||
|
delete(t.byNameValue, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(t.ents, t.ents[n:])
|
||||||
|
for k := t.len() - n; k < t.len(); k++ {
|
||||||
|
t.ents[k] = HeaderField{} // so strings can be garbage collected
|
||||||
|
}
|
||||||
|
t.ents = t.ents[:t.len()-n]
|
||||||
|
if t.evictCount+uint64(n) < t.evictCount {
|
||||||
|
panic("evictCount overflow")
|
||||||
|
}
|
||||||
|
t.evictCount += uint64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// search finds f in the table. If there is no match, i is 0.
|
||||||
|
// If both name and value match, i is the matched index and nameValueMatch
|
||||||
|
// becomes true. If only name matches, i points to that index and
|
||||||
|
// nameValueMatch becomes false.
|
||||||
|
//
|
||||||
|
// The returned index is a 1-based HPACK index. For dynamic tables, HPACK says
|
||||||
|
// that index 1 should be the newest entry, but t.ents[0] is the oldest entry,
|
||||||
|
// meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
|
||||||
|
// table, the return value i actually refers to the entry t.ents[t.len()-i].
|
||||||
|
//
|
||||||
|
// All tables are assumed to be a dynamic tables except for the global
|
||||||
|
// staticTable pointer.
|
||||||
|
//
|
||||||
|
// See Section 2.3.3.
|
||||||
|
func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
|
||||||
|
if !f.Sensitive {
|
||||||
|
if id := t.byNameValue[pairNameValue{f.Name, f.Value}]; id != 0 {
|
||||||
|
return t.idToIndex(id), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if id := t.byName[f.Name]; id != 0 {
|
||||||
|
return t.idToIndex(id), false
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// idToIndex converts a unique id to an HPACK index.
|
||||||
|
// See Section 2.3.3.
|
||||||
|
func (t *headerFieldTable) idToIndex(id uint64) uint64 {
|
||||||
|
if id <= t.evictCount {
|
||||||
|
panic(fmt.Sprintf("id (%v) <= evictCount (%v)", id, t.evictCount))
|
||||||
|
}
|
||||||
|
k := id - t.evictCount - 1 // convert id to an index t.ents[k]
|
||||||
|
if t != staticTable {
|
||||||
|
return uint64(t.len()) - k // dynamic table
|
||||||
|
}
|
||||||
|
return k + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
|
||||||
|
var staticTable = newStaticTable()
|
||||||
|
var staticTableEntries = [...]HeaderField{
|
||||||
|
{Name: ":authority"},
|
||||||
|
{Name: ":method", Value: "GET"},
|
||||||
|
{Name: ":method", Value: "POST"},
|
||||||
|
{Name: ":path", Value: "/"},
|
||||||
|
{Name: ":path", Value: "/index.html"},
|
||||||
|
{Name: ":scheme", Value: "http"},
|
||||||
|
{Name: ":scheme", Value: "https"},
|
||||||
|
{Name: ":status", Value: "200"},
|
||||||
|
{Name: ":status", Value: "204"},
|
||||||
|
{Name: ":status", Value: "206"},
|
||||||
|
{Name: ":status", Value: "304"},
|
||||||
|
{Name: ":status", Value: "400"},
|
||||||
|
{Name: ":status", Value: "404"},
|
||||||
|
{Name: ":status", Value: "500"},
|
||||||
|
{Name: "accept-charset"},
|
||||||
|
{Name: "accept-encoding", Value: "gzip, deflate"},
|
||||||
|
{Name: "accept-language"},
|
||||||
|
{Name: "accept-ranges"},
|
||||||
|
{Name: "accept"},
|
||||||
|
{Name: "access-control-allow-origin"},
|
||||||
|
{Name: "age"},
|
||||||
|
{Name: "allow"},
|
||||||
|
{Name: "authorization"},
|
||||||
|
{Name: "cache-control"},
|
||||||
|
{Name: "content-disposition"},
|
||||||
|
{Name: "content-encoding"},
|
||||||
|
{Name: "content-language"},
|
||||||
|
{Name: "content-length"},
|
||||||
|
{Name: "content-location"},
|
||||||
|
{Name: "content-range"},
|
||||||
|
{Name: "content-type"},
|
||||||
|
{Name: "cookie"},
|
||||||
|
{Name: "date"},
|
||||||
|
{Name: "etag"},
|
||||||
|
{Name: "expect"},
|
||||||
|
{Name: "expires"},
|
||||||
|
{Name: "from"},
|
||||||
|
{Name: "host"},
|
||||||
|
{Name: "if-match"},
|
||||||
|
{Name: "if-modified-since"},
|
||||||
|
{Name: "if-none-match"},
|
||||||
|
{Name: "if-range"},
|
||||||
|
{Name: "if-unmodified-since"},
|
||||||
|
{Name: "last-modified"},
|
||||||
|
{Name: "link"},
|
||||||
|
{Name: "location"},
|
||||||
|
{Name: "max-forwards"},
|
||||||
|
{Name: "proxy-authenticate"},
|
||||||
|
{Name: "proxy-authorization"},
|
||||||
|
{Name: "range"},
|
||||||
|
{Name: "referer"},
|
||||||
|
{Name: "refresh"},
|
||||||
|
{Name: "retry-after"},
|
||||||
|
{Name: "server"},
|
||||||
|
{Name: "set-cookie"},
|
||||||
|
{Name: "strict-transport-security"},
|
||||||
|
{Name: "transfer-encoding"},
|
||||||
|
{Name: "user-agent"},
|
||||||
|
{Name: "vary"},
|
||||||
|
{Name: "via"},
|
||||||
|
{Name: "www-authenticate"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStaticTable() *headerFieldTable {
|
||||||
|
t := &headerFieldTable{}
|
||||||
|
t.init()
|
||||||
|
for _, e := range staticTableEntries[:] {
|
||||||
|
t.addEntry(e)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
var huffmanCodes = [256]uint32{
|
||||||
|
0x1ff8,
|
||||||
|
0x7fffd8,
|
||||||
|
0xfffffe2,
|
||||||
|
0xfffffe3,
|
||||||
|
0xfffffe4,
|
||||||
|
0xfffffe5,
|
||||||
|
0xfffffe6,
|
||||||
|
0xfffffe7,
|
||||||
|
0xfffffe8,
|
||||||
|
0xffffea,
|
||||||
|
0x3ffffffc,
|
||||||
|
0xfffffe9,
|
||||||
|
0xfffffea,
|
||||||
|
0x3ffffffd,
|
||||||
|
0xfffffeb,
|
||||||
|
0xfffffec,
|
||||||
|
0xfffffed,
|
||||||
|
0xfffffee,
|
||||||
|
0xfffffef,
|
||||||
|
0xffffff0,
|
||||||
|
0xffffff1,
|
||||||
|
0xffffff2,
|
||||||
|
0x3ffffffe,
|
||||||
|
0xffffff3,
|
||||||
|
0xffffff4,
|
||||||
|
0xffffff5,
|
||||||
|
0xffffff6,
|
||||||
|
0xffffff7,
|
||||||
|
0xffffff8,
|
||||||
|
0xffffff9,
|
||||||
|
0xffffffa,
|
||||||
|
0xffffffb,
|
||||||
|
0x14,
|
||||||
|
0x3f8,
|
||||||
|
0x3f9,
|
||||||
|
0xffa,
|
||||||
|
0x1ff9,
|
||||||
|
0x15,
|
||||||
|
0xf8,
|
||||||
|
0x7fa,
|
||||||
|
0x3fa,
|
||||||
|
0x3fb,
|
||||||
|
0xf9,
|
||||||
|
0x7fb,
|
||||||
|
0xfa,
|
||||||
|
0x16,
|
||||||
|
0x17,
|
||||||
|
0x18,
|
||||||
|
0x0,
|
||||||
|
0x1,
|
||||||
|
0x2,
|
||||||
|
0x19,
|
||||||
|
0x1a,
|
||||||
|
0x1b,
|
||||||
|
0x1c,
|
||||||
|
0x1d,
|
||||||
|
0x1e,
|
||||||
|
0x1f,
|
||||||
|
0x5c,
|
||||||
|
0xfb,
|
||||||
|
0x7ffc,
|
||||||
|
0x20,
|
||||||
|
0xffb,
|
||||||
|
0x3fc,
|
||||||
|
0x1ffa,
|
||||||
|
0x21,
|
||||||
|
0x5d,
|
||||||
|
0x5e,
|
||||||
|
0x5f,
|
||||||
|
0x60,
|
||||||
|
0x61,
|
||||||
|
0x62,
|
||||||
|
0x63,
|
||||||
|
0x64,
|
||||||
|
0x65,
|
||||||
|
0x66,
|
||||||
|
0x67,
|
||||||
|
0x68,
|
||||||
|
0x69,
|
||||||
|
0x6a,
|
||||||
|
0x6b,
|
||||||
|
0x6c,
|
||||||
|
0x6d,
|
||||||
|
0x6e,
|
||||||
|
0x6f,
|
||||||
|
0x70,
|
||||||
|
0x71,
|
||||||
|
0x72,
|
||||||
|
0xfc,
|
||||||
|
0x73,
|
||||||
|
0xfd,
|
||||||
|
0x1ffb,
|
||||||
|
0x7fff0,
|
||||||
|
0x1ffc,
|
||||||
|
0x3ffc,
|
||||||
|
0x22,
|
||||||
|
0x7ffd,
|
||||||
|
0x3,
|
||||||
|
0x23,
|
||||||
|
0x4,
|
||||||
|
0x24,
|
||||||
|
0x5,
|
||||||
|
0x25,
|
||||||
|
0x26,
|
||||||
|
0x27,
|
||||||
|
0x6,
|
||||||
|
0x74,
|
||||||
|
0x75,
|
||||||
|
0x28,
|
||||||
|
0x29,
|
||||||
|
0x2a,
|
||||||
|
0x7,
|
||||||
|
0x2b,
|
||||||
|
0x76,
|
||||||
|
0x2c,
|
||||||
|
0x8,
|
||||||
|
0x9,
|
||||||
|
0x2d,
|
||||||
|
0x77,
|
||||||
|
0x78,
|
||||||
|
0x79,
|
||||||
|
0x7a,
|
||||||
|
0x7b,
|
||||||
|
0x7ffe,
|
||||||
|
0x7fc,
|
||||||
|
0x3ffd,
|
||||||
|
0x1ffd,
|
||||||
|
0xffffffc,
|
||||||
|
0xfffe6,
|
||||||
|
0x3fffd2,
|
||||||
|
0xfffe7,
|
||||||
|
0xfffe8,
|
||||||
|
0x3fffd3,
|
||||||
|
0x3fffd4,
|
||||||
|
0x3fffd5,
|
||||||
|
0x7fffd9,
|
||||||
|
0x3fffd6,
|
||||||
|
0x7fffda,
|
||||||
|
0x7fffdb,
|
||||||
|
0x7fffdc,
|
||||||
|
0x7fffdd,
|
||||||
|
0x7fffde,
|
||||||
|
0xffffeb,
|
||||||
|
0x7fffdf,
|
||||||
|
0xffffec,
|
||||||
|
0xffffed,
|
||||||
|
0x3fffd7,
|
||||||
|
0x7fffe0,
|
||||||
|
0xffffee,
|
||||||
|
0x7fffe1,
|
||||||
|
0x7fffe2,
|
||||||
|
0x7fffe3,
|
||||||
|
0x7fffe4,
|
||||||
|
0x1fffdc,
|
||||||
|
0x3fffd8,
|
||||||
|
0x7fffe5,
|
||||||
|
0x3fffd9,
|
||||||
|
0x7fffe6,
|
||||||
|
0x7fffe7,
|
||||||
|
0xffffef,
|
||||||
|
0x3fffda,
|
||||||
|
0x1fffdd,
|
||||||
|
0xfffe9,
|
||||||
|
0x3fffdb,
|
||||||
|
0x3fffdc,
|
||||||
|
0x7fffe8,
|
||||||
|
0x7fffe9,
|
||||||
|
0x1fffde,
|
||||||
|
0x7fffea,
|
||||||
|
0x3fffdd,
|
||||||
|
0x3fffde,
|
||||||
|
0xfffff0,
|
||||||
|
0x1fffdf,
|
||||||
|
0x3fffdf,
|
||||||
|
0x7fffeb,
|
||||||
|
0x7fffec,
|
||||||
|
0x1fffe0,
|
||||||
|
0x1fffe1,
|
||||||
|
0x3fffe0,
|
||||||
|
0x1fffe2,
|
||||||
|
0x7fffed,
|
||||||
|
0x3fffe1,
|
||||||
|
0x7fffee,
|
||||||
|
0x7fffef,
|
||||||
|
0xfffea,
|
||||||
|
0x3fffe2,
|
||||||
|
0x3fffe3,
|
||||||
|
0x3fffe4,
|
||||||
|
0x7ffff0,
|
||||||
|
0x3fffe5,
|
||||||
|
0x3fffe6,
|
||||||
|
0x7ffff1,
|
||||||
|
0x3ffffe0,
|
||||||
|
0x3ffffe1,
|
||||||
|
0xfffeb,
|
||||||
|
0x7fff1,
|
||||||
|
0x3fffe7,
|
||||||
|
0x7ffff2,
|
||||||
|
0x3fffe8,
|
||||||
|
0x1ffffec,
|
||||||
|
0x3ffffe2,
|
||||||
|
0x3ffffe3,
|
||||||
|
0x3ffffe4,
|
||||||
|
0x7ffffde,
|
||||||
|
0x7ffffdf,
|
||||||
|
0x3ffffe5,
|
||||||
|
0xfffff1,
|
||||||
|
0x1ffffed,
|
||||||
|
0x7fff2,
|
||||||
|
0x1fffe3,
|
||||||
|
0x3ffffe6,
|
||||||
|
0x7ffffe0,
|
||||||
|
0x7ffffe1,
|
||||||
|
0x3ffffe7,
|
||||||
|
0x7ffffe2,
|
||||||
|
0xfffff2,
|
||||||
|
0x1fffe4,
|
||||||
|
0x1fffe5,
|
||||||
|
0x3ffffe8,
|
||||||
|
0x3ffffe9,
|
||||||
|
0xffffffd,
|
||||||
|
0x7ffffe3,
|
||||||
|
0x7ffffe4,
|
||||||
|
0x7ffffe5,
|
||||||
|
0xfffec,
|
||||||
|
0xfffff3,
|
||||||
|
0xfffed,
|
||||||
|
0x1fffe6,
|
||||||
|
0x3fffe9,
|
||||||
|
0x1fffe7,
|
||||||
|
0x1fffe8,
|
||||||
|
0x7ffff3,
|
||||||
|
0x3fffea,
|
||||||
|
0x3fffeb,
|
||||||
|
0x1ffffee,
|
||||||
|
0x1ffffef,
|
||||||
|
0xfffff4,
|
||||||
|
0xfffff5,
|
||||||
|
0x3ffffea,
|
||||||
|
0x7ffff4,
|
||||||
|
0x3ffffeb,
|
||||||
|
0x7ffffe6,
|
||||||
|
0x3ffffec,
|
||||||
|
0x3ffffed,
|
||||||
|
0x7ffffe7,
|
||||||
|
0x7ffffe8,
|
||||||
|
0x7ffffe9,
|
||||||
|
0x7ffffea,
|
||||||
|
0x7ffffeb,
|
||||||
|
0xffffffe,
|
||||||
|
0x7ffffec,
|
||||||
|
0x7ffffed,
|
||||||
|
0x7ffffee,
|
||||||
|
0x7ffffef,
|
||||||
|
0x7fffff0,
|
||||||
|
0x3ffffee,
|
||||||
|
}
|
||||||
|
|
||||||
|
var huffmanCodeLen = [256]uint8{
|
||||||
|
13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28,
|
||||||
|
28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||||
|
6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6,
|
||||||
|
5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10,
|
||||||
|
13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6,
|
||||||
|
15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5,
|
||||||
|
6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28,
|
||||||
|
20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23,
|
||||||
|
24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24,
|
||||||
|
22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23,
|
||||||
|
21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23,
|
||||||
|
26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25,
|
||||||
|
19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27,
|
||||||
|
20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23,
|
||||||
|
26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26,
|
||||||
|
}
|
385
vendor/golang.org/x/net/http2/http2.go
generated
vendored
Normal file
385
vendor/golang.org/x/net/http2/http2.go
generated
vendored
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
// Copyright 2014 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 http2 implements the HTTP/2 protocol.
|
||||||
|
//
|
||||||
|
// This package is low-level and intended to be used directly by very
|
||||||
|
// few people. Most users will use it indirectly through the automatic
|
||||||
|
// use by the net/http package (from Go 1.6 and later).
|
||||||
|
// For use in earlier Go versions see ConfigureServer. (Transport support
|
||||||
|
// requires Go 1.6 or later)
|
||||||
|
//
|
||||||
|
// See https://http2.github.io/ for more information on HTTP/2.
|
||||||
|
//
|
||||||
|
// See https://http2.golang.org/ for a test server running this code.
|
||||||
|
//
|
||||||
|
package http2 // import "golang.org/x/net/http2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
VerboseLogs bool
|
||||||
|
logFrameWrites bool
|
||||||
|
logFrameReads bool
|
||||||
|
inTests bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
e := os.Getenv("GODEBUG")
|
||||||
|
if strings.Contains(e, "http2debug=1") {
|
||||||
|
VerboseLogs = true
|
||||||
|
}
|
||||||
|
if strings.Contains(e, "http2debug=2") {
|
||||||
|
VerboseLogs = true
|
||||||
|
logFrameWrites = true
|
||||||
|
logFrameReads = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ClientPreface is the string that must be sent by new
|
||||||
|
// connections from clients.
|
||||||
|
ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
||||||
|
|
||||||
|
// SETTINGS_MAX_FRAME_SIZE default
|
||||||
|
// http://http2.github.io/http2-spec/#rfc.section.6.5.2
|
||||||
|
initialMaxFrameSize = 16384
|
||||||
|
|
||||||
|
// NextProtoTLS is the NPN/ALPN protocol negotiated during
|
||||||
|
// HTTP/2's TLS setup.
|
||||||
|
NextProtoTLS = "h2"
|
||||||
|
|
||||||
|
// http://http2.github.io/http2-spec/#SettingValues
|
||||||
|
initialHeaderTableSize = 4096
|
||||||
|
|
||||||
|
initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size
|
||||||
|
|
||||||
|
defaultMaxReadFrameSize = 1 << 20
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientPreface = []byte(ClientPreface)
|
||||||
|
)
|
||||||
|
|
||||||
|
type streamState int
|
||||||
|
|
||||||
|
// HTTP/2 stream states.
|
||||||
|
//
|
||||||
|
// See http://tools.ietf.org/html/rfc7540#section-5.1.
|
||||||
|
//
|
||||||
|
// For simplicity, the server code merges "reserved (local)" into
|
||||||
|
// "half-closed (remote)". This is one less state transition to track.
|
||||||
|
// The only downside is that we send PUSH_PROMISEs slightly less
|
||||||
|
// liberally than allowable. More discussion here:
|
||||||
|
// https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0599.html
|
||||||
|
//
|
||||||
|
// "reserved (remote)" is omitted since the client code does not
|
||||||
|
// support server push.
|
||||||
|
const (
|
||||||
|
stateIdle streamState = iota
|
||||||
|
stateOpen
|
||||||
|
stateHalfClosedLocal
|
||||||
|
stateHalfClosedRemote
|
||||||
|
stateClosed
|
||||||
|
)
|
||||||
|
|
||||||
|
var stateName = [...]string{
|
||||||
|
stateIdle: "Idle",
|
||||||
|
stateOpen: "Open",
|
||||||
|
stateHalfClosedLocal: "HalfClosedLocal",
|
||||||
|
stateHalfClosedRemote: "HalfClosedRemote",
|
||||||
|
stateClosed: "Closed",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st streamState) String() string {
|
||||||
|
return stateName[st]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting is a setting parameter: which setting it is, and its value.
|
||||||
|
type Setting struct {
|
||||||
|
// ID is which setting is being set.
|
||||||
|
// See http://http2.github.io/http2-spec/#SettingValues
|
||||||
|
ID SettingID
|
||||||
|
|
||||||
|
// Val is the value.
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Setting) String() string {
|
||||||
|
return fmt.Sprintf("[%v = %d]", s.ID, s.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether the setting is valid.
|
||||||
|
func (s Setting) Valid() error {
|
||||||
|
// Limits and error codes from 6.5.2 Defined SETTINGS Parameters
|
||||||
|
switch s.ID {
|
||||||
|
case SettingEnablePush:
|
||||||
|
if s.Val != 1 && s.Val != 0 {
|
||||||
|
return ConnectionError(ErrCodeProtocol)
|
||||||
|
}
|
||||||
|
case SettingInitialWindowSize:
|
||||||
|
if s.Val > 1<<31-1 {
|
||||||
|
return ConnectionError(ErrCodeFlowControl)
|
||||||
|
}
|
||||||
|
case SettingMaxFrameSize:
|
||||||
|
if s.Val < 16384 || s.Val > 1<<24-1 {
|
||||||
|
return ConnectionError(ErrCodeProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SettingID is an HTTP/2 setting as defined in
|
||||||
|
// http://http2.github.io/http2-spec/#iana-settings
|
||||||
|
type SettingID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
SettingHeaderTableSize SettingID = 0x1
|
||||||
|
SettingEnablePush SettingID = 0x2
|
||||||
|
SettingMaxConcurrentStreams SettingID = 0x3
|
||||||
|
SettingInitialWindowSize SettingID = 0x4
|
||||||
|
SettingMaxFrameSize SettingID = 0x5
|
||||||
|
SettingMaxHeaderListSize SettingID = 0x6
|
||||||
|
)
|
||||||
|
|
||||||
|
var settingName = map[SettingID]string{
|
||||||
|
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
|
||||||
|
SettingEnablePush: "ENABLE_PUSH",
|
||||||
|
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
|
||||||
|
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
|
||||||
|
SettingMaxFrameSize: "MAX_FRAME_SIZE",
|
||||||
|
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SettingID) String() string {
|
||||||
|
if v, ok := settingName[s]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validWireHeaderFieldName reports whether v is a valid header field
|
||||||
|
// name (key). See httpguts.ValidHeaderName for the base rules.
|
||||||
|
//
|
||||||
|
// Further, http2 says:
|
||||||
|
// "Just as in HTTP/1.x, header field names are strings of ASCII
|
||||||
|
// characters that are compared in a case-insensitive
|
||||||
|
// fashion. However, header field names MUST be converted to
|
||||||
|
// lowercase prior to their encoding in HTTP/2. "
|
||||||
|
func validWireHeaderFieldName(v string) bool {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range v {
|
||||||
|
if !httpguts.IsTokenRune(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if 'A' <= r && r <= 'Z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpCodeString(code int) string {
|
||||||
|
switch code {
|
||||||
|
case 200:
|
||||||
|
return "200"
|
||||||
|
case 404:
|
||||||
|
return "404"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// from pkg io
|
||||||
|
type stringWriter interface {
|
||||||
|
WriteString(s string) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A gate lets two goroutines coordinate their activities.
|
||||||
|
type gate chan struct{}
|
||||||
|
|
||||||
|
func (g gate) Done() { g <- struct{}{} }
|
||||||
|
func (g gate) Wait() { <-g }
|
||||||
|
|
||||||
|
// A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed).
|
||||||
|
type closeWaiter chan struct{}
|
||||||
|
|
||||||
|
// Init makes a closeWaiter usable.
|
||||||
|
// It exists because so a closeWaiter value can be placed inside a
|
||||||
|
// larger struct and have the Mutex and Cond's memory in the same
|
||||||
|
// allocation.
|
||||||
|
func (cw *closeWaiter) Init() {
|
||||||
|
*cw = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close marks the closeWaiter as closed and unblocks any waiters.
|
||||||
|
func (cw closeWaiter) Close() {
|
||||||
|
close(cw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits for the closeWaiter to become closed.
|
||||||
|
func (cw closeWaiter) Wait() {
|
||||||
|
<-cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferedWriter is a buffered writer that writes to w.
|
||||||
|
// Its buffered writer is lazily allocated as needed, to minimize
|
||||||
|
// idle memory usage with many connections.
|
||||||
|
type bufferedWriter struct {
|
||||||
|
_ incomparable
|
||||||
|
w io.Writer // immutable
|
||||||
|
bw *bufio.Writer // non-nil when data is buffered
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferedWriter(w io.Writer) *bufferedWriter {
|
||||||
|
return &bufferedWriter{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufWriterPoolBufferSize is the size of bufio.Writer's
|
||||||
|
// buffers created using bufWriterPool.
|
||||||
|
//
|
||||||
|
// TODO: pick a less arbitrary value? this is a bit under
|
||||||
|
// (3 x typical 1500 byte MTU) at least. Other than that,
|
||||||
|
// not much thought went into it.
|
||||||
|
const bufWriterPoolBufferSize = 4 << 10
|
||||||
|
|
||||||
|
var bufWriterPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return bufio.NewWriterSize(nil, bufWriterPoolBufferSize)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bufferedWriter) Available() int {
|
||||||
|
if w.bw == nil {
|
||||||
|
return bufWriterPoolBufferSize
|
||||||
|
}
|
||||||
|
return w.bw.Available()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bufferedWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if w.bw == nil {
|
||||||
|
bw := bufWriterPool.Get().(*bufio.Writer)
|
||||||
|
bw.Reset(w.w)
|
||||||
|
w.bw = bw
|
||||||
|
}
|
||||||
|
return w.bw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bufferedWriter) Flush() error {
|
||||||
|
bw := w.bw
|
||||||
|
if bw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := bw.Flush()
|
||||||
|
bw.Reset(nil)
|
||||||
|
bufWriterPool.Put(bw)
|
||||||
|
w.bw = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustUint31(v int32) uint32 {
|
||||||
|
if v < 0 || v > 2147483647 {
|
||||||
|
panic("out of range")
|
||||||
|
}
|
||||||
|
return uint32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bodyAllowedForStatus reports whether a given response status code
|
||||||
|
// permits a body. See RFC 7230, section 3.3.
|
||||||
|
func bodyAllowedForStatus(status int) bool {
|
||||||
|
switch {
|
||||||
|
case status >= 100 && status <= 199:
|
||||||
|
return false
|
||||||
|
case status == 204:
|
||||||
|
return false
|
||||||
|
case status == 304:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpError struct {
|
||||||
|
_ incomparable
|
||||||
|
msg string
|
||||||
|
timeout bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *httpError) Error() string { return e.msg }
|
||||||
|
func (e *httpError) Timeout() bool { return e.timeout }
|
||||||
|
func (e *httpError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true}
|
||||||
|
|
||||||
|
type connectionStater interface {
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorterPool = sync.Pool{New: func() interface{} { return new(sorter) }}
|
||||||
|
|
||||||
|
type sorter struct {
|
||||||
|
v []string // owned by sorter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sorter) Len() int { return len(s.v) }
|
||||||
|
func (s *sorter) Swap(i, j int) { s.v[i], s.v[j] = s.v[j], s.v[i] }
|
||||||
|
func (s *sorter) Less(i, j int) bool { return s.v[i] < s.v[j] }
|
||||||
|
|
||||||
|
// Keys returns the sorted keys of h.
|
||||||
|
//
|
||||||
|
// The returned slice is only valid until s used again or returned to
|
||||||
|
// its pool.
|
||||||
|
func (s *sorter) Keys(h http.Header) []string {
|
||||||
|
keys := s.v[:0]
|
||||||
|
for k := range h {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
s.v = keys
|
||||||
|
sort.Sort(s)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sorter) SortStrings(ss []string) {
|
||||||
|
// Our sorter works on s.v, which sorter owns, so
|
||||||
|
// stash it away while we sort the user's buffer.
|
||||||
|
save := s.v
|
||||||
|
s.v = ss
|
||||||
|
sort.Sort(s)
|
||||||
|
s.v = save
|
||||||
|
}
|
||||||
|
|
||||||
|
// validPseudoPath reports whether v is a valid :path pseudo-header
|
||||||
|
// value. It must be either:
|
||||||
|
//
|
||||||
|
// *) a non-empty string starting with '/'
|
||||||
|
// *) the string '*', for OPTIONS requests.
|
||||||
|
//
|
||||||
|
// For now this is only used a quick check for deciding when to clean
|
||||||
|
// up Opaque URLs before sending requests from the Transport.
|
||||||
|
// See golang.org/issue/16847
|
||||||
|
//
|
||||||
|
// We used to enforce that the path also didn't start with "//", but
|
||||||
|
// Google's GFE accepts such paths and Chrome sends them, so ignore
|
||||||
|
// that part of the spec. See golang.org/issue/19103.
|
||||||
|
func validPseudoPath(v string) bool {
|
||||||
|
return (len(v) > 0 && v[0] == '/') || v == "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
// incomparable is a zero-width, non-comparable type. Adding it to a struct
|
||||||
|
// makes that struct also non-comparable, and generally doesn't add
|
||||||
|
// any size (as long as it's first).
|
||||||
|
type incomparable [0]func()
|
20
vendor/golang.org/x/net/http2/not_go111.go
generated
vendored
Normal file
20
vendor/golang.org/x/net/http2/not_go111.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build !go1.11
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptrace"
|
||||||
|
"net/textproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { return false }
|
||||||
|
|
||||||
|
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {}
|
||||||
|
|
||||||
|
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
|
||||||
|
return nil
|
||||||
|
}
|
168
vendor/golang.org/x/net/http2/pipe.go
generated
vendored
Normal file
168
vendor/golang.org/x/net/http2/pipe.go
generated
vendored
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
// Copyright 2014 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 http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pipe is a goroutine-safe io.Reader/io.Writer pair. It's like
|
||||||
|
// io.Pipe except there are no PipeReader/PipeWriter halves, and the
|
||||||
|
// underlying buffer is an interface. (io.Pipe is always unbuffered)
|
||||||
|
type pipe struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
c sync.Cond // c.L lazily initialized to &p.mu
|
||||||
|
b pipeBuffer // nil when done reading
|
||||||
|
unread int // bytes unread when done
|
||||||
|
err error // read error once empty. non-nil means closed.
|
||||||
|
breakErr error // immediate read error (caller doesn't see rest of b)
|
||||||
|
donec chan struct{} // closed on error
|
||||||
|
readFn func() // optional code to run in Read before error
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeBuffer interface {
|
||||||
|
Len() int
|
||||||
|
io.Writer
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipe) Len() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.b == nil {
|
||||||
|
return p.unread
|
||||||
|
}
|
||||||
|
return p.b.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read waits until data is available and copies bytes
|
||||||
|
// from the buffer into p.
|
||||||
|
func (p *pipe) Read(d []byte) (n int, err error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.c.L == nil {
|
||||||
|
p.c.L = &p.mu
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if p.breakErr != nil {
|
||||||
|
return 0, p.breakErr
|
||||||
|
}
|
||||||
|
if p.b != nil && p.b.Len() > 0 {
|
||||||
|
return p.b.Read(d)
|
||||||
|
}
|
||||||
|
if p.err != nil {
|
||||||
|
if p.readFn != nil {
|
||||||
|
p.readFn() // e.g. copy trailers
|
||||||
|
p.readFn = nil // not sticky like p.err
|
||||||
|
}
|
||||||
|
p.b = nil
|
||||||
|
return 0, p.err
|
||||||
|
}
|
||||||
|
p.c.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errClosedPipeWrite = errors.New("write on closed buffer")
|
||||||
|
|
||||||
|
// Write copies bytes from p into the buffer and wakes a reader.
|
||||||
|
// It is an error to write more data than the buffer can hold.
|
||||||
|
func (p *pipe) Write(d []byte) (n int, err error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.c.L == nil {
|
||||||
|
p.c.L = &p.mu
|
||||||
|
}
|
||||||
|
defer p.c.Signal()
|
||||||
|
if p.err != nil {
|
||||||
|
return 0, errClosedPipeWrite
|
||||||
|
}
|
||||||
|
if p.breakErr != nil {
|
||||||
|
p.unread += len(d)
|
||||||
|
return len(d), nil // discard when there is no reader
|
||||||
|
}
|
||||||
|
return p.b.Write(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithError causes the next Read (waking up a current blocked
|
||||||
|
// Read if needed) to return the provided err after all data has been
|
||||||
|
// read.
|
||||||
|
//
|
||||||
|
// The error must be non-nil.
|
||||||
|
func (p *pipe) CloseWithError(err error) { p.closeWithError(&p.err, err, nil) }
|
||||||
|
|
||||||
|
// BreakWithError causes the next Read (waking up a current blocked
|
||||||
|
// Read if needed) to return the provided err immediately, without
|
||||||
|
// waiting for unread data.
|
||||||
|
func (p *pipe) BreakWithError(err error) { p.closeWithError(&p.breakErr, err, nil) }
|
||||||
|
|
||||||
|
// closeWithErrorAndCode is like CloseWithError but also sets some code to run
|
||||||
|
// in the caller's goroutine before returning the error.
|
||||||
|
func (p *pipe) closeWithErrorAndCode(err error, fn func()) { p.closeWithError(&p.err, err, fn) }
|
||||||
|
|
||||||
|
func (p *pipe) closeWithError(dst *error, err error, fn func()) {
|
||||||
|
if err == nil {
|
||||||
|
panic("err must be non-nil")
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.c.L == nil {
|
||||||
|
p.c.L = &p.mu
|
||||||
|
}
|
||||||
|
defer p.c.Signal()
|
||||||
|
if *dst != nil {
|
||||||
|
// Already been done.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.readFn = fn
|
||||||
|
if dst == &p.breakErr {
|
||||||
|
if p.b != nil {
|
||||||
|
p.unread += p.b.Len()
|
||||||
|
}
|
||||||
|
p.b = nil
|
||||||
|
}
|
||||||
|
*dst = err
|
||||||
|
p.closeDoneLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires p.mu be held.
|
||||||
|
func (p *pipe) closeDoneLocked() {
|
||||||
|
if p.donec == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Close if unclosed. This isn't racy since we always
|
||||||
|
// hold p.mu while closing.
|
||||||
|
select {
|
||||||
|
case <-p.donec:
|
||||||
|
default:
|
||||||
|
close(p.donec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error (if any) first set by BreakWithError or CloseWithError.
|
||||||
|
func (p *pipe) Err() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.breakErr != nil {
|
||||||
|
return p.breakErr
|
||||||
|
}
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done returns a channel which is closed if and when this pipe is closed
|
||||||
|
// with CloseWithError.
|
||||||
|
func (p *pipe) Done() <-chan struct{} {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.donec == nil {
|
||||||
|
p.donec = make(chan struct{})
|
||||||
|
if p.err != nil || p.breakErr != nil {
|
||||||
|
// Already hit an error.
|
||||||
|
p.closeDoneLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.donec
|
||||||
|
}
|
2968
vendor/golang.org/x/net/http2/server.go
generated
vendored
Normal file
2968
vendor/golang.org/x/net/http2/server.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue