Merge branch 'development' into feature/context-provider-command

This commit is contained in:
Adnan Hajdarevic 2020-04-28 07:44:55 +02:00
commit 49b375f625
451 changed files with 162922 additions and 42024 deletions

View file

@ -1,23 +1,29 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
- master
os:
- linux
- osx
- windows
arch:
- amd64
- arm64
matrix:
fast_finish: true
allow_failures:
- go: tip
- go: master
exclude:
- os: windows
go: tip
go: master
- os: windows
arch: arm64
- os: osx
arch: arm64
install:
- go get -d -v -t ./...

40
Godeps/Godeps.json generated
View file

@ -1,40 +0,0 @@
{
"ImportPath": "github.com/adnanh/webhook",
"GoVersion": "go1.9",
"GodepVersion": "v79",
"Deps": [
{
"ImportPath": "github.com/codegangsta/negroni",
"Comment": "v0.2.0-151-g5bc66cf",
"Rev": "5bc66cf1ad89af58511e07e108a31f219ed61012"
},
{
"ImportPath": "github.com/ghodss/yaml",
"Comment": "v1.0.0",
"Rev": "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
},
{
"ImportPath": "github.com/gorilla/mux",
"Comment": "v1.5.0-2-gbdd5a5a",
"Rev": "bdd5a5a1b0b489d297b73eb62b5f6328df198bfc"
},
{
"ImportPath": "github.com/satori/go.uuid",
"Comment": "v1.1.0-8-g5bf94b6",
"Rev": "5bf94b69c6b68ee1b541973bb8e1144db23a194b"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "ebfc5b4631820b793c9010c87fd8fef0f39eb082"
},
{
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.4.2",
"Rev": "629574ca2a5df945712d3079857300b5e4da0236"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
}
]
}

5
Godeps/Readme generated
View file

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View file

@ -26,7 +26,7 @@ If you don't have time to waste configuring, hosting, debugging and maintaining
# Getting started
## Installation
### Building from source
To get started, first make sure you've properly set up your [Golang](http://golang.org/doc/install) environment and then run the
To get started, first make sure you've properly set up your [Go](http://golang.org/doc/install) 1.12 or newer environment and then run
```bash
$ go get github.com/adnanh/webhook
```
@ -77,12 +77,26 @@ By performing a simple HTTP GET or POST request to that endpoint, your specified
However, hook defined like that could pose a security threat to your system, because anyone who knows your endpoint, can send a request and execute your command. To prevent that, you can use the `"trigger-rule"` property for your hook, to specify the exact circumstances under which the hook would be triggered. For example, you can use them to add a secret that you must supply as a parameter in order to successfully trigger the hook. Please check out the [Hook rules page](docs/Hook-Rules.md) for detailed list of available rules and their usage.
## Multipart Form Data
[webhook][w] provides limited support the parsing of multipart form data.
Multipart form data can contain two types of parts: values and files.
All form _values_ are automatically added to the `payload` scope.
Use the `parse-parameters-as-json` settings to parse a given value as JSON.
All files are ignored unless they match one of the following criteria:
1. The `Content-Type` header is `application/json`.
1. The part is named in the `parse-parameters-as-json` setting.
In either case, the given file part will be parsed as JSON and added to the `payload` map.
## Templates
[webhook][w] can parse the `hooks.json` input file as a Go template when given the `-template` [CLI parameter](docs/Webhook-Parameters.md). See the [Templates page](docs/Templates.md) for more details on template usage.
## Using HTTPS
[webhook][w] by default serves hooks using http. If you want [webhook][w] to serve secure content using https, you can use the `-secure` flag while starting [webhook][w]. Files containing a certificate and matching private key for the server must be provided using the `-cert /path/to/cert.pem` and `-key /path/to/key.pem` flags. If the certificate is signed by a certificate authority, the cert file should be the concatenation of the server's certificate followed by the CA's certificate.
TLS version and cipher suite selection flags are available from the command line. To list available cipher suites, use the `-list-cipher-suites` flag. The `-tls-min-version` flag can be used with `-list-cipher-suites`.
## CORS Headers
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.

102
cipher_suites.go Normal file
View file

@ -0,0 +1,102 @@
// Copyright 2010 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.
// Copied from Go 1.14 tip src/crypto/tls/cipher_suites.go
package main
import (
"crypto/tls"
"fmt"
)
// CipherSuite is a TLS cipher suite. Note that most functions in this package
// accept and expose cipher suite IDs instead of this type.
type CipherSuite struct {
ID uint16
Name string
// Supported versions is the list of TLS protocol versions that can
// negotiate this cipher suite.
SupportedVersions []uint16
// Insecure is true if the cipher suite has known security issues
// due to its primitives, design, or implementation.
Insecure bool
}
var (
supportedUpToTLS12 = []uint16{tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12}
supportedOnlyTLS12 = []uint16{tls.VersionTLS12}
supportedOnlyTLS13 = []uint16{tls.VersionTLS13}
)
// CipherSuites returns a list of cipher suites currently implemented by this
// package, excluding those with security issues, which are returned by
// InsecureCipherSuites.
//
// The list is sorted by ID. Note that the default cipher suites selected by
// this package might depend on logic that can't be captured by a static list.
func CipherSuites() []*CipherSuite {
return []*CipherSuite{
{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{tls.TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
{tls.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
{tls.TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
// go1.14
// {tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
// {tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
}
}
// InsecureCipherSuites returns a list of cipher suites currently implemented by
// this package and which have security issues.
//
// Most applications should not use the cipher suites in this list, and should
// only use those returned by CipherSuites.
func InsecureCipherSuites() []*CipherSuite {
// RC4 suites are broken because RC4 is.
// CBC-SHA256 suites have no Lucky13 countermeasures.
return []*CipherSuite{
{tls.TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{tls.TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
}
}
// CipherSuiteName returns the standard name for the passed cipher suite ID
// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or a fallback representation
// of the ID value if the cipher suite is not implemented by this package.
func CipherSuiteName(id uint16) string {
for _, c := range CipherSuites() {
if c.ID == id {
return c.Name
}
}
for _, c := range InsecureCipherSuites() {
if c.ID == id {
return c.Name
}
}
return fmt.Sprintf("0x%04X", id)
}

View file

@ -10,6 +10,7 @@ Hooks are defined as JSON objects. Please note that in order to be considered va
* `response-headers` - specifies the list of headers in format `{"name": "X-Example-Header", "value": "it works"}` that will be returned in HTTP response for the hook
* `success-http-response-code` - specifies the HTTP status code to be returned upon success
* `incoming-payload-content-type` - sets the `Content-Type` of the incoming HTTP request (ie. `application/json`); useful when the request lacks a `Content-Type` or sends an erroneous value
* `http-methods` - a list of allowed HTTP methods, such as `POST` and `GET`
* `include-command-output-in-response` - boolean whether webhook should wait for the command to finish and return the raw output as a response to the hook initiator. If the command fails to execute or encounters any errors while executing the response will result in 500 Internal Server Error HTTP status code, otherwise the 200 OK status code will be returned.
* `include-command-output-in-response-on-error` - boolean whether webhook should include command stdout & stderror as a response in failed executions. It only works if `include-command-output-in-response` is set to `true`.
* `parse-parameters-as-json` - specifies the list of arguments that contain JSON strings. These parameters will be decoded by webhook and you can access them like regular objects in rules and `pass-arguments-to-command`.

View file

@ -287,12 +287,12 @@ __Not recommended in production due to low security__
]
```
# JIRA Webhooks
## JIRA Webhooks
[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks)
# Pass File-to-command sample
## Pass File-to-command sample
## Webhook configuration
### Webhook configuration
<pre>
[
@ -315,7 +315,7 @@ __Not recommended in production due to low security__
]
</pre>
## Sample client usage
### Sample client usage
Store the following file as `testRequest.json`.
@ -424,3 +424,97 @@ Travis sends webhooks as `payload=<JSON_STRING>`, so the payload needs to be par
}
]
```
## XML Payload
Given the following payload:
```xml
<app>
<users>
<user id="1" name="Jeff" />
<user id="2" name="Sally" />
</users>
<messages>
<message id="1" from_user="1" to_user="2">Hello!!</message>
</messages>
</app>
```
```json
[
{
"id": "deploy",
"execute-command": "/root/my-server/deployment.sh",
"command-working-directory": "/root/my-server",
"trigger-rule": {
"and": [
{
"match": {
"type": "value",
"parameter": {
"source": "payload",
"name": "app.users.user.0.-name"
},
"value": "Jeff"
}
},
{
"match": {
"type": "value",
"parameter": {
"source": "payload",
"name": "app.messages.message.#text"
},
"value": "Hello!!"
}
},
],
}
}
]
```
## Multipart Form Data
Example of a [Plex Media Server webhook](https://support.plex.tv/articles/115002267687-webhooks/).
The Plex Media Server will send two parts: payload and thumb.
We only care about the payload part.
```json
[
{
"id": "plex",
"execute-command": "play-command.sh",
"parse-parameters-as-json": [
{
"source": "payload",
"name": "payload"
}
],
"trigger-rule":
{
"match":
{
"type": "value",
"parameter": {
"source": "payload",
"name": "payload.event"
},
"value": "media.play"
}
}
}
]
```
Each part of a multipart form data body will have a `Content-Disposition` header.
Some example headers:
```
Content-Disposition: form-data; name="payload"
Content-Disposition: form-data; name="thumb"; filename="thumb.jpg"
```
We key off of the `name` attribute in the `Content-Disposition` value.

View file

@ -186,7 +186,60 @@ For the regex syntax, check out <http://golang.org/pkg/regexp/syntax/>
}
```
### 4. Match Whitelisted IP range
Note that if multiple signatures were passed via a comma separated string, each
will be tried unless a match is found. For example:
```
X-Hub-Signature: sha1=the-first-signature,sha1=the-second-signature
```
### 4. Match payload-hash-sha256
```json
{
"match":
{
"type": "payload-hash-sha256",
"secret": "yoursecret",
"parameter":
{
"source": "header",
"name": "X-Signature"
}
}
}
```
Note that if multiple signatures were passed via a comma separated string, each
will be tried unless a match is found. For example:
```
X-Hub-Signature: sha256=the-first-signature,sha256=the-second-signature
```
### 5. Match payload-hash-sha512
```json
{
"match":
{
"type": "payload-hash-sha512",
"secret": "yoursecret",
"parameter":
{
"source": "header",
"name": "X-Signature"
}
}
}
```
Note that if multiple signatures were passed via a comma separated string, each
will be tried unless a match is found. For example:
```
X-Hub-Signature: sha512=the-first-signature,sha512=the-second-signature
```
### 6. Match Whitelisted IP range
The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks). To match a single IP address only, use `/32`.
@ -200,7 +253,7 @@ The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedi
}
```
### 5. Match scalr-signature
### 7. Match scalr-signature
The trigger rule checks the scalr signature and also checks that the request was signed less than 5 minutes before it was received.
A unqiue signing key is generated for each webhook endpoint URL you register in Scalr.

View file

@ -68,6 +68,34 @@ There are four types of request values:
If the payload contains a key with the specified name "commits.0.commit.id", then the value of that key has priority over the dot-notation referencing.
3. XML Payload
Referencing XML payload parameters is much like the JSON examples above, but XML is more complex.
Element attributes are prefixed by a hyphen (`-`).
Element values are prefixed by a pound (`#`).
Take the following XML payload:
```xml
<app>
<users>
<user id="1" name="Jeff" />
<user id="2" name="Sally" />
</users>
<messages>
<message id="1" from_user="1" to_user="2">Hello!!</message>
</messages>
</app>
```
To access a given `user` element, you must treat them as an array.
So `app.users.user.0.name` yields `Jeff`.
Since there's only one `message` tag, it's not treated as an array.
So `app.messages.message.id` yields `1`.
To access the text within the `message` tag, you would use: `app.messages.message.#text`.
If you are referencing values for environment, you can use `envname` property to set the name of the environment variable like so
```json
{
@ -98,4 +126,4 @@ and for query variables you can use
{
"source": "entire-query"
}
```
```

View file

@ -3,36 +3,60 @@
Usage of webhook:
-cert string
path to the HTTPS certificate pem file (default "cert.pem")
-cipher-suites string
comma-separated list of supported TLS cipher suites
-debug
show debug output
-header value
response header to return, specified in format name=value, use multiple times to set multiple headers
-hooks value
path to the json file containing defined hooks the webhook should serve, use multiple times to load from different files
-hotreload
watch hooks file for changes and reload them automatically
-http-methods string
globally restrict allowed HTTP methods; separate methods with comma
-ip string
ip the webhook should serve hooks on (default "0.0.0.0")
-key string
path to the HTTPS certificate private key pem file (default "key.pem")
-list-cipher-suites
list available TLS cipher suites
-logfile string
send log output to a file; implicitly enables verbose logging
-nopanic
do not panic if hooks cannot be loaded when webhook is not running in verbose mode
-pidfile string
create PID file at the given path
-port int
port the webhook should serve hooks on (default 9000)
-secure
use HTTPS instead of HTTP
-setgid int
set group ID after opening listening port; must be used with setuid
-setuid int
set user ID after opening listening port; must be used with setgid
-template
parse hooks file as a Go template
-tls-min-version string
minimum TLS version (1.0, 1.1, 1.2, 1.3) (default "1.2")
-urlprefix string
url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id) (default "hooks")
-verbose
show verbose output
-version
display webhook version and quit
-x-request-id
use X-Request-Id header, if present, as request ID
-x-request-id-limit int
truncate X-Request-Id header to limit; default no limit
```
Use any of the above specified flags to override their default behavior.
# Live reloading hooks
If you are running an OS that supports USR1 signal, you can use it to trigger hooks reload from hooks file, without restarting the webhook instance.
If you are running an OS that supports the HUP or USR1 signal, you can use it to trigger hooks reload from hooks file, without restarting the webhook instance.
```bash
kill -USR1 webhookpid
```
kill -HUP webhookpid
```

12
droppriv_nope.go Normal file
View file

@ -0,0 +1,12 @@
// +build linux windows
package main
import (
"errors"
"runtime"
)
func dropPrivileges(uid, gid int) error {
return errors.New("setuid and setgid not supported on " + runtime.GOOS)
}

21
droppriv_unix.go Normal file
View file

@ -0,0 +1,21 @@
// +build !windows,!linux
package main
import (
"syscall"
)
func dropPrivileges(uid, gid int) error {
err := syscall.Setgid(gid)
if err != nil {
return err
}
err = syscall.Setuid(uid)
if err != nil {
return err
}
return nil
}

19
go.mod Normal file
View file

@ -0,0 +1,19 @@
module github.com/adnanh/webhook
go 1.13
require (
github.com/clbanning/mxj v1.8.4
github.com/dustin/go-humanize v1.0.0
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/ghodss/yaml v1.0.0
github.com/go-chi/chi v4.0.2+incompatible
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/mux v1.7.3
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/fsnotify.v1 v1.4.2
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 // indirect
)

34
go.sum Normal file
View file

@ -0,0 +1,34 @@
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.2 h1:AwZiD/bIUttYJ+n/k1UwlSUsM+VSE6id7UAnSKqQ+Tc=
gopkg.in/fsnotify.v1 v1.4.2/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View file

@ -5,11 +5,14 @@ import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
"io/ioutil"
"log"
"math"
@ -47,15 +50,43 @@ const (
EnvNamespace string = "HOOK_"
)
// ParameterNodeError describes an error walking a parameter node.
type ParameterNodeError struct {
key string
}
func (e *ParameterNodeError) Error() string {
if e == nil {
return "<nil>"
}
return fmt.Sprintf("parameter node not found: %s", e.key)
}
// IsParameterNodeError returns whether err is of type ParameterNodeError.
func IsParameterNodeError(err error) bool {
switch err.(type) {
case *ParameterNodeError:
return true
default:
return false
}
}
// SignatureError describes an invalid payload signature passed to Hook.
type SignatureError struct {
Signature string
Signature string
Signatures []string
}
func (e *SignatureError) Error() string {
if e == nil {
return "<nil>"
}
if e.Signatures != nil {
return fmt.Sprintf("invalid payload signatures %s", e.Signatures)
}
return fmt.Sprintf("invalid payload signature %s", e.Signature)
}
@ -95,25 +126,67 @@ func (e *ParseError) Error() string {
return e.Err.Error()
}
// ExtractCommaSeparatedValues will extract the values matching the key.
func ExtractCommaSeparatedValues(source, prefix string) []string {
parts := strings.Split(source, ",")
values := make([]string, 0)
for _, part := range parts {
if strings.HasPrefix(part, prefix) {
values = append(values, strings.TrimPrefix(part, prefix))
}
}
return values
}
// ExtractSignatures will extract all the signatures from the source.
func ExtractSignatures(source, prefix string) []string {
// If there are multiple possible matches, let the comma seperated extractor
// do it's work.
if strings.Contains(source, ",") {
return ExtractCommaSeparatedValues(source, prefix)
}
// There were no commas, so just trim the prefix (if it even exists) and
// pass it back.
return []string{
strings.TrimPrefix(source, prefix),
}
}
// ValidateMAC will verify that the expected mac for the given hash will match
// the one provided.
func ValidateMAC(payload []byte, mac hash.Hash, signatures []string) (string, error) {
// Write the payload to the provided hash.
_, err := mac.Write(payload)
if err != nil {
return "", err
}
expectedMAC := hex.EncodeToString(mac.Sum(nil))
for _, signature := range signatures {
if hmac.Equal([]byte(signature), []byte(expectedMAC)) {
return expectedMAC, err
}
}
return expectedMAC, &SignatureError{
Signatures: signatures,
}
}
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
if secret == "" {
return "", errors.New("signature validation secret can not be empty")
}
signature = strings.TrimPrefix(signature, "sha1=")
// Extract the signatures.
signatures := ExtractSignatures(signature, "sha1=")
mac := hmac.New(sha1.New, []byte(secret))
_, err := mac.Write(payload)
if err != nil {
return "", err
}
expectedMAC := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
return expectedMAC, &SignatureError{signature}
}
return expectedMAC, err
// Validate the MAC.
return ValidateMAC(payload, hmac.New(sha1.New, []byte(secret)), signatures)
}
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
@ -122,19 +195,24 @@ func CheckPayloadSignature256(payload []byte, secret string, signature string) (
return "", errors.New("signature validation secret can not be empty")
}
signature = strings.TrimPrefix(signature, "sha256=")
// Extract the signatures.
signatures := ExtractSignatures(signature, "sha256=")
mac := hmac.New(sha256.New, []byte(secret))
_, err := mac.Write(payload)
if err != nil {
return "", err
}
expectedMAC := hex.EncodeToString(mac.Sum(nil))
// Validate the MAC.
return ValidateMAC(payload, hmac.New(sha256.New, []byte(secret)), signatures)
}
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
return expectedMAC, &SignatureError{signature}
// CheckPayloadSignature512 calculates and verifies SHA512 signature of the given payload
func CheckPayloadSignature512(payload []byte, secret string, signature string) (string, error) {
if secret == "" {
return "", errors.New("signature validation secret can not be empty")
}
return expectedMAC, err
// Extract the signatures.
signatures := ExtractSignatures(signature, "sha512=")
// Validate the MAC.
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
}
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
@ -157,7 +235,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
expectedSignature := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
return false, &SignatureError{providedSignature}
return false, &SignatureError{Signature: providedSignature}
}
if !checkDate {
@ -172,7 +250,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
delta := math.Abs(now.Sub(date).Seconds())
if delta > 300 {
return false, &SignatureError{"outdated"}
return false, &SignatureError{Signature: "outdated"}
}
return true, nil
}
@ -254,9 +332,9 @@ func ReplaceParameter(s string, params interface{}, value interface{}) bool {
}
// GetParameter extracts interface{} value based on the passed string
func GetParameter(s string, params interface{}) (interface{}, bool) {
func GetParameter(s string, params interface{}) (interface{}, error) {
if params == nil {
return nil, false
return nil, errors.New("no parameters")
}
paramsValue := reflect.ValueOf(params)
@ -270,7 +348,7 @@ func GetParameter(s string, params interface{}) (interface{}, bool) {
index, err := strconv.ParseUint(p[0], 10, 64)
if err != nil || paramsValueSliceLength <= int(index) {
return nil, false
return nil, &ParameterNodeError{s}
}
return GetParameter(p[1], params.([]interface{})[index])
@ -279,18 +357,18 @@ func GetParameter(s string, params interface{}) (interface{}, bool) {
index, err := strconv.ParseUint(s, 10, 64)
if err != nil || paramsValueSliceLength <= int(index) {
return nil, false
return nil, &ParameterNodeError{s}
}
return params.([]interface{})[index], true
return params.([]interface{})[index], nil
}
return nil, false
return nil, &ParameterNodeError{s}
case reflect.Map:
// Check for raw key
if v, ok := params.(map[string]interface{})[s]; ok {
return v, true
return v, nil
}
// Checked for dotted references
@ -300,19 +378,21 @@ func GetParameter(s string, params interface{}) (interface{}, bool) {
return GetParameter(p[1], pValue)
}
return pValue, true
return pValue, nil
}
}
return nil, false
return nil, &ParameterNodeError{s}
}
// ExtractParameterAsString extracts value from interface{} as string based on the passed string
func ExtractParameterAsString(s string, params interface{}) (string, bool) {
if pValue, ok := GetParameter(s, params); ok {
return fmt.Sprintf("%v", pValue), true
func ExtractParameterAsString(s string, params interface{}) (string, error) {
pValue, err := GetParameter(s, params)
if err != nil {
return "", err
}
return "", false
return fmt.Sprintf("%v", pValue), nil
}
// Argument type specifies the parameter key name and the source it should
@ -326,7 +406,7 @@ type Argument struct {
// Get Argument method returns the value for the Argument's key name
// based on the Argument's source
func (ha *Argument) Get(headers, query, payload *map[string]interface{}, context *map[string]interface{}) (string, bool) {
func (ha *Argument) Get(headers, query, payload *map[string]interface{}, context *map[string]interface{}) (string, error) {
var source *map[string]interface{}
key := ha.Name
@ -341,35 +421,35 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}, context
case SourceContext:
source = context
case SourceString:
return ha.Name, true
return ha.Name, nil
case SourceEntirePayload:
r, err := json.Marshal(payload)
if err != nil {
return "", false
return "", err
}
return string(r), true
return string(r), nil
case SourceEntireHeaders:
r, err := json.Marshal(headers)
if err != nil {
return "", false
return "", err
}
return string(r), true
return string(r), nil
case SourceEntireQuery:
r, err := json.Marshal(query)
if err != nil {
return "", false
return "", err
}
return string(r), true
return string(r), nil
}
if source != nil {
return ExtractParameterAsString(key, *source)
}
return "", false
return "", errors.New("no source for value retrieval")
}
// Header is a structure containing header name and it's value
@ -455,6 +535,7 @@ type Hook struct {
TriggerRuleMismatchHttpResponseCode int `json:"trigger-rule-mismatch-http-response-code,omitempty"`
IncomingPayloadContentType string `json:"incoming-payload-content-type,omitempty"`
SuccessHttpResponseCode int `json:"success-http-response-code,omitempty"`
HTTPMethods []string `json:"http-methods"`
}
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
@ -463,7 +544,10 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
errors := make([]error, 0)
for i := range h.JSONStringParameters {
if arg, ok := h.JSONStringParameters[i].Get(headers, query, payload, context); ok {
arg, err := h.JSONStringParameters[i].Get(headers, query, payload, context)
if err != nil {
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
} else {
var newArg map[string]interface{}
decoder := json.NewDecoder(strings.NewReader(string(arg)))
@ -499,8 +583,6 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
} else {
errors = append(errors, &SourceError{h.JSONStringParameters[i]})
}
} else {
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
}
}
@ -520,12 +602,14 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
args = append(args, h.ExecuteCommand)
for i := range h.PassArgumentsToCommand {
if arg, ok := h.PassArgumentsToCommand[i].Get(headers, query, payload, context); ok {
args = append(args, arg)
} else {
arg, err := h.PassArgumentsToCommand[i].Get(headers, query, payload, context)
if err != nil {
args = append(args, "")
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
continue
}
args = append(args, arg)
}
if len(errors) > 0 {
@ -542,16 +626,18 @@ func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string
args := make([]string, 0)
errors := make([]error, 0)
for i := range h.PassEnvironmentToCommand {
if arg, ok := h.PassEnvironmentToCommand[i].Get(headers, query, payload, context); ok {
if h.PassEnvironmentToCommand[i].EnvName != "" {
// first try to use the EnvName if specified
args = append(args, h.PassEnvironmentToCommand[i].EnvName+"="+arg)
} else {
// then fallback on the name
args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg)
}
} else {
arg, err := h.PassEnvironmentToCommand[i].Get(headers, query, payload, context)
if err != nil {
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
continue
}
if h.PassEnvironmentToCommand[i].EnvName != "" {
// first try to use the EnvName if specified
args = append(args, h.PassEnvironmentToCommand[i].EnvName+"="+arg)
} else {
// then fallback on the name
args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg)
}
}
@ -576,30 +662,30 @@ func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[strin
args := make([]FileParameter, 0)
errors := make([]error, 0)
for i := range h.PassFileToCommand {
if arg, ok := h.PassFileToCommand[i].Get(headers, query, payload, context); ok {
if h.PassFileToCommand[i].EnvName == "" {
// if no environment-variable name is set, fall-back on the name
log.Printf("no ENVVAR name specified, falling back to [%s]", EnvNamespace+strings.ToUpper(h.PassFileToCommand[i].Name))
h.PassFileToCommand[i].EnvName = EnvNamespace + strings.ToUpper(h.PassFileToCommand[i].Name)
}
var fileContent []byte
if h.PassFileToCommand[i].Base64Decode {
dec, err := base64.StdEncoding.DecodeString(arg)
if err != nil {
log.Printf("error decoding string [%s]", err)
}
fileContent = []byte(dec)
} else {
fileContent = []byte(arg)
}
args = append(args, FileParameter{EnvName: h.PassFileToCommand[i].EnvName, Data: fileContent})
} else {
arg, err := h.PassFileToCommand[i].Get(headers, query, payload, context)
if err != nil {
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
continue
}
if h.PassFileToCommand[i].EnvName == "" {
// if no environment-variable name is set, fall-back on the name
log.Printf("no ENVVAR name specified, falling back to [%s]", EnvNamespace+strings.ToUpper(h.PassFileToCommand[i].Name))
h.PassFileToCommand[i].EnvName = EnvNamespace + strings.ToUpper(h.PassFileToCommand[i].Name)
}
var fileContent []byte
if h.PassFileToCommand[i].Base64Decode {
dec, err := base64.StdEncoding.DecodeString(arg)
if err != nil {
log.Printf("error decoding string [%s]", err)
}
fileContent = []byte(dec)
} else {
fileContent = []byte(arg)
}
args = append(args, FileParameter{EnvName: h.PassFileToCommand[i].EnvName, Data: fileContent})
}
if len(errors) > 0 {
@ -645,8 +731,7 @@ func (h *Hooks) LoadFromFile(path string, asTemplate bool) error {
file = buf.Bytes()
}
e = yaml.Unmarshal(file, h)
return e
return yaml.Unmarshal(file, h)
}
// Append appends hooks unless the new hooks contain a hook with an ID that already exists
@ -768,6 +853,7 @@ const (
MatchRegex string = "regex"
MatchHashSHA1 string = "payload-hash-sha1"
MatchHashSHA256 string = "payload-hash-sha256"
MatchHashSHA512 string = "payload-hash-sha512"
IPWhitelist string = "ip-whitelist"
ScalrSignature string = "scalr-signature"
)
@ -782,10 +868,11 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, con
return CheckScalrSignature(*headers, *body, r.Secret, true)
}
if arg, ok := r.Parameter.Get(headers, query, payload, context); ok {
arg, err := r.Parameter.Get(headers, query, payload, context)
if err == nil {
switch r.Type {
case MatchValue:
return arg == r.Value, nil
return compare(arg, r.Value), nil
case MatchRegex:
return regexp.MatchString(r.Regex, arg)
case MatchHashSHA1:
@ -794,9 +881,17 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, con
case MatchHashSHA256:
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
return err == nil, err
case MatchHashSHA512:
_, err := CheckPayloadSignature512(*body, r.Secret, arg)
return err == nil, err
}
}
return false, nil
return false, err
}
// compare is a helper function for constant time string comparisons.
func compare(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
// getenv provides a template function to retrieve OS environment variables.

View file

@ -28,9 +28,9 @@ func TestGetParameter(t *testing.T) {
{"z.b", map[string]interface{}{"a": map[string]interface{}{"z": 2}}, nil, false},
{"a.2", map[string]interface{}{"a": []interface{}{"a", "b"}}, nil, false},
} {
res, ok := GetParameter(test.key, test.val)
if ok != test.ok {
t.Errorf("unexpected result given {%q, %q}: %t\n", test.key, test.val, ok)
res, err := GetParameter(test.key, test.val)
if (err == nil) != test.ok {
t.Errorf("unexpected result given {%q, %q}: %s\n", test.key, test.val, err)
}
if !reflect.DeepEqual(res, test.expect) {
@ -48,8 +48,11 @@ var checkPayloadSignatureTests = []struct {
}{
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
// failures
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
}
@ -76,8 +79,11 @@ var checkPayloadSignature256Tests = []struct {
}{
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
// failures
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
}
@ -94,6 +100,33 @@ func TestCheckPayloadSignature256(t *testing.T) {
}
}
var checkPayloadSignature512Tests = []struct {
payload []byte
secret string
signature string
mac string
ok bool
}{
{[]byte(`{"a": "z"}`), "secret", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
{[]byte(`{"a": "z"}`), "secret", "sha512=4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
// failures
{[]byte(`{"a": "z"}`), "secret", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", false},
{[]byte(`{"a": "z"}`), "", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "", false},
}
func TestCheckPayloadSignature512(t *testing.T) {
for _, tt := range checkPayloadSignature512Tests {
mac, err := CheckPayloadSignature512(tt.payload, tt.secret, tt.signature)
if (err == nil) != tt.ok || mac != tt.mac {
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
}
if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
t.Errorf("error message should not disclose expected mac: %s", err)
}
}
}
var checkScalrSignatureTests = []struct {
description string
headers map[string]interface{}
@ -198,9 +231,9 @@ var extractParameterTests = []struct {
func TestExtractParameter(t *testing.T) {
for _, tt := range extractParameterTests {
value, ok := ExtractParameterAsString(tt.s, tt.params)
if ok != tt.ok || value != tt.value {
t.Errorf("failed to extract parameter %q:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, ok:%#v}", tt.s, tt.value, tt.ok, value, ok)
value, err := ExtractParameterAsString(tt.s, tt.params)
if (err == nil) != tt.ok || value != tt.value {
t.Errorf("failed to extract parameter %q:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.s, tt.value, tt.ok, value, err)
}
}
}
@ -225,9 +258,9 @@ var argumentGetTests = []struct {
func TestArgumentGet(t *testing.T) {
for _, tt := range argumentGetTests {
a := Argument{tt.source, tt.name, "", false}
value, ok := a.Get(tt.headers, tt.query, tt.payload)
if ok != tt.ok || value != tt.value {
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, ok:%#v}", tt.source, tt.name, tt.value, tt.ok, value, ok)
value, err := a.Get(tt.headers, tt.query, tt.payload)
if (err == nil) != tt.ok || value != tt.value {
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.source, tt.name, tt.value, tt.ok, value, err)
}
}
}
@ -346,10 +379,10 @@ var hooksLoadFromFileTests = []struct {
asTemplate bool
ok bool
}{
{"../hooks.json.example", false, true},
{"../hooks.yaml.example", false, true},
{"../hooks.json.tmpl.example", true, true},
{"../hooks.yaml.tmpl.example", true, true},
{"../../hooks.json.example", false, true},
{"../../hooks.yaml.example", false, true},
{"../../hooks.json.tmpl.example", true, true},
{"../../hooks.yaml.tmpl.example", true, true},
{"", false, true},
// failures
{"missing.json", false, false},
@ -425,7 +458,7 @@ var matchRuleTests = []struct {
// failures
{"value", "", "", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, false}, // reference invalid header
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
// errors
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
@ -450,7 +483,7 @@ func TestMatchRule(t *testing.T) {
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, tt.remoteAddr)
if ok != tt.ok || (err != nil) != tt.err {
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, (err != nil))
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
}
}
}
@ -513,7 +546,7 @@ var andRuleTests = []struct {
"invalid rule",
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
false, false,
false, true,
},
}
@ -568,7 +601,7 @@ var orRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
},
&map[string]interface{}{"Y": "Z"}, nil, nil, []byte{},
false, false,
false, true,
},
}
@ -601,3 +634,17 @@ func TestNotRule(t *testing.T) {
}
}
}
func TestCompare(t *testing.T) {
for _, tt := range []struct {
a, b string
ok bool
}{
{"abcd", "abcd", true},
{"zyxw", "abcd", false},
} {
if ok := compare(tt.a, tt.b); ok != tt.ok {
t.Errorf("compare failed for %q and %q: got %v\n", tt.a, tt.b, ok)
}
}
}

View file

@ -0,0 +1,124 @@
package middleware
// Derived from from the Goa project, MIT Licensed
// https://github.com/goadesign/goa/blob/v3/http/middleware/debug.go
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"sort"
"strings"
"github.com/gorilla/mux"
)
// responseDupper tees the response to a buffer and a response writer.
type responseDupper struct {
http.ResponseWriter
Buffer *bytes.Buffer
Status int
}
// Dumper returns a debug middleware which prints detailed information about
// incoming requests and outgoing responses including all headers, parameters
// and bodies.
func Dumper(w io.Writer) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
// Request ID
rid := r.Context().Value(RequestIDKey)
// Request URL
buf.WriteString(fmt.Sprintf("> [%s] %s %s", rid, r.Method, r.URL.String()))
// Request Headers
keys := make([]string, len(r.Header))
i := 0
for k := range r.Header {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
}
// Request parameters
params := mux.Vars(r)
keys = make([]string, len(params))
i = 0
for k := range params {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
}
// Request body
b, err := ioutil.ReadAll(r.Body)
if err != nil {
b = []byte("failed to read body: " + err.Error())
}
if len(b) > 0 {
buf.WriteByte('\n')
lines := strings.Split(string(b), "\n")
for _, line := range lines {
buf.WriteString(fmt.Sprintf("> [%s] %s\n", rid, line))
}
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
dupper := &responseDupper{ResponseWriter: rw, Buffer: &bytes.Buffer{}}
h.ServeHTTP(dupper, r)
buf.WriteString(fmt.Sprintf("\n< [%s] %s", rid, http.StatusText(dupper.Status)))
keys = make([]string, len(dupper.Header()))
i = 0
for k := range dupper.Header() {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
buf.WriteString(fmt.Sprintf("\n< [%s] %s: %s", rid, k, strings.Join(dupper.Header()[k], ", ")))
}
if dupper.Buffer.Len() > 0 {
buf.WriteByte('\n')
lines := strings.Split(dupper.Buffer.String(), "\n")
for _, line := range lines {
buf.WriteString(fmt.Sprintf("< [%s] %s\n", rid, line))
}
}
buf.WriteByte('\n')
w.Write(buf.Bytes())
})
}
}
// Write writes the data to the buffer and connection as part of an HTTP reply.
func (r *responseDupper) Write(b []byte) (int, error) {
r.Buffer.Write(b)
return r.ResponseWriter.Write(b)
}
// WriteHeader records the status and sends an HTTP response header with status code.
func (r *responseDupper) WriteHeader(s int) {
r.Status = s
r.ResponseWriter.WriteHeader(s)
}
// Hijack supports the http.Hijacker interface.
func (r *responseDupper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
return hijacker.Hijack()
}
return nil, nil, fmt.Errorf("dumper middleware: inner ResponseWriter cannot be hijacked: %T", r.ResponseWriter)
}

View file

@ -0,0 +1,59 @@
package middleware
import (
"bytes"
"fmt"
"log"
"net/http"
"time"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi/middleware"
)
// Logger is a middleware that logs useful data about each HTTP request.
type Logger struct {
Logger middleware.LoggerInterface
}
// NewLogger creates a new RequestLogger Handler.
func NewLogger() func(next http.Handler) http.Handler {
return middleware.RequestLogger(&Logger{})
}
// NewLogEntry creates a new LogEntry for the request.
func (l *Logger) NewLogEntry(r *http.Request) middleware.LogEntry {
e := &LogEntry{
req: r,
buf: &bytes.Buffer{},
}
return e
}
// LogEntry represents an individual log entry.
type LogEntry struct {
*Logger
req *http.Request
buf *bytes.Buffer
}
// Write constructs and writes the final log entry.
func (l *LogEntry) Write(status, bytes int, elapsed time.Duration) {
rid := GetReqID(l.req.Context())
if rid != "" {
fmt.Fprintf(l.buf, "[%s] ", rid)
}
fmt.Fprintf(l.buf, "%03d | %s | %s | ", status, humanize.IBytes(uint64(bytes)), elapsed)
l.buf.WriteString(l.req.Host + " | " + l.req.Method + " " + l.req.RequestURI)
log.Print(l.buf.String())
}
/// Panic prints the call stack for a panic.
func (l *LogEntry) Panic(v interface{}, stack []byte) {
e := l.NewLogEntry(l.req).(*LogEntry)
fmt.Fprintf(e.buf, "panic: %#v", v)
log.Print(e.buf.String())
log.Print(string(stack))
}

View file

@ -0,0 +1,98 @@
package middleware
// Derived from Goa project, MIT Licensed
// https://github.com/goadesign/goa/blob/v3/http/middleware/requestid.go
import (
"context"
"net/http"
"github.com/gofrs/uuid"
)
// Key to use when setting the request ID.
type ctxKeyRequestID int
// RequestIDKey is the key that holds the unique request ID in a request context.
const RequestIDKey ctxKeyRequestID = 0
// RequestID is a middleware that injects a request ID into the context of each
// request.
func RequestID(options ...RequestIDOption) func(http.Handler) http.Handler {
o := newRequestIDOptions(options...)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var id string
if o.UseRequestID() {
id = r.Header.Get("X-Request-Id")
if o.requestIDLimit > 0 && len(id) > o.requestIDLimit {
id = id[:o.requestIDLimit]
}
}
if id == "" {
id = uuid.Must(uuid.NewV4()).String()[:6]
}
ctx = context.WithValue(ctx, RequestIDKey, id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// GetReqID returns a request ID from the given context if one is present.
// Returns the empty string if a request ID cannot be found.
func GetReqID(ctx context.Context) string {
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
}
func UseXRequestIDHeaderOption(f bool) RequestIDOption {
return func(o *RequestIDOptions) *RequestIDOptions {
o.useXRequestID = f
return o
}
}
func XRequestIDLimitOption(limit int) RequestIDOption {
return func(o *RequestIDOptions) *RequestIDOptions {
o.requestIDLimit = limit
return o
}
}
type (
RequestIDOption func(*RequestIDOptions) *RequestIDOptions
RequestIDOptions struct {
// useXRequestID enabled the use of the X-Request-Id request header as
// the request ID.
useXRequestID bool
// requestIDLimit is the maximum length of the X-Request-Id header
// allowed. Values longer than this value are truncated. Zero value
// means no limit.
requestIDLimit int
}
)
func newRequestIDOptions(options ...RequestIDOption) *RequestIDOptions {
o := new(RequestIDOptions)
for _, opt := range options {
o = opt(o)
}
return o
}
func (o *RequestIDOptions) UseRequestID() bool {
return o.useXRequestID
}

View file

@ -0,0 +1,4 @@
Package pidfile is derived from github.com/moby/moby/pkg/pidfile.
Moby is licensed under the Apache License, Version 2.0.
Copyright 2012-2017 Docker, Inc.

View file

@ -0,0 +1,11 @@
// +build !windows
package pidfile
import "os"
// MkdirAll creates a directory named path along with any necessary parents,
// with permission specified by attribute perm for all dir created.
func MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}

View file

@ -0,0 +1,109 @@
// +build windows
package pidfile
import (
"os"
"regexp"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
// MkdirAll implementation that is volume path aware for Windows. It can be used
// as a drop-in replacement for os.MkdirAll()
func MkdirAll(path string, _ os.FileMode) error {
return mkdirall(path, false, "")
}
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
// so that it is both volume path aware, and can create a directory with
// a DACL.
func mkdirall(path string, applyACL bool, sddl string) error {
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
return nil
}
// The rest of this method is largely copied from os.MkdirAll and should be kept
// as-is to ensure compatibility.
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{
Op: "mkdir",
Path: path,
Err: syscall.ENOTDIR,
}
}
// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
i--
}
j := i
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
j--
}
if j > 1 {
// Create parent
err = mkdirall(path[0:j-1], false, sddl)
if err != nil {
return err
}
}
// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
if applyACL {
err = mkdirWithACL(path, sddl)
} else {
err = os.Mkdir(path, 0)
}
if err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}
// mkdirWithACL creates a new directory. If there is an error, it will be of
// type *PathError. .
//
// This is a modified and combined version of os.Mkdir and windows.Mkdir
// in golang to cater for creating a directory am ACL permitting full
// access, with inheritance, to any subfolder/file for Built-in Administrators
// and Local System.
func mkdirWithACL(name string, sddl string) error {
sa := windows.SecurityAttributes{Length: 0}
sd, err := windows.SecurityDescriptorFromString(sddl)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
sa.SecurityDescriptor = sd
namep, err := windows.UTF16PtrFromString(name)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
e := windows.CreateDirectory(namep, &sa)
if e != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: e}
}
return nil
}

View file

@ -0,0 +1,51 @@
// Package pidfile provides structure and helper functions to create and remove
// PID file. A PID file is usually a file used to store the process ID of a
// running process.
package pidfile
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
// PIDFile is a file used to store the process ID of a running process.
type PIDFile struct {
path string
}
func checkPIDFileAlreadyExists(path string) error {
if pidByte, err := ioutil.ReadFile(path); err == nil {
pidString := strings.TrimSpace(string(pidByte))
if pid, err := strconv.Atoi(pidString); err == nil {
if processExists(pid) {
return fmt.Errorf("pid file found, ensure webhook is not running or delete %s", path)
}
}
}
return nil
}
// New creates a PIDfile using the specified path.
func New(path string) (*PIDFile, error) {
if err := checkPIDFileAlreadyExists(path); err != nil {
return nil, err
}
// Note MkdirAll returns nil if a directory already exists
if err := MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
return nil, err
}
if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
return nil, err
}
return &PIDFile{path: path}, nil
}
// Remove removes the PIDFile.
func (file PIDFile) Remove() error {
return os.Remove(file.path)
}

View file

@ -0,0 +1,14 @@
// +build darwin
package pidfile
import (
"golang.org/x/sys/unix"
)
func processExists(pid int) bool {
// OS X does not have a proc filesystem.
// Use kill -0 pid to judge if the process exists.
err := unix.Kill(pid, 0)
return err == nil
}

View file

@ -0,0 +1,38 @@
package pidfile
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestNewAndRemove(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "test-pidfile")
if err != nil {
t.Fatal("Could not create test directory")
}
path := filepath.Join(dir, "testfile")
file, err := New(path)
if err != nil {
t.Fatal("Could not create test file", err)
}
_, err = New(path)
if err == nil {
t.Fatal("Test file creation not blocked")
}
if err := file.Remove(); err != nil {
t.Fatal("Could not delete created test file")
}
}
func TestRemoveInvalidPath(t *testing.T) {
file := PIDFile{path: filepath.Join("foo", "bar")}
if err := file.Remove(); err == nil {
t.Fatal("Non-existing file doesn't give an error on delete")
}
}

View file

@ -0,0 +1,16 @@
// +build !windows,!darwin
package pidfile
import (
"os"
"path/filepath"
"strconv"
)
func processExists(pid int) bool {
if _, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid))); err == nil {
return true
}
return false
}

View file

@ -0,0 +1,25 @@
package pidfile
import (
"golang.org/x/sys/windows"
)
const (
processQueryLimitedInformation = 0x1000
stillActive = 259
)
func processExists(pid int) bool {
h, err := windows.OpenProcess(processQueryLimitedInformation, false, uint32(pid))
if err != nil {
return false
}
var c uint32
err = windows.GetExitCodeProcess(h, &c)
windows.Close(h)
if err != nil {
return c == stillActive
}
return true
}

View file

@ -14,6 +14,9 @@ func setupSignals() {
signals = make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGUSR1)
signal.Notify(signals, syscall.SIGHUP)
signal.Notify(signals, syscall.SIGTERM)
signal.Notify(signals, os.Interrupt)
go watchForSignals()
}
@ -23,11 +26,26 @@ func watchForSignals() {
for {
sig := <-signals
if sig == syscall.SIGUSR1 {
switch sig {
case syscall.SIGUSR1:
log.Println("caught USR1 signal")
reloadAllHooks()
} else {
case syscall.SIGHUP:
log.Println("caught HUP signal")
reloadAllHooks()
case os.Interrupt, syscall.SIGTERM:
log.Printf("caught %s signal; exiting\n", sig)
if pidFile != nil {
err := pidFile.Remove()
if err != nil {
log.Print(err)
}
}
os.Exit(0)
default:
log.Printf("caught unhandled signal %+v\n", sig)
}
}

View file

@ -3,6 +3,7 @@
"id": "github",
"execute-command": "{{ .Hookecho }}",
"command-working-directory": "/",
"http-methods": ["Post "],
"include-command-output-in-response": true,
"trigger-rule-mismatch-http-response-code": 400,
"pass-environment-to-command":
@ -137,6 +138,60 @@
}
}
},
{
"id": "xml",
"execute-command": "{{ .Hookecho }}",
"command-working-directory": "/",
"response-message": "success",
"trigger-rule": {
"and": [
{
"match": {
"type": "value",
"parameter": {
"source": "payload",
"name": "app.users.user.0.-name"
},
"value": "Jeff"
}
},
{
"match": {
"type": "value",
"parameter": {
"source": "payload",
"name": "app.messages.message.#text"
},
"value": "Hello!!"
}
},
],
}
},
{
"id": "plex",
"execute-command": "{{ .Hookecho }}",
"command-working-directory": "/",
"response-message": "success",
"parse-parameters-as-json": [
{
"source": "payload",
"name": "payload"
}
],
"trigger-rule":
{
"match":
{
"type": "value",
"parameter": {
"source": "payload",
"name": "payload.event"
},
"value": "media.play"
}
}
},
{
"id": "capture-command-output-on-success-not-by-default",
"pass-arguments-to-command": [

View file

@ -1,4 +1,6 @@
- id: github
http-methods:
- "Post "
trigger-rule:
and:
- match:
@ -76,6 +78,40 @@
include-command-output-in-response: true
command-working-directory: /
- id: xml
execute-command: '{{ .Hookecho }}'
command-working-directory: /
response-message: success
trigger-rule:
and:
- match:
type: value
parameter:
source: payload
name: app.users.user.0.-name
value: Jeff
- match:
type: value
parameter:
source: payload
name: "app.messages.message.#text"
value: "Hello!!"
- id: plex
trigger-rule:
match:
type: value
parameter:
source: payload
name: payload.event
value: media.play
parse-parameters-as-json:
- source: payload
name: payload
execute-command: '{{ .Hookecho }}'
response-message: success
command-working-directory: /
- id: capture-command-output-on-success-not-by-default
pass-arguments-to-command:
- source: string
@ -113,4 +149,4 @@
- id: warn-on-space
execute-command: '{{ .Hookecho }} foo'
include-command-output-in-response: true
include-command-output-in-response: true

85
tls.go Normal file
View file

@ -0,0 +1,85 @@
package main
import (
"crypto/tls"
"io"
"log"
"strings"
)
func writeTLSSupportedCipherStrings(w io.Writer, min uint16) error {
for _, c := range CipherSuites() {
var found bool
for _, v := range c.SupportedVersions {
if v >= min {
found = true
}
}
if !found {
continue
}
_, err := w.Write([]byte(c.Name + "\n"))
if err != nil {
return err
}
}
return nil
}
// getTLSMinVersion converts a version string into a TLS version ID.
func getTLSMinVersion(v string) uint16 {
switch v {
case "1.0":
return tls.VersionTLS10
case "1.1":
return tls.VersionTLS11
case "1.2", "":
return tls.VersionTLS12
case "1.3":
return tls.VersionTLS13
default:
log.Fatalln("error: unknown minimum TLS version:", v)
return 0
}
}
// getTLSCipherSuites converts a comma separated list of cipher suites into a
// slice of TLS cipher suite IDs.
func getTLSCipherSuites(v string) []uint16 {
supported := CipherSuites()
if v == "" {
suites := make([]uint16, len(supported))
for _, cs := range supported {
suites = append(suites, cs.ID)
}
return suites
}
var found bool
txts := strings.Split(v, ",")
suites := make([]uint16, len(txts))
for _, want := range txts {
found = false
for _, cs := range supported {
if want == cs.Name {
suites = append(suites, cs.ID)
found = true
}
}
if !found {
log.Fatalln("error: unknown TLS cipher suite:", want)
}
}
return suites
}

55
vendor/github.com/clbanning/mxj/LICENSE generated vendored Normal file
View file

@ -0,0 +1,55 @@
Copyright (c) 2012-2016 Charles Banning <clbanning@gmail.com>. All rights reserved.
The MIT License (MIT)
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.
===============================================================================
Go Language Copyright & License -
Copyright 2009 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.
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.

189
vendor/github.com/clbanning/mxj/anyxml.go generated vendored Normal file
View file

@ -0,0 +1,189 @@
package mxj
import (
"encoding/xml"
"reflect"
)
const (
DefaultElementTag = "element"
)
// Encode arbitrary value as XML.
//
// Note: unmarshaling the resultant
// XML may not return the original value, since tag labels may have been injected
// to create the XML representation of the value.
/*
Encode an arbitrary JSON object.
package main
import (
"encoding/json"
"fmt"
"github.com/clbanning/mxj"
)
func main() {
jsondata := []byte(`[
{ "somekey":"somevalue" },
"string",
3.14159265,
true
]`)
var i interface{}
err := json.Unmarshal(jsondata, &i)
if err != nil {
// do something
}
x, err := mxj.AnyXmlIndent(i, "", " ", "mydoc")
if err != nil {
// do something else
}
fmt.Println(string(x))
}
output:
<mydoc>
<somekey>somevalue</somekey>
<element>string</element>
<element>3.14159265</element>
<element>true</element>
</mydoc>
*/
// Alternative values for DefaultRootTag and DefaultElementTag can be set as:
// AnyXml( v, myRootTag, myElementTag).
func AnyXml(v interface{}, tags ...string) ([]byte, error) {
var rt, et string
if len(tags) == 1 || len(tags) == 2 {
rt = tags[0]
} else {
rt = DefaultRootTag
}
if len(tags) == 2 {
et = tags[1]
} else {
et = DefaultElementTag
}
if v == nil {
if useGoXmlEmptyElemSyntax {
return []byte("<" + rt + "></" + rt + ">"), nil
}
return []byte("<" + rt + "/>"), nil
}
if reflect.TypeOf(v).Kind() == reflect.Struct {
return xml.Marshal(v)
}
var err error
s := new(string)
p := new(pretty)
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">"
for _, vv := range v.([]interface{}) {
switch vv.(type) {
case map[string]interface{}:
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(false, s, tag, val, p)
}
} else {
err = mapToXmlIndent(false, s, et, vv, p)
}
default:
err = mapToXmlIndent(false, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.Xml(rt)
default:
err = mapToXmlIndent(false, s, rt, v, p)
b = []byte(*s)
}
return b, err
}
// Encode an arbitrary value as a pretty XML string.
// Alternative values for DefaultRootTag and DefaultElementTag can be set as:
// AnyXmlIndent( v, "", " ", myRootTag, myElementTag).
func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte, error) {
var rt, et string
if len(tags) == 1 || len(tags) == 2 {
rt = tags[0]
} else {
rt = DefaultRootTag
}
if len(tags) == 2 {
et = tags[1]
} else {
et = DefaultElementTag
}
if v == nil {
if useGoXmlEmptyElemSyntax {
return []byte(prefix + "<" + rt + "></" + rt + ">"), nil
}
return []byte(prefix + "<" + rt + "/>"), nil
}
if reflect.TypeOf(v).Kind() == reflect.Struct {
return xml.MarshalIndent(v, prefix, indent)
}
var err error
s := new(string)
p := new(pretty)
p.indent = indent
p.padding = prefix
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">\n"
p.Indent()
for _, vv := range v.([]interface{}) {
switch vv.(type) {
case map[string]interface{}:
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(true, s, tag, val, p)
}
} else {
p.start = 1 // we 1 tag in
err = mapToXmlIndent(true, s, et, vv, p)
*s += "\n"
}
default:
p.start = 0 // in case trailing p.start = 1
err = mapToXmlIndent(true, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.XmlIndent(prefix, indent, rt)
default:
err = mapToXmlIndent(true, s, rt, v, p)
b = []byte(*s)
}
return b, err
}

54
vendor/github.com/clbanning/mxj/atomFeedString.xml generated vendored Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
An attempt at adding pubsubhubbub support to Rietveld.
http://code.google.com/p/pubsubhubbub
http://code.google.com/p/rietveld/issues/detail?id=155
The server side of the protocol is trivial:
1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
feeds that will be pubsubhubbubbed.
2. every time one of those feeds changes, tell the hub
with a simple POST request.
I have tested this by adding debug prints to a local hub
server and checking that the server got the right publish
requests.
I can&amp;#39;t quite get the server to work, but I think the bug
is not in my code. I think that the server expects to be
able to grab the feed and see the feed&amp;#39;s actual URL in
the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
the :port from the URL, and I cannot for the life of me
figure out how to get the Atom generator deep inside
django not to do that, or even where it is doing that,
or even what code is running to generate the Atom feed.
(I thought I knew but I added some assert False statements
and it kept running!)
Ignoring that particular problem, I would appreciate
feedback on the right way to get the two values at
the top of feeds.py marked NOTE(rsc).
</summary></entry><entry><title>rietveld: correct tab handling
</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html">
This fixes the buggy tab rendering that can be seen at
http://codereview.appspot.com/116075/diff/1/2
The fundamental problem was that the tab code was
not being told what column the text began in, so it
didn&amp;#39;t know where to put the tab stops. Another problem
was that some of the code assumed that string byte
offsets were the same as column offsets, which is only
true if there are no tabs.
In the process of fixing this, I cleaned up the arguments
to Fold and ExpandTabs and renamed them Break and
_ExpandTabs so that I could be sure that I found all the
call sites. I also wanted to verify that ExpandTabs was
not being used from outside intra_region_diff.py.
</summary></entry></feed> `

134
vendor/github.com/clbanning/mxj/doc.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2015, 2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
/*
Marshal/Unmarshal XML to/from map[string]interface{} values (and JSON); extract/modify values from maps by key or key-path, including wildcards.
mxj supplants the legacy x2j and j2x packages. The subpackage x2j-wrapper is provided to facilitate migrating from the x2j package. The x2j and j2x subpackages provide similar functionality of the old packages but are not function-name compatible with them.
Note: this library was designed for processing ad hoc anonymous messages. Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly.
Related Packages:
checkxml: github.com/clbanning/checkxml provides functions for validating XML data.
Notes:
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.
2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing.
2017.02.21: github.com/clbanning/checkxml provides functions for validating XML data.
2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods.
2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag().
2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc.
2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix().
2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable.
2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars().
2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf".
To cast them to float64, first set flag with CastNanInf(true).
2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure.
2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization.
2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM).
2015-12-02: NewMapXmlSeq() with mv.XmlSeq() & co. will try to preserve structure of XML doc when re-encoding.
2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML.
SUMMARY
type Map map[string]interface{}
Create a Map value, 'mv', from any map[string]interface{} value, 'v':
mv := Map(v)
Unmarshal / marshal XML as a Map value, 'mv':
mv, err := NewMapXml(xmlValue) // unmarshal
xmlValue, err := mv.Xml() // marshal
Unmarshal XML from an io.Reader as a Map value, 'mv':
mv, err := NewMapXmlReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
mv, raw, err := NewMapXmlReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded
Marshal Map value, 'mv', to an XML Writer (io.Writer):
err := mv.XmlWriter(xmlWriter)
raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter
Also, for prettified output:
xmlValue, err := mv.XmlIndent(prefix, indent, ...)
err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...)
raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...)
Bulk process XML with error handling (note: handlers must return a boolean value):
err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error))
err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte))
Converting XML to JSON: see Examples for NewMapXml and HandleXmlReader.
There are comparable functions and methods for JSON processing.
Arbitrary structure values can be decoded to / encoded from Map values:
mv, err := NewMapStruct(structVal)
err := mv.Struct(structPointer)
To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON
or structure to a Map value, 'mv', or cast a map[string]interface{} value to a Map value, 'mv', then:
paths := mv.PathsForKey(key)
path := mv.PathForKeyShortest(key)
values, err := mv.ValuesForKey(key, subkeys)
values, err := mv.ValuesForPath(path, subkeys) // 'path' can be dot-notation with wildcards and indexed arrays.
count, err := mv.UpdateValuesForPath(newVal, path, subkeys)
Get everything at once, irrespective of path depth:
leafnodes := mv.LeafNodes()
leafvalues := mv.LeafValues()
A new Map with whatever keys are desired can be created from the current Map and then encoded in XML
or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.)
newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
newMap, err := mv.NewMap("oldKey1", "oldKey3", "oldKey5") // a subset of 'mv'; see "examples/partial.go"
newXml, err := newMap.Xml() // for example
newJson, err := newMap.Json() // ditto
XML PARSING CONVENTIONS
Using NewMapXml()
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`,
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or
`SetAttrPrefix()`.)
- If the element is a simple element and has attributes, the element value
is given the key `#text` for its `map[string]interface{}` representation. (See
the 'atomFeedString.xml' test data, below.)
- XML comments, directives, and process instructions are ignored.
- If CoerceKeysToLower() has been called, then the resultant keys will be lower case.
Using NewMapXmlSeq()
- Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values
where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the
value for `<attr_label>`.
- All elements, except for the root, have a "#seq" key.
- Comments, directives, and process instructions are unmarshalled into the Map using the
keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more
specifics.)
- Name space syntax is preserved:
- <ns:key>something</ns.key> parses to map["ns:key"]interface{}{"something"}
- xmlns:ns="http://myns.com/ns" parses to map["xmlns:ns"]interface{}{"http://myns.com/ns"}
Both
- By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them
to be cast, set a flag to cast them using CastNanInf(true).
XML ENCODING CONVENTIONS
- 'nil' Map values, which may represent 'null' JSON values, are encoded as "<tag/>".
NOTE: the operation is not symmetric as "<tag/>" elements are decoded as 'tag:""' Map values,
which, then, encode in JSON as '"tag":""' values..
- ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go
randomizes the walk through map[string]interface{} values.) If you plan to re-encode the
Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and
mv.XmlSeq() - these try to preserve the element sequencing but with added complexity when
working with the Map representation.
*/
package mxj

54
vendor/github.com/clbanning/mxj/escapechars.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"bytes"
)
var xmlEscapeChars bool
// XMLEscapeChars(true) forces escaping invalid characters in attribute and element values.
// NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is
// then '&amp;' will be re-escaped as '&amp;amp;'.
//
/*
The values are:
" &quot;
' &apos;
< &lt;
> &gt;
& &amp;
*/
func XMLEscapeChars(b bool) {
xmlEscapeChars = b
}
// Scan for '&' first, since 's' may contain "&amp;" that is parsed to "&amp;amp;"
// - or "&lt;" that is parsed to "&amp;lt;".
var escapechars = [][2][]byte{
{[]byte(`&`), []byte(`&amp;`)},
{[]byte(`<`), []byte(`&lt;`)},
{[]byte(`>`), []byte(`&gt;`)},
{[]byte(`"`), []byte(`&quot;`)},
{[]byte(`'`), []byte(`&apos;`)},
}
func escapeChars(s string) string {
if len(s) == 0 {
return s
}
b := []byte(s)
for _, v := range escapechars {
n := bytes.Count(b, v[0])
if n == 0 {
continue
}
b = bytes.Replace(b, v[0], v[1], n)
}
return string(b)
}

7
vendor/github.com/clbanning/mxj/exists.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
package mxj
// Checks whether the path exists
func (mv Map) Exists(path string, subkeys ...string) bool {
v, err := mv.ValuesForPath(path, subkeys...)
return err == nil && len(v) > 0
}

287
vendor/github.com/clbanning/mxj/files.go generated vendored Normal file
View file

@ -0,0 +1,287 @@
package mxj
import (
"fmt"
"io"
"os"
)
type Maps []Map
func NewMaps() Maps {
return make(Maps, 0)
}
type MapRaw struct {
M Map
R []byte
}
// NewMapsFromXmlFile - creates an array from a file of JSON values.
func NewMapsFromJsonFile(name string) (Maps, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]Map, 0)
for {
m, raw, err := NewMapJsonReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw))
}
if len(m) > 0 {
am = append(am, m)
}
if err == io.EOF {
break
}
}
return am, nil
}
// ReadMapsFromJsonFileRaw - creates an array of MapRaw from a file of JSON values.
func NewMapsFromJsonFileRaw(name string) ([]MapRaw, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]MapRaw, 0)
for {
mr := new(MapRaw)
mr.M, mr.R, err = NewMapJsonReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R))
}
if len(mr.M) > 0 {
am = append(am, *mr)
}
if err == io.EOF {
break
}
}
return am, nil
}
// NewMapsFromXmlFile - creates an array from a file of XML values.
func NewMapsFromXmlFile(name string) (Maps, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]Map, 0)
for {
m, raw, err := NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw))
}
if len(m) > 0 {
am = append(am, m)
}
if err == io.EOF {
break
}
}
return am, nil
}
// NewMapsFromXmlFileRaw - creates an array of MapRaw from a file of XML values.
// NOTE: the slice with the raw XML is clean with no extra capacity - unlike NewMapXmlReaderRaw().
// It is slow at parsing a file from disk and is intended for relatively small utility files.
func NewMapsFromXmlFileRaw(name string) ([]MapRaw, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]MapRaw, 0)
for {
mr := new(MapRaw)
mr.M, mr.R, err = NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R))
}
if len(mr.M) > 0 {
am = append(am, *mr)
}
if err == io.EOF {
break
}
}
return am, nil
}
// ------------------------ Maps writing -------------------------
// These are handy-dandy methods for dumping configuration data, etc.
// JsonString - analogous to mv.Json()
func (mvs Maps) JsonString(safeEncoding ...bool) (string, error) {
var s string
for _, v := range mvs {
j, err := v.Json()
if err != nil {
return s, err
}
s += string(j)
}
return s, nil
}
// JsonStringIndent - analogous to mv.JsonIndent()
func (mvs Maps) JsonStringIndent(prefix, indent string, safeEncoding ...bool) (string, error) {
var s string
var haveFirst bool
for _, v := range mvs {
j, err := v.JsonIndent(prefix, indent)
if err != nil {
return s, err
}
if haveFirst {
s += "\n"
} else {
haveFirst = true
}
s += string(j)
}
return s, nil
}
// XmlString - analogous to mv.Xml()
func (mvs Maps) XmlString() (string, error) {
var s string
for _, v := range mvs {
x, err := v.Xml()
if err != nil {
return s, err
}
s += string(x)
}
return s, nil
}
// XmlStringIndent - analogous to mv.XmlIndent()
func (mvs Maps) XmlStringIndent(prefix, indent string) (string, error) {
var s string
for _, v := range mvs {
x, err := v.XmlIndent(prefix, indent)
if err != nil {
return s, err
}
s += string(x)
}
return s, nil
}
// JsonFile - write Maps to named file as JSON
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use JsonWriter method.
func (mvs Maps) JsonFile(file string, safeEncoding ...bool) error {
var encoding bool
if len(safeEncoding) == 1 {
encoding = safeEncoding[0]
}
s, err := mvs.JsonString(encoding)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// JsonFileIndent - write Maps to named file as pretty JSON
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use JsonIndentWriter method.
func (mvs Maps) JsonFileIndent(file, prefix, indent string, safeEncoding ...bool) error {
var encoding bool
if len(safeEncoding) == 1 {
encoding = safeEncoding[0]
}
s, err := mvs.JsonStringIndent(prefix, indent, encoding)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// XmlFile - write Maps to named file as XML
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use XmlWriter method.
func (mvs Maps) XmlFile(file string) error {
s, err := mvs.XmlString()
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// XmlFileIndent - write Maps to named file as pretty XML
// Note: the file will be created,if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use XmlIndentWriter method.
func (mvs Maps) XmlFileIndent(file, prefix, indent string) error {
s, err := mvs.XmlStringIndent(prefix, indent)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}

2
vendor/github.com/clbanning/mxj/files_test.badjson generated vendored Normal file
View file

@ -0,0 +1,2 @@
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
{ "with":"some", "bad":JSON, "in":"it" }

9
vendor/github.com/clbanning/mxj/files_test.badxml generated vendored Normal file
View file

@ -0,0 +1,9 @@
<doc>
<some>test</some>
<data>for files.go</data>
</doc>
<msg>
<just>some</just>
<another>doc</other>
<for>test case</for>
</msg>

2
vendor/github.com/clbanning/mxj/files_test.json generated vendored Normal file
View file

@ -0,0 +1,2 @@
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
{ "with":"just", "two":2, "JSON":"values", "true":true }

9
vendor/github.com/clbanning/mxj/files_test.xml generated vendored Normal file
View file

@ -0,0 +1,9 @@
<doc>
<some>test</some>
<data>for files.go</data>
</doc>
<msg>
<just>some</just>
<another>doc</another>
<for>test case</for>
</msg>

1
vendor/github.com/clbanning/mxj/files_test_dup.json generated vendored Normal file
View file

@ -0,0 +1 @@
{"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"}

1
vendor/github.com/clbanning/mxj/files_test_dup.xml generated vendored Normal file
View file

@ -0,0 +1 @@
<doc><data>for files.go</data><some>test</some></doc><msg><another>doc</another><for>test case</for><just>some</just></msg>

12
vendor/github.com/clbanning/mxj/files_test_indent.json generated vendored Normal file
View file

@ -0,0 +1,12 @@
{
"a": "test",
"file": "for",
"files_test.go": "case",
"this": "is"
}
{
"JSON": "values",
"true": true,
"two": 2,
"with": "just"
}

View file

@ -0,0 +1,8 @@
<doc>
<data>for files.go</data>
<some>test</some>
</doc><msg>
<another>doc</another>
<for>test case</for>
<just>some</just>
</msg>

35
vendor/github.com/clbanning/mxj/gob.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
// gob.go - Encode/Decode a Map into a gob object.
package mxj
import (
"bytes"
"encoding/gob"
)
// NewMapGob returns a Map value for a gob object that has been
// encoded from a map[string]interface{} (or compatible type) value.
// It is intended to provide symmetric handling of Maps that have
// been encoded using mv.Gob.
func NewMapGob(gobj []byte) (Map, error) {
m := make(map[string]interface{}, 0)
if len(gobj) == 0 {
return m, nil
}
r := bytes.NewReader(gobj)
dec := gob.NewDecoder(r)
if err := dec.Decode(&m); err != nil {
return m, err
}
return m, nil
}
// Gob returns a gob-encoded value for the Map 'mv'.
func (mv Map) Gob() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(map[string]interface{}(mv)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

323
vendor/github.com/clbanning/mxj/json.go generated vendored Normal file
View file

@ -0,0 +1,323 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"bytes"
"encoding/json"
"fmt"
"io"
"time"
)
// ------------------------------ write JSON -----------------------
// Just a wrapper on json.Marshal.
// If option safeEncoding is'true' then safe encoding of '<', '>' and '&'
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
func (mv Map) Json(safeEncoding ...bool) ([]byte, error) {
var s bool
if len(safeEncoding) == 1 {
s = safeEncoding[0]
}
b, err := json.Marshal(mv)
if !s {
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
}
return b, err
}
// Just a wrapper on json.MarshalIndent.
// If option safeEncoding is'true' then safe encoding of '<' , '>' and '&'
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) {
var s bool
if len(safeEncoding) == 1 {
s = safeEncoding[0]
}
b, err := json.MarshalIndent(mv, prefix, indent)
if !s {
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
}
return b, err
}
// The following implementation is provided for symmetry with NewMapJsonReader[Raw]
// The names will also provide a key for the number of return arguments.
// Writes the Map as JSON on the Writer.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error {
b, err := mv.Json(safeEncoding...)
if err != nil {
return err
}
_, err = jsonWriter.Write(b)
return err
}
// Writes the Map as JSON on the Writer. []byte is the raw JSON that was written.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) {
b, err := mv.Json(safeEncoding...)
if err != nil {
return b, err
}
_, err = jsonWriter.Write(b)
return b, err
}
// Writes the Map as pretty JSON on the Writer.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error {
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
if err != nil {
return err
}
_, err = jsonWriter.Write(b)
return err
}
// Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) {
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
if err != nil {
return b, err
}
_, err = jsonWriter.Write(b)
return b, err
}
// --------------------------- read JSON -----------------------------
// Decode numericvalues as json.Number type Map values - see encoding/json#Number.
// NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(),
// etc.; it does not affect NewMapXml(), etc. The XML encoders mv.Xml() and mv.XmlIndent()
// do recognize json.Number types; a JSON object can be decoded to a Map with json.Number
// value types and the resulting Map can be correctly encoded into a XML object.
var JsonUseNumber bool
// Just a wrapper on json.Unmarshal
// Converting JSON to XML is a simple as:
// ...
// mapVal, merr := mxj.NewMapJson(jsonVal)
// if merr != nil {
// // handle error
// }
// xmlVal, xerr := mapVal.Xml()
// if xerr != nil {
// // handle error
// }
// NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}],
// will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map.
// See mxj/j2x/j2x_test.go.
func NewMapJson(jsonVal []byte) (Map, error) {
// empty or nil begets empty
if len(jsonVal) == 0 {
m := make(map[string]interface{}, 0)
return m, nil
}
// handle a goofy case ...
if jsonVal[0] == '[' {
jsonVal = []byte(`{"object":` + string(jsonVal) + `}`)
}
m := make(map[string]interface{})
// err := json.Unmarshal(jsonVal, &m)
buf := bytes.NewReader(jsonVal)
dec := json.NewDecoder(buf)
if JsonUseNumber {
dec.UseNumber()
}
err := dec.Decode(&m)
return m, err
}
// Retrieve a Map value from an io.Reader.
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
// a JSON object.
func NewMapJsonReader(jsonReader io.Reader) (Map, error) {
jb, err := getJson(jsonReader)
if err != nil || len(*jb) == 0 {
return nil, err
}
// Unmarshal the 'presumed' JSON string
return NewMapJson(*jb)
}
// Retrieve a Map value and raw JSON - []byte - from an io.Reader.
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
// a JSON object and retrieve the raw JSON in a single call.
func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) {
jb, err := getJson(jsonReader)
if err != nil || len(*jb) == 0 {
return nil, *jb, err
}
// Unmarshal the 'presumed' JSON string
m, merr := NewMapJson(*jb)
return m, *jb, merr
}
// Pull the next JSON string off the stream: just read from first '{' to its closing '}'.
// Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package.
func getJson(rdr io.Reader) (*[]byte, error) {
bval := make([]byte, 1)
jb := make([]byte, 0)
var inQuote, inJson bool
var parenCnt int
var previous byte
// scan the input for a matched set of {...}
// json.Unmarshal will handle syntax checking.
for {
_, err := rdr.Read(bval)
if err != nil {
if err == io.EOF && inJson && parenCnt > 0 {
return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb))
}
return &jb, err
}
switch bval[0] {
case '{':
if !inQuote {
parenCnt++
inJson = true
}
case '}':
if !inQuote {
parenCnt--
}
if parenCnt < 0 {
return nil, fmt.Errorf("closing } without opening {: %s", string(jb))
}
case '"':
if inQuote {
if previous == '\\' {
break
}
inQuote = false
} else {
inQuote = true
}
case '\n', '\r', '\t', ' ':
if !inQuote {
continue
}
}
if inJson {
jb = append(jb, bval[0])
if parenCnt == 0 {
break
}
}
previous = bval[0]
}
return &jb, nil
}
// ------------------------------- JSON Reader handler via Map values -----------------------
// Default poll delay to keep Handler from spinning on an open stream
// like sitting on os.Stdin waiting for imput.
var jhandlerPollInterval = time.Duration(1e6)
// While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader().
// This avoids treating one or other as a special case and discussing the underlying stdlib logic.
// Bulk process JSON using handlers that process a Map value.
// 'rdr' is an io.Reader for the JSON (stream).
// 'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing.
// 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error.
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'.
func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
var n int
for {
m, merr := NewMapJsonReader(jsonReader)
n++
// handle error condition with errhandler
if merr != nil && merr != io.EOF {
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
if ok := errHandler(merr); !ok {
// caused reader termination
return merr
}
continue
}
// pass to maphandler
if len(m) != 0 {
if ok := mapHandler(m); !ok {
break
}
} else if merr != io.EOF {
<-time.After(jhandlerPollInterval)
}
if merr == io.EOF {
break
}
}
return nil
}
// Bulk process JSON using handlers that process a Map value and the raw JSON.
// 'rdr' is an io.Reader for the JSON (stream).
// 'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing.
// 'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error.
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'.
func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
var n int
for {
m, raw, merr := NewMapJsonReaderRaw(jsonReader)
n++
// handle error condition with errhandler
if merr != nil && merr != io.EOF {
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
if ok := errHandler(merr, raw); !ok {
// caused reader termination
return merr
}
continue
}
// pass to maphandler
if len(m) != 0 {
if ok := mapHandler(m, raw); !ok {
break
}
} else if merr != io.EOF {
<-time.After(jhandlerPollInterval)
}
if merr == io.EOF {
break
}
}
return nil
}

671
vendor/github.com/clbanning/mxj/keyvalues.go generated vendored Normal file
View file

@ -0,0 +1,671 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// keyvalues.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters.
package mxj
import (
"errors"
"fmt"
"strconv"
"strings"
)
// ----------------------------- get everything FOR a single key -------------------------
const (
minArraySize = 32
)
var defaultArraySize int = minArraySize
// Adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath().
// This can have the effect of significantly reducing memory allocation-copy functions for large data sets.
// Returns the initial buffer size.
func SetArraySize(size int) int {
if size > minArraySize {
defaultArraySize = size
} else {
defaultArraySize = minArraySize
}
return defaultArraySize
}
// Return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match.
// On error, the returned slice is 'nil'. NOTE: 'key' can be wildcard, "*".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - If the 'key' refers to a list, then "key:value" could select a list member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|".
func (mv Map) ValuesForKey(key string, subkeys ...string) ([]interface{}, error) {
m := map[string]interface{}(mv)
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
ret := make([]interface{}, 0, defaultArraySize)
var cnt int
hasKey(m, key, &ret, &cnt, subKeyMap)
return ret[:cnt], nil
}
var KeyNotExistError = errors.New("Key does not exist")
// ValueForKey is a wrapper on ValuesForKey. It returns the first member of []interface{}, if any.
// If there is no value, "nil, nil" is returned.
func (mv Map) ValueForKey(key string, subkeys ...string) (interface{}, error) {
vals, err := mv.ValuesForKey(key, subkeys...)
if err != nil {
return nil, err
}
if len(vals) == 0 {
return nil, KeyNotExistError
}
return vals[0], nil
}
// hasKey - if the map 'key' exists append it to array
// if it doesn't do nothing except scan array and map values
func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys map[string]interface{}) {
// func hasKey(iv interface{}, key string, ret *[]interface{}, subkeys map[string]interface{}) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
// see if the current value is of interest
if v, ok := vv[key]; ok {
switch v.(type) {
case map[string]interface{}:
if hasSubKeys(v, subkeys) {
*ret = append(*ret, v)
*cnt++
}
case []interface{}:
for _, av := range v.([]interface{}) {
if hasSubKeys(av, subkeys) {
*ret = append(*ret, av)
*cnt++
}
}
default:
if len(subkeys) == 0 {
*ret = append(*ret, v)
*cnt++
}
}
}
// wildcard case
if key == "*" {
for _, v := range vv {
switch v.(type) {
case map[string]interface{}:
if hasSubKeys(v, subkeys) {
*ret = append(*ret, v)
*cnt++
}
case []interface{}:
for _, av := range v.([]interface{}) {
if hasSubKeys(av, subkeys) {
*ret = append(*ret, av)
*cnt++
}
}
default:
if len(subkeys) == 0 {
*ret = append(*ret, v)
*cnt++
}
}
}
}
// scan the rest
for _, v := range vv {
hasKey(v, key, ret, cnt, subkeys)
}
case []interface{}:
for _, v := range iv.([]interface{}) {
hasKey(v, key, ret, cnt, subkeys)
}
}
}
// ----------------------- get everything for a node in the Map ---------------------------
// Allow indexed arrays in "path" specification. (Request from Abhijit Kadam - abhijitk100@gmail.com.)
// 2014.04.28 - implementation note.
// Implemented as a wrapper of (old)ValuesForPath() because we need look-ahead logic to handle expansion
// of wildcards and unindexed arrays. Embedding such logic into valuesForKeyPath() would have made the
// code much more complicated; this wrapper is straightforward, easy to debug, and doesn't add significant overhead.
// Retrieve all values for a path from the Map. If len(returned_values) == 0, then no match.
// On error, the returned array is 'nil'.
// 'path' is a dot-separated path of key values.
// - If a node in the path is '*', then everything beyond is walked.
// - 'path' can contain indexed array references, such as, "*.data[1]" and "msgs[2].data[0].field" -
// even "*[2].*[0].field".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - If the 'path' refers to a list, then "tag:value" would return member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|".
func (mv Map) ValuesForPath(path string, subkeys ...string) ([]interface{}, error) {
// If there are no array indexes in path, use legacy ValuesForPath() logic.
if strings.Index(path, "[") < 0 {
return mv.oldValuesForPath(path, subkeys...)
}
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
keys, kerr := parsePath(path)
if kerr != nil {
return nil, kerr
}
vals, verr := valuesForArray(keys, mv)
if verr != nil {
return nil, verr // Vals may be nil, but return empty array.
}
// Need to handle subkeys ... only return members of vals that satisfy conditions.
retvals := make([]interface{}, 0)
for _, v := range vals {
if hasSubKeys(v, subKeyMap) {
retvals = append(retvals, v)
}
}
return retvals, nil
}
func valuesForArray(keys []*key, m Map) ([]interface{}, error) {
var tmppath string
var haveFirst bool
var vals []interface{}
var verr error
lastkey := len(keys) - 1
for i := 0; i <= lastkey; i++ {
if !haveFirst {
tmppath = keys[i].name
haveFirst = true
} else {
tmppath += "." + keys[i].name
}
// Look-ahead: explode wildcards and unindexed arrays.
// Need to handle un-indexed list recursively:
// e.g., path is "stuff.data[0]" rather than "stuff[0].data[0]".
// Need to treat it as "stuff[0].data[0]", "stuff[1].data[0]", ...
if !keys[i].isArray && i < lastkey && keys[i+1].isArray {
// Can't pass subkeys because we may not be at literal end of path.
vv, vverr := m.oldValuesForPath(tmppath)
if vverr != nil {
return nil, vverr
}
for _, v := range vv {
// See if we can walk the value.
am, ok := v.(map[string]interface{})
if !ok {
continue
}
// Work the backend.
nvals, nvalserr := valuesForArray(keys[i+1:], Map(am))
if nvalserr != nil {
return nil, nvalserr
}
vals = append(vals, nvals...)
}
break // have recursed the whole path - return
}
if keys[i].isArray || i == lastkey {
// Don't pass subkeys because may not be at literal end of path.
vals, verr = m.oldValuesForPath(tmppath)
} else {
continue
}
if verr != nil {
return nil, verr
}
if i == lastkey && !keys[i].isArray {
break
}
// Now we're looking at an array - supposedly.
// Is index in range of vals?
if len(vals) <= keys[i].position {
vals = nil
break
}
// Return the array member of interest, if at end of path.
if i == lastkey {
vals = vals[keys[i].position:(keys[i].position + 1)]
break
}
// Extract the array member of interest.
am := vals[keys[i].position:(keys[i].position + 1)]
// must be a map[string]interface{} value so we can keep walking the path
amm, ok := am[0].(map[string]interface{})
if !ok {
vals = nil
break
}
m = Map(amm)
haveFirst = false
}
return vals, nil
}
type key struct {
name string
isArray bool
position int
}
func parsePath(s string) ([]*key, error) {
keys := strings.Split(s, ".")
ret := make([]*key, 0)
for i := 0; i < len(keys); i++ {
if keys[i] == "" {
continue
}
newkey := new(key)
if strings.Index(keys[i], "[") < 0 {
newkey.name = keys[i]
ret = append(ret, newkey)
continue
}
p := strings.Split(keys[i], "[")
newkey.name = p[0]
p = strings.Split(p[1], "]")
if p[0] == "" { // no right bracket
return nil, fmt.Errorf("no right bracket on key index: %s", keys[i])
}
// convert p[0] to a int value
pos, nerr := strconv.ParseInt(p[0], 10, 32)
if nerr != nil {
return nil, fmt.Errorf("cannot convert index to int value: %s", p[0])
}
newkey.position = int(pos)
newkey.isArray = true
ret = append(ret, newkey)
}
return ret, nil
}
// legacy ValuesForPath() - now wrapped to handle special case of indexed arrays in 'path'.
func (mv Map) oldValuesForPath(path string, subkeys ...string) ([]interface{}, error) {
m := map[string]interface{}(mv)
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
keys := strings.Split(path, ".")
if keys[len(keys)-1] == "" {
keys = keys[:len(keys)-1]
}
ivals := make([]interface{}, 0, defaultArraySize)
var cnt int
valuesForKeyPath(&ivals, &cnt, m, keys, subKeyMap)
return ivals[:cnt], nil
}
func valuesForKeyPath(ret *[]interface{}, cnt *int, m interface{}, keys []string, subkeys map[string]interface{}) {
lenKeys := len(keys)
// load 'm' values into 'ret'
// expand any lists
if lenKeys == 0 {
switch m.(type) {
case map[string]interface{}:
if subkeys != nil {
if ok := hasSubKeys(m, subkeys); !ok {
return
}
}
*ret = append(*ret, m)
*cnt++
case []interface{}:
for i, v := range m.([]interface{}) {
if subkeys != nil {
if ok := hasSubKeys(v, subkeys); !ok {
continue // only load list members with subkeys
}
}
*ret = append(*ret, (m.([]interface{}))[i])
*cnt++
}
default:
if subkeys != nil {
return // must be map[string]interface{} if there are subkeys
}
*ret = append(*ret, m)
*cnt++
}
return
}
// key of interest
key := keys[0]
switch key {
case "*": // wildcard - scan all values
switch m.(type) {
case map[string]interface{}:
for _, v := range m.(map[string]interface{}) {
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
case []interface{}:
for _, v := range m.([]interface{}) {
switch v.(type) {
// flatten out a list of maps - keys are processed
case map[string]interface{}:
for _, vv := range v.(map[string]interface{}) {
// valuesForKeyPath(ret, vv, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys)
}
default:
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
}
}
default: // key - must be map[string]interface{}
switch m.(type) {
case map[string]interface{}:
if v, ok := m.(map[string]interface{})[key]; ok {
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
case []interface{}: // may be buried in list
for _, v := range m.([]interface{}) {
switch v.(type) {
case map[string]interface{}:
if vv, ok := v.(map[string]interface{})[key]; ok {
// valuesForKeyPath(ret, vv, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys)
}
}
}
}
}
}
// hasSubKeys() - interface{} equality works for string, float64, bool
// 'v' must be a map[string]interface{} value to have subkeys
// 'a' can have k:v pairs with v.(string) == "*", which is treated like a wildcard.
func hasSubKeys(v interface{}, subkeys map[string]interface{}) bool {
if len(subkeys) == 0 {
return true
}
switch v.(type) {
case map[string]interface{}:
// do all subKey name:value pairs match?
mv := v.(map[string]interface{})
for skey, sval := range subkeys {
isNotKey := false
if skey[:1] == "!" { // a NOT-key
skey = skey[1:]
isNotKey = true
}
vv, ok := mv[skey]
if !ok { // key doesn't exist
if isNotKey { // key not there, but that's what we want
if kv, ok := sval.(string); ok && kv == "*" {
continue
}
}
return false
}
// wildcard check
if kv, ok := sval.(string); ok && kv == "*" {
if isNotKey { // key is there, and we don't want it
return false
}
continue
}
switch sval.(type) {
case string:
if s, ok := vv.(string); ok && s == sval.(string) {
if isNotKey {
return false
}
continue
}
case bool:
if b, ok := vv.(bool); ok && b == sval.(bool) {
if isNotKey {
return false
}
continue
}
case float64:
if f, ok := vv.(float64); ok && f == sval.(float64) {
if isNotKey {
return false
}
continue
}
}
// key there but didn't match subkey value
if isNotKey { // that's what we want
continue
}
return false
}
// all subkeys matched
return true
}
// not a map[string]interface{} value, can't have subkeys
return false
}
// Generate map of key:value entries as map[string]string.
// 'kv' arguments are "name:value" pairs: attribute keys are designated with prepended hyphen, '-'.
// If len(kv) == 0, the return is (nil, nil).
func getSubKeyMap(kv ...string) (map[string]interface{}, error) {
if len(kv) == 0 {
return nil, nil
}
m := make(map[string]interface{}, 0)
for _, v := range kv {
vv := strings.Split(v, fieldSep)
switch len(vv) {
case 2:
m[vv[0]] = interface{}(vv[1])
case 3:
switch vv[2] {
case "string", "char", "text":
m[vv[0]] = interface{}(vv[1])
case "bool", "boolean":
// ParseBool treats "1"==true & "0"==false
b, err := strconv.ParseBool(vv[1])
if err != nil {
return nil, fmt.Errorf("can't convert subkey value to bool: %s", vv[1])
}
m[vv[0]] = interface{}(b)
case "float", "float64", "num", "number", "numeric":
f, err := strconv.ParseFloat(vv[1], 64)
if err != nil {
return nil, fmt.Errorf("can't convert subkey value to float: %s", vv[1])
}
m[vv[0]] = interface{}(f)
default:
return nil, fmt.Errorf("unknown subkey conversion spec: %s", v)
}
default:
return nil, fmt.Errorf("unknown subkey spec: %s", v)
}
}
return m, nil
}
// ------------------------------- END of valuesFor ... ----------------------------
// ----------------------- locate where a key value is in the tree -------------------
//----------------------------- find all paths to a key --------------------------------
// Get all paths through Map, 'mv', (in dot-notation) that terminate with the specified key.
// Results can be used with ValuesForPath.
func (mv Map) PathsForKey(key string) []string {
m := map[string]interface{}(mv)
breadbasket := make(map[string]bool, 0)
breadcrumbs := ""
hasKeyPath(breadcrumbs, m, key, breadbasket)
if len(breadbasket) == 0 {
return nil
}
// unpack map keys to return
res := make([]string, len(breadbasket))
var i int
for k := range breadbasket {
res[i] = k
i++
}
return res
}
// Extract the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'..
// Paths are strings using dot-notation.
func (mv Map) PathForKeyShortest(key string) string {
paths := mv.PathsForKey(key)
lp := len(paths)
if lp == 0 {
return ""
}
if lp == 1 {
return paths[0]
}
shortest := paths[0]
shortestLen := len(strings.Split(shortest, "."))
for i := 1; i < len(paths); i++ {
vlen := len(strings.Split(paths[i], "."))
if vlen < shortestLen {
shortest = paths[i]
shortestLen = vlen
}
}
return shortest
}
// hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth
// This is really just a breadcrumber that saves all trails that hit the prescribed 'key'.
func hasKeyPath(crumbs string, iv interface{}, key string, basket map[string]bool) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
if _, ok := vv[key]; ok {
// create a new breadcrumb, intialized with the one we have
var nbc string
if crumbs == "" {
nbc = key
} else {
nbc = crumbs + "." + key
}
basket[nbc] = true
}
// walk on down the path, key could occur again at deeper node
for k, v := range vv {
// create a new breadcrumb, intialized with the one we have
var nbc string
if crumbs == "" {
nbc = k
} else {
nbc = crumbs + "." + k
}
hasKeyPath(nbc, v, key, basket)
}
case []interface{}:
// crumb-trail doesn't change, pass it on
for _, v := range iv.([]interface{}) {
hasKeyPath(crumbs, v, key, basket)
}
}
}
var PathNotExistError = errors.New("Path does not exist")
// ValueForPath wrap ValuesFor Path and returns the first value returned.
// If no value is found it returns 'nil' and PathNotExistError.
func (mv Map) ValueForPath(path string) (interface{}, error) {
vals, err := mv.ValuesForPath(path)
if err != nil {
return nil, err
}
if len(vals) == 0 {
return nil, PathNotExistError
}
return vals[0], nil
}
// Returns the first found value for the path as a string.
func (mv Map) ValueForPathString(path string) (string, error) {
vals, err := mv.ValuesForPath(path)
if err != nil {
return "", err
}
if len(vals) == 0 {
return "", errors.New("ValueForPath: path not found")
}
val := vals[0]
switch str := val.(type) {
case string:
return str, nil
default:
return "", fmt.Errorf("ValueForPath: unsupported type: %T", str)
}
}
// Returns the first found value for the path as a string.
// If the path is not found then it returns an empty string.
func (mv Map) ValueOrEmptyForPathString(path string) string {
str, _ := mv.ValueForPathString(path)
return str
}

112
vendor/github.com/clbanning/mxj/leafnode.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
package mxj
// leafnode.go - return leaf nodes with paths and values for the Map
// inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw
import (
"strconv"
"strings"
)
const (
NoAttributes = true // suppress LeafNode values that are attributes
)
// LeafNode - a terminal path value in a Map.
// For XML Map values it represents an attribute or simple element value - of type
// string unless Map was created using Cast flag. For JSON Map values it represents
// a string, numeric, boolean, or null value.
type LeafNode struct {
Path string // a dot-notation representation of the path with array subscripting
Value interface{} // the value at the path termination
}
// LeafNodes - returns an array of all LeafNode values for the Map.
// The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-')
// as well as the "#text" key for the associated simple element value.
//
// PrependAttrWithHypen(false) will result in attributes having .attr-name as
// terminal node in 'path' while the path for the element value, itself, will be
// the base path w/o "#text".
//
// LeafUseDotNotation(true) causes list members to be identified using ".N" syntax
// rather than "[N]" syntax.
func (mv Map) LeafNodes(no_attr ...bool) []LeafNode {
var a bool
if len(no_attr) == 1 {
a = no_attr[0]
}
l := make([]LeafNode, 0)
getLeafNodes("", "", map[string]interface{}(mv), &l, a)
return l
}
func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) {
// if stripping attributes, then also strip "#text" key
if !noattr || node != "#text" {
if path != "" && node[:1] != "[" {
path += "."
}
path += node
}
switch mv.(type) {
case map[string]interface{}:
for k, v := range mv.(map[string]interface{}) {
// if noattr && k[:1] == "-" {
if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 {
continue
}
getLeafNodes(path, k, v, l, noattr)
}
case []interface{}:
for i, v := range mv.([]interface{}) {
if useDotNotation {
getLeafNodes(path, strconv.Itoa(i), v, l, noattr)
} else {
getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr)
}
}
default:
// can't walk any further, so create leaf
n := LeafNode{path, mv}
*l = append(*l, n)
}
}
// LeafPaths - all paths that terminate in LeafNode values.
func (mv Map) LeafPaths(no_attr ...bool) []string {
ln := mv.LeafNodes()
ss := make([]string, len(ln))
for i := 0; i < len(ln); i++ {
ss[i] = ln[i].Path
}
return ss
}
// LeafValues - all terminal values in the Map.
func (mv Map) LeafValues(no_attr ...bool) []interface{} {
ln := mv.LeafNodes()
vv := make([]interface{}, len(ln))
for i := 0; i < len(ln); i++ {
vv[i] = ln[i].Value
}
return vv
}
// ====================== utilities ======================
// https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I
var useDotNotation bool
// LeafUseDotNotation sets a flag that list members in LeafNode paths
// should be identified using ".N" syntax rather than the default "[N]"
// syntax. Calling LeafUseDotNotation with no arguments toggles the
// flag on/off; otherwise, the argument sets the flag value 'true'/'false'.
func LeafUseDotNotation(b ...bool) {
if len(b) == 0 {
useDotNotation = !useDotNotation
return
}
useDotNotation = b[0]
}

86
vendor/github.com/clbanning/mxj/misc.go generated vendored Normal file
View file

@ -0,0 +1,86 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// misc.go - mimic functions (+others) called out in:
// https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ
// Primarily these methods let you retrive XML structure information.
package mxj
import (
"fmt"
"sort"
"strings"
)
// Return the root element of the Map. If there is not a single key in Map,
// then an error is returned.
func (mv Map) Root() (string, error) {
mm := map[string]interface{}(mv)
if len(mm) != 1 {
return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm))
}
for k, _ := range mm {
return k, nil
}
return "", nil
}
// If the path is an element with sub-elements, return a list of the sub-element
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are prefixed with
// '-', a hyphen, are considered attributes; see m.Attributes(path).
func (mv Map) Elements(path string) ([]string, error) {
e, err := mv.ValueForPath(path)
if err != nil {
return nil, err
}
switch e.(type) {
case map[string]interface{}:
ee := e.(map[string]interface{})
elems := make([]string, len(ee))
var i int
for k, _ := range ee {
if len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 {
continue // skip attributes
}
elems[i] = k
i++
}
elems = elems[:i]
// alphabetic sort keeps things tidy
sort.Strings(elems)
return elems, nil
}
return nil, fmt.Errorf("no elements for path: %s", path)
}
// If the path is an element with attributes, return a list of the attribute
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are not prefixed with
// '-', a hyphen, are not treated as attributes; see m.Elements(path). Also, if the
// attribute prefix is "" - SetAttrPrefix("") or PrependAttrWithHyphen(false) - then
// there are no identifiable attributes.
func (mv Map) Attributes(path string) ([]string, error) {
a, err := mv.ValueForPath(path)
if err != nil {
return nil, err
}
switch a.(type) {
case map[string]interface{}:
aa := a.(map[string]interface{})
attrs := make([]string, len(aa))
var i int
for k, _ := range aa {
if len(attrPrefix) == 0 || strings.Index(k, attrPrefix) != 0 {
continue // skip non-attributes
}
attrs[i] = k[len(attrPrefix):]
i++
}
attrs = attrs[:i]
// alphabetic sort keeps things tidy
sort.Strings(attrs)
return attrs, nil
}
return nil, fmt.Errorf("no attributes for path: %s", path)
}

128
vendor/github.com/clbanning/mxj/mxj.go generated vendored Normal file
View file

@ -0,0 +1,128 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"fmt"
"sort"
)
const (
Cast = true // for clarity - e.g., mxj.NewMapXml(doc, mxj.Cast)
SafeEncoding = true // ditto - e.g., mv.Json(mxj.SafeEncoding)
)
type Map map[string]interface{}
// Allocate a Map.
func New() Map {
m := make(map[string]interface{}, 0)
return m
}
// Cast a Map to map[string]interface{}
func (mv Map) Old() map[string]interface{} {
return mv
}
// Return a copy of mv as a newly allocated Map. If the Map only contains string,
// numeric, map[string]interface{}, and []interface{} values, then it can be thought
// of as a "deep copy." Copying a structure (or structure reference) value is subject
// to the noted restrictions.
// NOTE: If 'mv' includes structure values with, possibly, JSON encoding tags
// then only public fields of the structure are in the new Map - and with
// keys that conform to any encoding tag instructions. The structure itself will
// be represented as a map[string]interface{} value.
func (mv Map) Copy() (Map, error) {
// this is the poor-man's deep copy
// not efficient, but it works
j, jerr := mv.Json()
// must handle, we don't know how mv got built
if jerr != nil {
return nil, jerr
}
return NewMapJson(j)
}
// --------------- StringIndent ... from x2j.WriteMap -------------
// Pretty print a Map.
func (mv Map) StringIndent(offset ...int) string {
return writeMap(map[string]interface{}(mv), true, true, offset...)
}
// Pretty print a Map without the value type information - just key:value entries.
func (mv Map) StringIndentNoTypeInfo(offset ...int) string {
return writeMap(map[string]interface{}(mv), false, true, offset...)
}
// writeMap - dumps the map[string]interface{} for examination.
// 'typeInfo' causes value type to be printed.
// 'offset' is initial indentation count; typically: Write(m).
func writeMap(m interface{}, typeInfo, root bool, offset ...int) string {
var indent int
if len(offset) == 1 {
indent = offset[0]
}
var s string
switch m.(type) {
case []interface{}:
if typeInfo {
s += "[[]interface{}]"
}
for _, v := range m.([]interface{}) {
s += "\n"
for i := 0; i < indent; i++ {
s += " "
}
s += writeMap(v, typeInfo, false, indent+1)
}
case map[string]interface{}:
list := make([][2]string, len(m.(map[string]interface{})))
var n int
for k, v := range m.(map[string]interface{}) {
list[n][0] = k
list[n][1] = writeMap(v, typeInfo, false, indent+1)
n++
}
sort.Sort(mapList(list))
for _, v := range list {
if root {
root = false
} else {
s += "\n"
}
for i := 0; i < indent; i++ {
s += " "
}
s += v[0] + " : " + v[1]
}
default:
if typeInfo {
s += fmt.Sprintf("[%T] %+v", m, m)
} else {
s += fmt.Sprintf("%+v", m)
}
}
return s
}
// ======================== utility ===============
type mapList [][2]string
func (ml mapList) Len() int {
return len(ml)
}
func (ml mapList) Swap(i, j int) {
ml[i], ml[j] = ml[j], ml[i]
}
func (ml mapList) Less(i, j int) bool {
return ml[i][0] <= ml[j][0]
}

184
vendor/github.com/clbanning/mxj/newmap.go generated vendored Normal file
View file

@ -0,0 +1,184 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2014, 2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings
// keys can use dot-notation, keyOld can use wildcard, '*'
//
// Computational strategy -
// Using the key path - []string - traverse a new map[string]interface{} and
// insert the oldVal as the newVal when we arrive at the end of the path.
// If the type at the end is nil, then that is newVal
// If the type at the end is a singleton (string, float64, bool) an array is created.
// If the type at the end is an array, newVal is just appended.
// If the type at the end is a map, it is inserted if possible or the map value
// is converted into an array if necessary.
package mxj
import (
"errors"
"strings"
)
// (Map)NewMap - create a new Map from data in the current Map.
// 'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey'
// should be the value for 'newKey' in the returned Map.
// - 'oldKey' supports dot-notation as described for (Map)ValuesForPath()
// - 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays
// - "oldKey" is shorthand for the keypair value "oldKey:oldKey"
// - "oldKey:" and ":newKey" are invalid keypair values
// - if 'oldKey' does not exist in the current Map, it is not written to the new Map.
// "null" is not supported unless it is the current Map.
// - see newmap_test.go for several syntax examples
// - mv.NewMap() == mxj.New()
//
// NOTE: "examples/partial.go" shows how to create arbitrary sub-docs of an XML doc.
func (mv Map) NewMap(keypairs ...string) (Map, error) {
n := make(map[string]interface{}, 0)
if len(keypairs) == 0 {
return n, nil
}
// loop through the pairs
var oldKey, newKey string
var path []string
for _, v := range keypairs {
if len(v) == 0 {
continue // just skip over empty keypair arguments
}
// initialize oldKey, newKey and check
vv := strings.Split(v, ":")
if len(vv) > 2 {
return n, errors.New("oldKey:newKey keypair value not valid - " + v)
}
if len(vv) == 1 {
oldKey, newKey = vv[0], vv[0]
} else {
oldKey, newKey = vv[0], vv[1]
}
strings.TrimSpace(oldKey)
strings.TrimSpace(newKey)
if i := strings.Index(newKey, "*"); i > -1 {
return n, errors.New("newKey value cannot contain wildcard character - " + v)
}
if i := strings.Index(newKey, "["); i > -1 {
return n, errors.New("newKey value cannot contain indexed arrays - " + v)
}
if oldKey == "" || newKey == "" {
return n, errors.New("oldKey or newKey is not specified - " + v)
}
// get oldKey value
oldVal, err := mv.ValuesForPath(oldKey)
if err != nil {
return n, err
}
if len(oldVal) == 0 {
continue // oldKey has no value, may not exist in mv
}
// break down path
path = strings.Split(newKey, ".")
if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
path = path[:len(path)-1]
}
addNewVal(&n, path, oldVal)
}
return n, nil
}
// navigate 'n' to end of path and add val
func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
// newVal - either singleton or array
var newVal interface{}
if len(val) == 1 {
newVal = val[0] // is type interface{}
} else {
newVal = interface{}(val)
}
// walk to the position of interest, create it if necessary
m := (*n) // initialize map walker
var k string // key for m
lp := len(path) - 1 // when to stop looking
for i := 0; i < len(path); i++ {
k = path[i]
if i == lp {
break
}
var nm map[string]interface{} // holds position of next-map
switch m[k].(type) {
case nil: // need a map for next node in path, so go there
nm = make(map[string]interface{}, 0)
m[k] = interface{}(nm)
m = m[k].(map[string]interface{})
case map[string]interface{}:
// OK - got somewhere to walk to, go there
m = m[k].(map[string]interface{})
case []interface{}:
// add a map and nm points to new map unless there's already
// a map in the array, then nm points there
// The placement of the next value in the array is dependent
// on the sequence of members - could land on a map or a nil
// value first. TODO: how to test this.
a := make([]interface{}, 0)
var foundmap bool
for _, vv := range m[k].([]interface{}) {
switch vv.(type) {
case nil: // doesn't appear that this occurs, need a test case
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
foundmap = true
case map[string]interface{}:
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = vv.(map[string]interface{})
a = append(a, vv)
foundmap = true
default:
a = append(a, vv)
}
}
// no map found in array
if !foundmap {
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
}
m[k] = interface{}(a) // must insert in map
m = nm
default: // it's a string, float, bool, etc.
aa := make([]interface{}, 0)
nm = make(map[string]interface{}, 0)
aa = append(aa, m[k], nm)
m[k] = interface{}(aa)
m = nm
}
}
// value is nil, array or a singleton of some kind
// initially m.(type) == map[string]interface{}
v := m[k]
switch v.(type) {
case nil: // initialized
m[k] = newVal
case []interface{}:
a := m[k].([]interface{})
a = append(a, newVal)
m[k] = interface{}(a)
default: // v exists:string, float64, bool, map[string]interface, etc.
a := make([]interface{}, 0)
a = append(a, v, newVal)
m[k] = interface{}(a)
}
}

179
vendor/github.com/clbanning/mxj/readme.md generated vendored Normal file
View file

@ -0,0 +1,179 @@
<h2>mxj - to/from maps, XML and JSON</h2>
Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/modify values from maps by key or key-path, including wildcards.
mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use mxj/x2j and mxj/j2x packages.
<h4>Related Packages</h4>
https://github.com/clbanning/checkxml provides functions for validating XML data.
<h4>Refactor Decoder - 2015.11.15</h4>
For over a year I've wanted to refactor the XML-to-map[string]interface{} decoder to make it more performant. I recently took the time to do that, since we were using github.com/clbanning/mxj in a production system that could be deployed on a Raspberry Pi. Now the decoder is comparable to the stdlib JSON-to-map[string]interface{} decoder in terms of its additional processing overhead relative to decoding to a structure value. As shown by:
BenchmarkNewMapXml-4 100000 18043 ns/op
BenchmarkNewStructXml-4 100000 14892 ns/op
BenchmarkNewMapJson-4 300000 4633 ns/op
BenchmarkNewStructJson-4 300000 3427 ns/op
BenchmarkNewMapXmlBooks-4 20000 82850 ns/op
BenchmarkNewStructXmlBooks-4 20000 67822 ns/op
BenchmarkNewMapJsonBooks-4 100000 17222 ns/op
BenchmarkNewStructJsonBooks-4 100000 15309 ns/op
<h4>Notices</h4>
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.
2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing.
2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods.
2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag().
2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc.
2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix().
2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable.
2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars().
2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf".
To cast them to float64, first set flag with CastNanInf(true).
2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure.
2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization.
2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM).
2015.12.02: XML decoding/encoding that preserves original structure of document. See NewMapXmlSeq()
and mv.XmlSeq() / mv.XmlSeqIndent().
2015-05-20: New: mv.StringIndentNoTypeInfo().
Also, alphabetically sort map[string]interface{} values by key to prettify output for mv.Xml(),
mv.XmlIndent(), mv.StringIndent(), mv.StringIndentNoTypeInfo().
2014-11-09: IncludeTagSeqNum() adds "_seq" key with XML doc positional information.
(NOTE: PreserveXmlList() is similar and will be here soon.)
2014-09-18: inspired by NYTimes fork, added PrependAttrWithHyphen() to allow stripping hyphen from attribute tag.
2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML.
2014-04-28: ValuesForPath() and NewMap() now accept path with indexed array references.
<h4>Basic Unmarshal XML to map[string]interface{}</h4>
<pre>type Map map[string]interface{}</pre>
Create a `Map` value, 'mv', from any `map[string]interface{}` value, 'v':
<pre>mv := Map(v)</pre>
Unmarshal / marshal XML as a `Map` value, 'mv':
<pre>mv, err := NewMapXml(xmlValue) // unmarshal
xmlValue, err := mv.Xml() // marshal</pre>
Unmarshal XML from an `io.Reader` as a `Map` value, 'mv':
<pre>mv, err := NewMapXmlReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
mv, raw, err := NewMapXmlReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded</pre>
Marshal `Map` value, 'mv', to an XML Writer (`io.Writer`):
<pre>err := mv.XmlWriter(xmlWriter)
raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter</pre>
Also, for prettified output:
<pre>xmlValue, err := mv.XmlIndent(prefix, indent, ...)
err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...)
raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...)</pre>
Bulk process XML with error handling (note: handlers must return a boolean value):
<pre>err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error))
err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte))</pre>
Converting XML to JSON: see Examples for `NewMapXml` and `HandleXmlReader`.
There are comparable functions and methods for JSON processing.
Arbitrary structure values can be decoded to / encoded from `Map` values:
<pre>mv, err := NewMapStruct(structVal)
err := mv.Struct(structPointer)</pre>
<h4>Extract / modify Map values</h4>
To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON
or structure to a `Map` value, 'mv', or cast a `map[string]interface{}` value to a `Map` value, 'mv', then:
<pre>paths := mv.PathsForKey(key)
path := mv.PathForKeyShortest(key)
values, err := mv.ValuesForKey(key, subkeys)
values, err := mv.ValuesForPath(path, subkeys)
count, err := mv.UpdateValuesForPath(newVal, path, subkeys)</pre>
Get everything at once, irrespective of path depth:
<pre>leafnodes := mv.LeafNodes()
leafvalues := mv.LeafValues()</pre>
A new `Map` with whatever keys are desired can be created from the current `Map` and then encoded in XML
or JSON. (Note: keys can use dot-notation.)
<pre>newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
newMap, err := mv.NewMap("oldKey1", "oldKey3", "oldKey5") // a subset of 'mv'; see "examples/partial.go"
newXml, err := newMap.Xml() // for example
newJson, err := newMap.Json() // ditto</pre>
<h4>Usage</h4>
The package is fairly well [self-documented with examples](http://godoc.org/github.com/clbanning/mxj).
Also, the subdirectory "examples" contains a wide range of examples, several taken from golang-nuts discussions.
<h4>XML parsing conventions</h4>
Using NewMapXml()
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`,
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or
`SetAttrPrefix()`.)
- If the element is a simple element and has attributes, the element value
is given the key `#text` for its `map[string]interface{}` representation. (See
the 'atomFeedString.xml' test data, below.)
- XML comments, directives, and process instructions are ignored.
- If CoerceKeysToLower() has been called, then the resultant keys will be lower case.
Using NewMapXmlSeq()
- Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values
where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the
value for `<attr_label>`.
- All elements, except for the root, have a "#seq" key.
- Comments, directives, and process instructions are unmarshalled into the Map using the
keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more
specifics.)
- Name space syntax is preserved:
- `<ns:key>something</ns.key>` parses to `map["ns:key"]interface{}{"something"}`
- `xmlns:ns="http://myns.com/ns"` parses to `map["xmlns:ns"]interface{}{"http://myns.com/ns"}`
Both
- By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them
to be cast, set a flag to cast them using CastNanInf(true).
<h4>XML encoding conventions</h4>
- 'nil' `Map` values, which may represent 'null' JSON values, are encoded as `<tag/>`.
NOTE: the operation is not symmetric as `<tag/>` elements are decoded as `tag:""` `Map` values,
which, then, encode in JSON as `"tag":""` values.
- ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go
randomizes the walk through map[string]interface{} values.) If you plan to re-encode the
Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and
mv.XmlSeq() - these try to preserve the element sequencing but with added complexity when
working with the Map representation.
<h4>Running "go test"</h4>
Because there are no guarantees on the sequence map elements are retrieved, the tests have been
written for visual verification in most cases. One advantage is that you can easily use the
output from running "go test" as examples of calling the various functions and methods.
<h4>Motivation</h4>
I make extensive use of JSON for messaging and typically unmarshal the messages into
`map[string]interface{}` values. This is easily done using `json.Unmarshal` from the
standard Go libraries. Unfortunately, many legacy solutions use structured
XML messages; in those environments the applications would have to be refactored to
interoperate with my components.
The better solution is to just provide an alternative HTTP handler that receives
XML messages and parses it into a `map[string]interface{}` value and then reuse
all the JSON-based code. The Go `xml.Unmarshal()` function does not provide the same
option of unmarshaling XML messages into `map[string]interface{}` values. So I wrote
a couple of small functions to fill this gap and released them as the x2j package.
Over the next year and a half additional features were added, and the companion j2x
package was released to address XML encoding of arbitrary JSON and `map[string]interface{}`
values. As part of a refactoring of our production system and looking at how we had been
using the x2j and j2x packages we found that we rarely performed direct XML-to-JSON or
JSON-to_XML conversion and that working with the XML or JSON as `map[string]interface{}`
values was the primary value. Thus, everything was refactored into the mxj package.

37
vendor/github.com/clbanning/mxj/remove.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
package mxj
import "strings"
// Removes the path.
func (mv Map) Remove(path string) error {
m := map[string]interface{}(mv)
return remove(m, path)
}
func remove(m interface{}, path string) error {
val, err := prevValueByPath(m, path)
if err != nil {
return err
}
lastKey := lastKey(path)
delete(val, lastKey)
return nil
}
// returns the last key of the path.
// lastKey("a.b.c") would had returned "c"
func lastKey(path string) string {
keys := strings.Split(path, ".")
key := keys[len(keys)-1]
return key
}
// returns the path without the last key
// parentPath("a.b.c") whould had returned "a.b"
func parentPath(path string) string {
keys := strings.Split(path, ".")
parentPath := strings.Join(keys[0:len(keys)-1], ".")
return parentPath
}

54
vendor/github.com/clbanning/mxj/rename.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
package mxj
import (
"errors"
"strings"
)
// RenameKey renames a key in a Map.
// It works only for nested maps. It doesn't work for cases when it buried in a list.
func (mv Map) RenameKey(path string, newName string) error {
if !mv.Exists(path) {
return errors.New("RenameKey: path not found: " + path)
}
if mv.Exists(parentPath(path) + "." + newName) {
return errors.New("RenameKey: key already exists: " + newName)
}
m := map[string]interface{}(mv)
return renameKey(m, path, newName)
}
func renameKey(m interface{}, path string, newName string) error {
val, err := prevValueByPath(m, path)
if err != nil {
return err
}
oldName := lastKey(path)
val[newName] = val[oldName]
delete(val, oldName)
return nil
}
// returns a value which contains a last key in the path
// For example: prevValueByPath("a.b.c", {a{b{c: 3}}}) returns {c: 3}
func prevValueByPath(m interface{}, path string) (map[string]interface{}, error) {
keys := strings.Split(path, ".")
switch mValue := m.(type) {
case map[string]interface{}:
for key, value := range mValue {
if key == keys[0] {
if len(keys) == 1 {
return mValue, nil
} else {
// keep looking for the full path to the key
return prevValueByPath(value, strings.Join(keys[1:], "."))
}
}
}
}
return nil, errors.New("prevValueByPath: didn't find path " + path)
}

26
vendor/github.com/clbanning/mxj/set.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
package mxj
import (
"strings"
)
// Sets the value for the path
func (mv Map) SetValueForPath(value interface{}, path string) error {
pathAry := strings.Split(path, ".")
parentPathAry := pathAry[0 : len(pathAry)-1]
parentPath := strings.Join(parentPathAry, ".")
val, err := mv.ValueForPath(parentPath)
if err != nil {
return err
}
if val == nil {
return nil // we just ignore the request if there's no val
}
key := pathAry[len(pathAry)-1]
cVal := val.(map[string]interface{})
cVal[key] = value
return nil
}

20
vendor/github.com/clbanning/mxj/setfieldsep.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
package mxj
// Per: https://github.com/clbanning/mxj/issues/37#issuecomment-278651862
var fieldSep string = ":"
// SetFieldSeparator changes the default field separator, ":", for the
// newVal argument in mv.UpdateValuesForPath and the optional 'subkey' arguments
// in mv.ValuesForKey and mv.ValuesForPath.
//
// E.g., if the newVal value is "http://blah/blah", setting the field separator
// to "|" will allow the newVal specification, "<key>|http://blah/blah" to parse
// properly. If called with no argument or an empty string value, the field
// separator is set to the default, ":".
func SetFieldSeparator(s ...string) {
if len(s) == 0 || s[0] == "" {
fieldSep = ":" // the default
return
}
fieldSep = s[0]
}

29
vendor/github.com/clbanning/mxj/songtext.xml generated vendored Normal file
View file

@ -0,0 +1,29 @@
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
<verse name="verse 2" no="2">
<line no="1">Oh, Henry was the end of the story</line>
<line no="2">Then everything went wrong</line>
<line no="3">And we'll return it to its former glory</line>
<line no="4">But it just takes so long</line>
</verse>
</verses>
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>

30
vendor/github.com/clbanning/mxj/strict.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// strict.go actually addresses setting xml.Decoder attribute
// values. This'll let you parse non-standard XML.
package mxj
import (
"encoding/xml"
)
// CustomDecoder can be used to specify xml.Decoder attribute
// values, e.g., Strict:false, to be used. By default CustomDecoder
// is nil. If CustomeDecoder != nil, then mxj.XmlCharsetReader variable is
// ignored and must be set as part of the CustomDecoder value, if needed.
// Usage:
// mxj.CustomDecoder = &xml.Decoder{Strict:false}
var CustomDecoder *xml.Decoder
// useCustomDecoder copy over public attributes from customDecoder
func useCustomDecoder(d *xml.Decoder) {
d.Strict = CustomDecoder.Strict
d.AutoClose = CustomDecoder.AutoClose
d.Entity = CustomDecoder.Entity
d.CharsetReader = CustomDecoder.CharsetReader
d.DefaultSpace = CustomDecoder.DefaultSpace
}

54
vendor/github.com/clbanning/mxj/struct.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2012-2017 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"encoding/json"
"errors"
"reflect"
// "github.com/fatih/structs"
)
// Create a new Map value from a structure. Error returned if argument is not a structure.
// Only public structure fields are decoded in the Map value. See github.com/fatih/structs#Map
// for handling of "structs" tags.
// DEPRECATED - import github.com/fatih/structs and cast result of structs.Map to mxj.Map.
// import "github.com/fatih/structs"
// ...
// sm, err := structs.Map(<some struct>)
// if err != nil {
// // handle error
// }
// m := mxj.Map(sm)
// Alernatively uncomment the old source and import in struct.go.
func NewMapStruct(structVal interface{}) (Map, error) {
return nil, errors.New("deprecated - see package documentation")
/*
if !structs.IsStruct(structVal) {
return nil, errors.New("NewMapStruct() error: argument is not type Struct")
}
return structs.Map(structVal), nil
*/
}
// Marshal a map[string]interface{} into a structure referenced by 'structPtr'. Error returned
// if argument is not a pointer or if json.Unmarshal returns an error.
// json.Unmarshal structure encoding rules are followed to encode public structure fields.
func (mv Map) Struct(structPtr interface{}) error {
// should check that we're getting a pointer.
if reflect.ValueOf(structPtr).Kind() != reflect.Ptr {
return errors.New("mv.Struct() error: argument is not type Ptr")
}
m := map[string]interface{}(mv)
j, err := json.Marshal(m)
if err != nil {
return err
}
return json.Unmarshal(j, structPtr)
}

256
vendor/github.com/clbanning/mxj/updatevalues.go generated vendored Normal file
View file

@ -0,0 +1,256 @@
// Copyright 2012-2014, 2017 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// updatevalues.go - modify a value based on path and possibly sub-keys
// TODO(clb): handle simple elements with attributes and NewMapXmlSeq Map values.
package mxj
import (
"fmt"
"strconv"
"strings"
)
// Update value based on path and possible sub-key values.
// A count of the number of values changed and any error are returned.
// If the count == 0, then no path (and subkeys) matched.
// 'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified
// or a string value "key:value[:type]" where type is "bool" or "num" to cast the value.
// 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
// NOTE: 'path' spec does not currently support indexed array references.
// 'subkeys' are "key:value[:type]" entries that must match for path node
// The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
//
// NOTES:
// 1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value.
// 2. Values in Maps created using NewMapXmlSeq are map[string]interface{} values with a "#text" key.
// 3. If values in 'newVal' or 'subkeys' args contain ":", use SetFieldSeparator to an unused symbol,
// perhaps "|".
func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) {
m := map[string]interface{}(mv)
// extract the subkeys
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return 0, err
}
}
// extract key and value from newVal
var key string
var val interface{}
switch newVal.(type) {
case map[string]interface{}, Map:
switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec)
case Map:
newVal = newVal.(Map).Old()
}
if len(newVal.(map[string]interface{})) != 1 {
return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal)
}
for key, val = range newVal.(map[string]interface{}) {
}
case string: // split it as a key:value pair
ss := strings.Split(newVal.(string), fieldSep)
n := len(ss)
if n < 2 || n > 3 {
return 0, fmt.Errorf("unknown newVal spec - %+v", newVal)
}
key = ss[0]
if n == 2 {
val = interface{}(ss[1])
} else if n == 3 {
switch ss[2] {
case "bool", "boolean":
nv, err := strconv.ParseBool(ss[1])
if err != nil {
return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal)
}
val = interface{}(nv)
case "num", "numeric", "float", "int":
nv, err := strconv.ParseFloat(ss[1], 64)
if err != nil {
return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal)
}
val = interface{}(nv)
default:
return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal)
}
}
default:
return 0, fmt.Errorf("invalid newVal type - %+v", newVal)
}
// parse path
keys := strings.Split(path, ".")
var count int
updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count)
return count, nil
}
// navigate the path
func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) {
// ----- at end node: looking at possible node to get 'key' ----
if len(keys) == 1 {
updateValue(key, value, m, keys[0], subkeys, cnt)
return
}
// ----- here we are navigating the path thru the penultimate node --------
// key of interest is keys[0] - the next in the path
switch keys[0] {
case "*": // wildcard - scan all values
switch m.(type) {
case map[string]interface{}:
for _, v := range m.(map[string]interface{}) {
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
case []interface{}:
for _, v := range m.([]interface{}) {
switch v.(type) {
// flatten out a list of maps - keys are processed
case map[string]interface{}:
for _, vv := range v.(map[string]interface{}) {
updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
}
default:
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
}
}
default: // key - must be map[string]interface{}
switch m.(type) {
case map[string]interface{}:
if v, ok := m.(map[string]interface{})[keys[0]]; ok {
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
case []interface{}: // may be buried in list
for _, v := range m.([]interface{}) {
switch v.(type) {
case map[string]interface{}:
if vv, ok := v.(map[string]interface{})[keys[0]]; ok {
updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
}
}
}
}
}
}
// change value if key and subkeys are present
func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) {
// there are two possible options for the value of 'keys0': map[string]interface, []interface{}
// and 'key' is a key in the map or is a key in a map in a list.
switch m.(type) {
case map[string]interface{}: // gotta have the last key
if keys0 == "*" {
for k := range m.(map[string]interface{}) {
updateValue(key, value, m, k, subkeys, cnt)
}
return
}
endVal, _ := m.(map[string]interface{})[keys0]
// if newV key is the end of path, replace the value for path-end
// may be []interface{} - means replace just an entry w/ subkeys
// otherwise replace the keys0 value if subkeys are there
// NOTE: this will replace the subkeys, also
if key == keys0 {
switch endVal.(type) {
case map[string]interface{}:
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
}
case []interface{}:
// without subkeys can't select list member to modify
// so key:value spec is it ...
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
break
}
nv := make([]interface{}, 0)
var valmodified bool
for _, v := range endVal.([]interface{}) {
// check entry subkeys
if hasSubKeys(v, subkeys) {
// replace v with value
nv = append(nv, value)
valmodified = true
(*cnt)++
continue
}
nv = append(nv, v)
}
if valmodified {
(m.(map[string]interface{}))[keys0] = interface{}(nv)
}
default: // anything else is a strict replacement
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
}
}
return
}
// so value is for an element of endVal
// if endVal is a map then 'key' must be there w/ subkeys
// if endVal is a list then 'key' must be in a list member w/ subkeys
switch endVal.(type) {
case map[string]interface{}:
if !hasSubKeys(endVal, subkeys) {
return
}
if _, ok := (endVal.(map[string]interface{}))[key]; ok {
(endVal.(map[string]interface{}))[key] = value
(*cnt)++
}
case []interface{}: // keys0 points to a list, check subkeys
for _, v := range endVal.([]interface{}) {
// got to be a map so we can replace value for 'key'
vv, vok := v.(map[string]interface{})
if !vok {
continue
}
if _, ok := vv[key]; !ok {
continue
}
if !hasSubKeys(vv, subkeys) {
continue
}
vv[key] = value
(*cnt)++
}
}
case []interface{}: // key may be in a list member
// don't need to handle keys0 == "*"; we're looking at everything, anyway.
for _, v := range m.([]interface{}) {
// only map values - we're looking for 'key'
mm, ok := v.(map[string]interface{})
if !ok {
continue
}
if _, ok := mm[key]; !ok {
continue
}
if !hasSubKeys(mm, subkeys) {
continue
}
mm[key] = value
(*cnt)++
}
}
// return
}

1139
vendor/github.com/clbanning/mxj/xml.go generated vendored Normal file

File diff suppressed because it is too large Load diff

828
vendor/github.com/clbanning/mxj/xmlseq.go generated vendored Normal file
View file

@ -0,0 +1,828 @@
// Copyright 2012-2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// xmlseq.go - version of xml.go with sequence # injection on Decoding and sorting on Encoding.
// Also, handles comments, directives and process instructions.
package mxj
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"sort"
"strings"
)
var NoRoot = errors.New("no root key")
var NO_ROOT = NoRoot // maintain backwards compatibility
// ------------------- NewMapXmlSeq & NewMapXmlSeqReader ... -------------------------
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
// The xml.Decoder.RawToken method is used to parse the XML, so there is no checking for appropriate xml.EndElement values;
// thus, it is assumed that the XML is valid.
//
// NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #seq.
// If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
// NOTE: "#seq" key/value pairs are removed on encoding with mv.XmlSeq() / mv.XmlSeqIndent().
// • attributes are a map - map["#attr"]map["attr_key"]map[string]interface{}{"#text":<aval>, "#seq":<num>}
// • all simple elements are decoded as map["#text"]interface{} with a "#seq" k:v pair, as well.
// • lists always decode as map["list_tag"][]map[string]interface{} where the array elements are maps that
// include a "#seq" k:v pair based on sequence they are decoded. Thus, XML like:
// <doc>
// <ltag>value 1</ltag>
// <newtag>value 2</newtag>
// <ltag>value 3</ltag>
// </doc>
// is decoded as:
// doc :
// ltag :[[]interface{}]
// [item: 0]
// #seq :[int] 0
// #text :[string] value 1
// [item: 1]
// #seq :[int] 2
// #text :[string] value 3
// newtag :
// #seq :[int] 1
// #text :[string] value 2
// It will encode in proper sequence even though the Map representation merges all "ltag" elements in an array.
// • comments - "<!--comment-->" - are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair.
// • directives - "<!text>" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair.
// • process instructions - "<?instr?>" - are decoded as map["#procinst"]interface{} where the #procinst value
// is of map[string]interface{} type with the following keys: #target, #inst, and #seq.
// • comments, directives, and procinsts that are NOT part of a document with a root key will be returned as
// map[string]interface{} and the error value 'NoRoot'.
// • note: "<![CDATA[" syntax is lost in xml.Decode parser - and is not handled here, either.
// and: "\r\n" is converted to "\n"
//
// NOTES:
// 1. The 'xmlVal' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
//
// NAME SPACES:
// 1. Keys in the Map value that are parsed from a <name space prefix>:<local name> tag preserve the
// "<prefix>:" notation rather than stripping it as with NewMapXml().
// 2. Attribute keys for name space prefix declarations preserve "xmlns:<prefix>" notation.
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
return xmlSeqToMap(xmlVal, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
// NOTES:
// 1. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
// We need to put an *os.File reader in a ByteReader or the xml.NewDecoder
// will wrap it in a bufio.Reader and seek on the file beyond where the
// xml.Decoder parses!
if _, ok := xmlReader.(io.ByteReader); !ok {
xmlReader = myByteReader(xmlReader) // see code at EOF
}
// build the map
return xmlSeqReaderToMap(xmlReader, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
// NOTES:
// 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
// See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large
// data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body
// you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call.
// 2. The 'raw' return value may be larger than the XML text value.
// 3. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 4. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
// create TeeReader so we can retrieve raw XML
buf := make([]byte, 0)
wb := bytes.NewBuffer(buf)
trdr := myTeeReader(xmlReader, wb)
m, err := xmlSeqReaderToMap(trdr, r)
// retrieve the raw XML that was decoded
b := wb.Bytes()
// err may be NoRoot
return m, b, err
}
// xmlSeqReaderToMap() - parse a XML io.Reader to a map[string]interface{} value
func xmlSeqReaderToMap(rdr io.Reader, r bool) (map[string]interface{}, error) {
// parse the Reader
p := xml.NewDecoder(rdr)
if CustomDecoder != nil {
useCustomDecoder(p)
} else {
p.CharsetReader = XmlCharsetReader
}
return xmlSeqToMapParser("", nil, p, r)
}
// xmlSeqToMap - convert a XML doc into map[string]interface{} value
func xmlSeqToMap(doc []byte, r bool) (map[string]interface{}, error) {
b := bytes.NewReader(doc)
p := xml.NewDecoder(b)
if CustomDecoder != nil {
useCustomDecoder(p)
} else {
p.CharsetReader = XmlCharsetReader
}
return xmlSeqToMapParser("", nil, p, r)
}
// ===================================== where the work happens =============================
// xmlSeqToMapParser - load a 'clean' XML doc into a map[string]interface{} directly.
// Add #seq tag value for each element decoded - to be used for Encoding later.
func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[string]interface{}, error) {
if snakeCaseKeys {
skey = strings.Replace(skey, "-", "_", -1)
}
// NOTE: all attributes and sub-elements parsed into 'na', 'na' is returned as value for 'skey' in 'n'.
var n, na map[string]interface{}
var seq int // for including seq num when decoding
// Allocate maps and load attributes, if any.
// NOTE: on entry from NewMapXml(), etc., skey=="", and we fall through
// to get StartElement then recurse with skey==xml.StartElement.Name.Local
// where we begin allocating map[string]interface{} values 'n' and 'na'.
if skey != "" {
// 'n' only needs one slot - save call to runtime•hashGrow()
// 'na' we don't know
n = make(map[string]interface{}, 1)
na = make(map[string]interface{})
if len(a) > 0 {
// xml.Attr is decoded into: map["#attr"]map[<attr_label>]interface{}
// where interface{} is map[string]interface{}{"#text":<attr_val>, "#seq":<attr_seq>}
aa := make(map[string]interface{}, len(a))
for i, v := range a {
if snakeCaseKeys {
v.Name.Local = strings.Replace(v.Name.Local, "-", "_", -1)
}
if len(v.Name.Space) > 0 {
aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
} else {
aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
}
}
na["#attr"] = aa
}
}
// Return XMPP <stream:stream> message.
if handleXMPPStreamTag && skey == "stream:stream" {
n[skey] = na
return n, nil
}
for {
t, err := p.RawToken()
if err != nil {
if err != io.EOF {
return nil, errors.New("xml.Decoder.Token() - " + err.Error())
}
return nil, err
}
switch t.(type) {
case xml.StartElement:
tt := t.(xml.StartElement)
// First call to xmlSeqToMapParser() doesn't pass xml.StartElement - the map key.
// So when the loop is first entered, the first token is the root tag along
// with any attributes, which we process here.
//
// Subsequent calls to xmlSeqToMapParser() will pass in tag+attributes for
// processing before getting the next token which is the element value,
// which is done above.
if skey == "" {
if len(tt.Name.Space) > 0 {
return xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r)
} else {
return xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r)
}
}
// If not initializing the map, parse the element.
// len(nn) == 1, necessarily - it is just an 'n'.
var nn map[string]interface{}
if len(tt.Name.Space) > 0 {
nn, err = xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r)
} else {
nn, err = xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r)
}
if err != nil {
return nil, err
}
// The nn map[string]interface{} value is a na[nn_key] value.
// We need to see if nn_key already exists - means we're parsing a list.
// This may require converting na[nn_key] value into []interface{} type.
// First, extract the key:val for the map - it's a singleton.
var key string
var val interface{}
for key, val = range nn {
break
}
// add "#seq" k:v pair -
// Sequence number included even in list elements - this should allow us
// to properly resequence even something goofy like:
// <list>item 1</list>
// <subelement>item 2</subelement>
// <list>item 3</list>
// where all the "list" subelements are decoded into an array.
switch val.(type) {
case map[string]interface{}:
val.(map[string]interface{})["#seq"] = seq
seq++
case interface{}: // a non-nil simple element: string, float64, bool
v := map[string]interface{}{"#text": val, "#seq": seq}
seq++
val = v
}
// 'na' holding sub-elements of n.
// See if 'key' already exists.
// If 'key' exists, then this is a list, if not just add key:val to na.
if v, ok := na[key]; ok {
var a []interface{}
switch v.(type) {
case []interface{}:
a = v.([]interface{})
default: // anything else - note: v.(type) != nil
a = []interface{}{v}
}
a = append(a, val)
na[key] = a
} else {
na[key] = val // save it as a singleton
}
case xml.EndElement:
if skey != "" {
tt := t.(xml.EndElement)
if snakeCaseKeys {
tt.Name.Local = strings.Replace(tt.Name.Local, "-", "_", -1)
}
var name string
if len(tt.Name.Space) > 0 {
name = tt.Name.Space + `:` + tt.Name.Local
} else {
name = tt.Name.Local
}
if skey != name {
return nil, fmt.Errorf("element %s not properly terminated, got %s at #%d",
skey, name, p.InputOffset())
}
}
// len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case.
if len(n) == 0 {
// If len(na)==0 we have an empty element == "";
// it has no xml.Attr nor xml.CharData.
// Empty element content will be map["etag"]map["#text"]""
// after #seq injection - map["etag"]map["#seq"]seq - after return.
if len(na) > 0 {
n[skey] = na
} else {
n[skey] = "" // empty element
}
}
return n, nil
case xml.CharData:
// clean up possible noise
tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ")
if skey == "" {
// per Adrian (http://www.adrianlungu.com/) catch stray text
// in decoder stream -
// https://github.com/clbanning/mxj/pull/14#issuecomment-182816374
// NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get
// a p.Token() decoding error when the BOM is UTF-16 or UTF-32.
continue
}
if len(tt) > 0 {
// every simple element is a #text and has #seq associated with it
na["#text"] = cast(tt, r)
na["#seq"] = seq
seq++
}
case xml.Comment:
if n == nil { // no root 'key'
n = map[string]interface{}{"#comment": string(t.(xml.Comment))}
return n, NoRoot
}
cm := make(map[string]interface{}, 2)
cm["#text"] = string(t.(xml.Comment))
cm["#seq"] = seq
seq++
na["#comment"] = cm
case xml.Directive:
if n == nil { // no root 'key'
n = map[string]interface{}{"#directive": string(t.(xml.Directive))}
return n, NoRoot
}
dm := make(map[string]interface{}, 2)
dm["#text"] = string(t.(xml.Directive))
dm["#seq"] = seq
seq++
na["#directive"] = dm
case xml.ProcInst:
if n == nil {
na = map[string]interface{}{"#target": t.(xml.ProcInst).Target, "#inst": string(t.(xml.ProcInst).Inst)}
n = map[string]interface{}{"#procinst": na}
return n, NoRoot
}
pm := make(map[string]interface{}, 3)
pm["#target"] = t.(xml.ProcInst).Target
pm["#inst"] = string(t.(xml.ProcInst).Inst)
pm["#seq"] = seq
seq++
na["#procinst"] = pm
default:
// noop - shouldn't ever get here, now, since we handle all token types
}
}
}
// ------------------ END: NewMapXml & NewMapXmlReader -------------------------
// --------------------- mv.XmlSeq & mv.XmlSeqWriter -------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a Map as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// The following rules apply.
// - The key label "#text" is treated as the value for a simple element with attributes.
// - The "#seq" key is used to seqence the subelements or attributes but is ignored for writing.
// - The "#attr" map key identifies the map of attribute map[string]interface{} values with "#text" key.
// - The "#comment" map key identifies a comment in the value "#text" map entry - <!--comment-->.
// - The "#directive" map key identifies a directive in the value "#text" map entry - <!directive>.
// - The "#procinst" map key identifies a process instruction in the value "#target" and "#inst"
// map entries - <?target inst?>.
// - Value type encoding:
// > string, bool, float64, int, int32, int64, float32: per "%v" formating
// > []bool, []uint8: by casting to string
// > structures, etc.: handed to xml.Marshal() - if there is an error, the element
// value is "UNKNOWN"
// - Elements with only attribute values or are null are terminated using "/>" unless XmlGoEmptyElemSystax() called.
// - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
// Thus, `{ "key":"value" }` encodes as "<key>value</key>".
func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
p := new(pretty) // just a stub
if len(m) == 1 && len(rootTag) == 0 {
for key, value := range m {
// if it's an array, see if all values are map[string]interface{}
// we force a new root tag if we'll end up with no key:value in the list
// so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></doc>
switch value.(type) {
case []interface{}:
for _, v := range value.([]interface{}) {
switch v.(type) {
case map[string]interface{}: // noop
default: // anything else
err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p)
goto done
}
}
}
err = mapToXmlSeqIndent(false, s, key, value, p)
}
} else if len(rootTag) == 1 {
err = mapToXmlSeqIndent(false, s, rootTag[0], m, p)
} else {
err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p)
}
done:
return []byte(*s), err
}
// The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
// The names will also provide a key for the number of return arguments.
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.XmlSeq(rootTag...)
if err != nil {
return err
}
_, err = xmlWriter.Write(x)
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeq(rootTag...)
if err != nil {
return x, err
}
_, err = xmlWriter.Write(x)
return x, err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return err
}
_, err = xmlWriter.Write(x)
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return x, err
}
_, err = xmlWriter.Write(x)
return x, err
}
// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
// ---------------------- XmlSeqIndent ----------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a map[string]interface{} as a pretty XML string.
// See mv.XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
p := new(pretty)
p.indent = indent
p.padding = prefix
if len(m) == 1 && len(rootTag) == 0 {
// this can extract the key for the single map element
// use it if it isn't a key for a list
for key, value := range m {
if _, ok := value.([]interface{}); ok {
err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p)
} else {
err = mapToXmlSeqIndent(true, s, key, value, p)
}
}
} else if len(rootTag) == 1 {
err = mapToXmlSeqIndent(true, s, rootTag[0], m, p)
} else {
err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p)
}
return []byte(*s), err
}
// where the work actually happens
// returns an error if an attribute is not atomic
func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
var endTag bool
var isSimple bool
var noEndTag bool
var elen int
var ss string
p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start}
switch value.(type) {
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
if doIndent {
*s += p.padding
}
if key != "#comment" && key != "#directive" && key != "#procinst" {
*s += `<` + key
}
}
switch value.(type) {
case map[string]interface{}:
val := value.(map[string]interface{})
if key == "#comment" {
*s += `<!--` + val["#text"].(string) + `-->`
noEndTag = true
break
}
if key == "#directive" {
*s += `<!` + val["#text"].(string) + `>`
noEndTag = true
break
}
if key == "#procinst" {
*s += `<?` + val["#target"].(string) + ` ` + val["#inst"].(string) + `?>`
noEndTag = true
break
}
haveAttrs := false
// process attributes first
if v, ok := val["#attr"].(map[string]interface{}); ok {
// First, unroll the map[string]interface{} into a []keyval array.
// Then sequence it.
kv := make([]keyval, len(v))
n := 0
for ak, av := range v {
kv[n] = keyval{ak, av}
n++
}
sort.Sort(elemListSeq(kv))
// Now encode the attributes in original decoding sequence, using keyval array.
for _, a := range kv {
vv := a.v.(map[string]interface{})
switch vv["#text"].(type) {
case string:
if xmlEscapeChars {
ss = escapeChars(vv["#text"].(string))
} else {
ss = vv["#text"].(string)
}
*s += ` ` + a.k + `="` + ss + `"`
case float64, bool, int, int32, int64, float32:
*s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"`
case []byte:
if xmlEscapeChars {
ss = escapeChars(string(vv["#text"].([]byte)))
} else {
ss = string(vv["#text"].([]byte))
}
*s += ` ` + a.k + `="` + ss + `"`
default:
return fmt.Errorf("invalid attribute value for: %s", a.k)
}
}
haveAttrs = true
}
// simple element?
// every map value has, at least, "#seq" and, perhaps, "#text" and/or "#attr"
_, seqOK := val["#seq"] // have key
if v, ok := val["#text"]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK {
if stmp, ok := v.(string); ok && stmp != "" {
if xmlEscapeChars {
stmp = escapeChars(stmp)
}
*s += ">" + stmp
endTag = true
elen = 1
}
isSimple = true
break
} else if !ok && ((len(val) == 2 && haveAttrs) || (len(val) == 1 && !haveAttrs)) && seqOK {
// here no #text but have #seq or #seq+#attr
endTag = false
break
}
// we now need to sequence everything except attributes
// 'kv' will hold everything that needs to be written
kv := make([]keyval, 0)
for k, v := range val {
if k == "#attr" { // already processed
continue
}
if k == "#seq" { // ignore - just for sorting
continue
}
switch v.(type) {
case []interface{}:
// unwind the array as separate entries
for _, vv := range v.([]interface{}) {
kv = append(kv, keyval{k, vv})
}
default:
kv = append(kv, keyval{k, v})
}
}
// close tag with possible attributes
*s += ">"
if doIndent {
*s += "\n"
}
// something more complex
p.mapDepth++
sort.Sort(elemListSeq(kv))
i := 0
for _, v := range kv {
switch v.v.(type) {
case []interface{}:
default:
if i == 0 && doIndent {
p.Indent()
}
}
i++
if err := mapToXmlSeqIndent(doIndent, s, v.k, v.v, p); err != nil {
return err
}
switch v.v.(type) {
case []interface{}: // handled in []interface{} case
default:
if doIndent {
p.Outdent()
}
}
i--
}
p.mapDepth--
endTag = true
elen = 1 // we do have some content other than attrs
case []interface{}:
for _, v := range value.([]interface{}) {
if doIndent {
p.Indent()
}
if err := mapToXmlSeqIndent(doIndent, s, key, v, p); err != nil {
return err
}
if doIndent {
p.Outdent()
}
}
return nil
case nil:
// terminate the tag
if doIndent {
*s += p.padding
}
*s += "<" + key
endTag, isSimple = true, true
break
default: // handle anything - even goofy stuff
elen = 0
switch value.(type) {
case string:
if xmlEscapeChars {
ss = escapeChars(value.(string))
} else {
ss = value.(string)
}
elen = len(ss)
if elen > 0 {
*s += ">" + ss
}
case float64, bool, int, int32, int64, float32:
v := fmt.Sprintf("%v", value)
elen = len(v)
if elen > 0 {
*s += ">" + v
}
case []byte: // NOTE: byte is just an alias for uint8
// similar to how xml.Marshal handles []byte structure members
if xmlEscapeChars {
ss = escapeChars(string(value.([]byte)))
} else {
ss = string(value.([]byte))
}
elen = len(ss)
if elen > 0 {
*s += ">" + ss
}
default:
var v []byte
var err error
if doIndent {
v, err = xml.MarshalIndent(value, p.padding, p.indent)
} else {
v, err = xml.Marshal(value)
}
if err != nil {
*s += ">UNKNOWN"
} else {
elen = len(v)
if elen > 0 {
*s += string(v)
}
}
}
isSimple = true
endTag = true
}
if endTag && !noEndTag {
if doIndent {
if !isSimple {
*s += p.padding
}
}
switch value.(type) {
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
if elen > 0 || useGoXmlEmptyElemSyntax {
if elen == 0 {
*s += ">"
}
*s += `</` + key + ">"
} else {
*s += `/>`
}
}
} else if !noEndTag {
if useGoXmlEmptyElemSyntax {
*s += `</` + key + ">"
// *s += "></" + key + ">"
} else {
*s += "/>"
}
}
if doIndent {
if p.cnt > p.start {
*s += "\n"
}
p.Outdent()
}
return nil
}
// the element sort implementation
type keyval struct {
k string
v interface{}
}
type elemListSeq []keyval
func (e elemListSeq) Len() int {
return len(e)
}
func (e elemListSeq) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e elemListSeq) Less(i, j int) bool {
var iseq, jseq int
var ok bool
if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok {
iseq = 9999999
}
if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok {
jseq = 9999999
}
return iseq <= jseq
}
// =============== https://groups.google.com/forum/#!topic/golang-nuts/lHPOHD-8qio
// BeautifyXml (re)formats an XML doc similar to Map.XmlIndent().
func BeautifyXml(b []byte, prefix, indent string) ([]byte, error) {
x, err := NewMapXmlSeq(b)
if err != nil {
return nil, err
}
return x.XmlSeqIndent(prefix, indent)
}

View file

@ -1 +0,0 @@
/coverage.txt

View file

@ -1,27 +0,0 @@
language: go
sudo: false
dist: trusty
go:
- 1.x
- 1.2.x
- 1.3.x
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- master
before_install:
- find "${GOPATH%%:*}" -name '*.a' -delete
- rm -rf "${GOPATH%%:*}/src/golang.org"
- go get golang.org/x/tools/cover
- go get golang.org/x/tools/cmd/cover
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s "https://codecov.io/bash")

View file

@ -1,37 +0,0 @@
# Change Log
**ATTN**: This project uses [semantic versioning](http://semver.org/).
## [Unreleased]
### Added
- `Recovery.ErrorHandlerFunc` for custom error handling during recovery
- `With()` helper for building a new `Negroni` struct chaining handlers from
existing `Negroni` structs
### Fixed
- `Written()` correct returns `false` if no response header has been written
### Changed
- Set default status to `0` in the case that no handler writes status -- was
previously `200` (in 0.2.0, before that it was `0` so this reestablishes that
behavior)
- Catch `panic`s thrown by callbacks provided to the `Recovery` handler
## [0.2.0] - 2016-05-10
### Added
- Support for variadic handlers in `New()`
- Added `Negroni.Handlers()` to fetch all of the handlers for a given chain
- Allowed size in `Recovery` handler was bumped to 8k
- `Negroni.UseFunc` to push another handler onto the chain
### Changed
- Set the status before calling `beforeFuncs` so the information is available to them
- Set default status to `200` in the case that no handler writes status -- was previously `0`
- Panic if `nil` handler is given to `negroni.Use`
## 0.1.0 - 2013-07-22
### Added
- Initial implementation.
[Unreleased]: https://github.com/urfave/negroni/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/urfave/negroni/compare/v0.1.0...v0.2.0

View file

@ -1,546 +0,0 @@
# Negroni
[![GoDoc](https://godoc.org/github.com/urfave/negroni?status.svg)](http://godoc.org/github.com/urfave/negroni)
[![Build Status](https://travis-ci.org/urfave/negroni.svg?branch=master)](https://travis-ci.org/urfave/negroni)
[![codebeat](https://codebeat.co/badges/47d320b1-209e-45e8-bd99-9094bc5111e2)](https://codebeat.co/projects/github-com-urfave-negroni)
[![codecov](https://codecov.io/gh/urfave/negroni/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/negroni)
**Notice:** This is the library formerly known as
`github.com/codegangsta/negroni` -- Github will automatically redirect requests
to this repository, but we recommend updating your references for clarity.
Negroni is an idiomatic approach to web middleware in Go. It is tiny,
non-intrusive, and encourages use of `net/http` Handlers.
If you like the idea of [Martini](https://github.com/go-martini/martini), but
you think it contains too much magic, then Negroni is a great fit.
Language Translations:
* [Deutsch (de_DE)](translations/README_de_de.md)
* [Português Brasileiro (pt_BR)](translations/README_pt_br.md)
* [简体中文 (zh_cn)](translations/README_zh_cn.md)
* [繁體中文 (zh_tw)](translations/README_zh_tw.md)
* [日本語 (ja_JP)](translations/README_ja_JP.md)
* [Français (fr_FR)](translations/README_fr_FR.md)
## Getting Started
After installing Go and setting up your
[GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file.
We'll call it `server.go`.
<!-- { "interrupt": true } -->
``` go
package main
import (
"fmt"
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to the home page!")
})
n := negroni.Classic() // Includes some default middlewares
n.UseHandler(mux)
http.ListenAndServe(":3000", n)
}
```
Then install the Negroni package (**NOTE**: &gt;= **go 1.1** is required):
```
go get github.com/urfave/negroni
```
Then run your server:
```
go run server.go
```
You will now have a Go `net/http` webserver running on `localhost:3000`.
### Packaging
If you are on Debian, `negroni` is also available as [a
package](https://packages.debian.org/sid/golang-github-urfave-negroni-dev) that
you can install via `apt install golang-github-urfave-negroni-dev` (at the time
of writing, it is in the `sid` repositories).
## Is Negroni a Framework?
Negroni is **not** a framework. It is a middleware-focused library that is
designed to work directly with `net/http`.
## Routing?
Negroni is BYOR (Bring your own Router). The Go community already has a number
of great http routers available, and Negroni tries to play well with all of them
by fully supporting `net/http`. For instance, integrating with [Gorilla Mux]
looks like so:
``` go
router := mux.NewRouter()
router.HandleFunc("/", HomeHandler)
n := negroni.New(Middleware1, Middleware2)
// Or use a middleware with the Use() function
n.Use(Middleware3)
// router goes last
n.UseHandler(router)
http.ListenAndServe(":3001", n)
```
## `negroni.Classic()`
`negroni.Classic()` provides some default middleware that is useful for most
applications:
* [`negroni.Recovery`](#recovery) - Panic Recovery Middleware.
* [`negroni.Logger`](#logger) - Request/Response Logger Middleware.
* [`negroni.Static`](#static) - Static File serving under the "public"
directory.
This makes it really easy to get started with some useful features from Negroni.
## Handlers
Negroni provides a bidirectional middleware flow. This is done through the
`negroni.Handler` interface:
``` go
type Handler interface {
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}
```
If a middleware hasn't already written to the `ResponseWriter`, it should call
the next `http.HandlerFunc` in the chain to yield to the next middleware
handler. This can be used for great good:
``` go
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// do some stuff before
next(rw, r)
// do some stuff after
}
```
And you can map it to the handler chain with the `Use` function:
``` go
n := negroni.New()
n.Use(negroni.HandlerFunc(MyMiddleware))
```
You can also map plain old `http.Handler`s:
``` go
n := negroni.New()
mux := http.NewServeMux()
// map your routes
n.UseHandler(mux)
http.ListenAndServe(":3000", n)
```
## `With()`
Negroni has a convenience function called `With`. `With` takes one or more
`Handler` instances and returns a new `Negroni` with the combination of the
receiver's handlers and the new handlers.
```go
// middleware we want to reuse
common := negroni.New()
common.Use(MyMiddleware1)
common.Use(MyMiddleware2)
// `specific` is a new negroni with the handlers from `common` combined with the
// the handlers passed in
specific := common.With(
SpecificMiddleware1,
SpecificMiddleware2
)
```
## `Run()`
Negroni has a convenience function called `Run`. `Run` takes an addr string
identical to [`http.ListenAndServe`](https://godoc.org/net/http#ListenAndServe).
<!-- { "interrupt": true } -->
``` go
package main
import (
"github.com/urfave/negroni"
)
func main() {
n := negroni.Classic()
n.Run(":8080")
}
```
If no address is provided, the `PORT` environment variable is used instead.
If the `PORT` environment variable is not defined, the default address will be used.
See [Run](https://godoc.org/github.com/urfave/negroni#Negroni.Run) for a complete description.
In general, you will want to use `net/http` methods and pass `negroni` as a
`Handler`, as this is more flexible, e.g.:
<!-- { "interrupt": true } -->
``` go
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to the home page!")
})
n := negroni.Classic() // Includes some default middlewares
n.UseHandler(mux)
s := &http.Server{
Addr: ":8080",
Handler: n,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
```
## Route Specific Middleware
If you have a route group of routes that need specific middleware to be
executed, you can simply create a new Negroni instance and use it as your route
handler.
``` go
router := mux.NewRouter()
adminRoutes := mux.NewRouter()
// add admin routes here
// Create a new negroni for the admin middleware
router.PathPrefix("/admin").Handler(negroni.New(
Middleware1,
Middleware2,
negroni.Wrap(adminRoutes),
))
```
If you are using [Gorilla Mux], here is an example using a subrouter:
``` go
router := mux.NewRouter()
subRouter := mux.NewRouter().PathPrefix("/subpath").Subrouter().StrictSlash(true)
subRouter.HandleFunc("/", someSubpathHandler) // "/subpath/"
subRouter.HandleFunc("/:id", someSubpathHandler) // "/subpath/:id"
// "/subpath" is necessary to ensure the subRouter and main router linkup
router.PathPrefix("/subpath").Handler(negroni.New(
Middleware1,
Middleware2,
negroni.Wrap(subRouter),
))
```
`With()` can be used to eliminate redundancy for middlewares shared across
routes.
``` go
router := mux.NewRouter()
apiRoutes := mux.NewRouter()
// add api routes here
webRoutes := mux.NewRouter()
// add web routes here
// create common middleware to be shared across routes
common := negroni.New(
Middleware1,
Middleware2,
)
// create a new negroni for the api middleware
// using the common middleware as a base
router.PathPrefix("/api").Handler(common.With(
APIMiddleware1,
negroni.Wrap(apiRoutes),
))
// create a new negroni for the web middleware
// using the common middleware as a base
router.PathPrefix("/web").Handler(common.With(
WebMiddleware1,
negroni.Wrap(webRoutes),
))
```
## Bundled Middleware
### Static
This middleware will serve files on the filesystem. If the files do not exist,
it proxies the request to the next middleware. If you want the requests for
non-existent files to return a `404 File Not Found` to the user you should look
at using [http.FileServer](https://golang.org/pkg/net/http/#FileServer) as
a handler.
Example:
<!-- { "interrupt": true } -->
``` go
package main
import (
"fmt"
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to the home page!")
})
// Example of using a http.FileServer if you want "server-like" rather than "middleware" behavior
// mux.Handle("/public", http.FileServer(http.Dir("/home/public")))
n := negroni.New()
n.Use(negroni.NewStatic(http.Dir("/tmp")))
n.UseHandler(mux)
http.ListenAndServe(":3002", n)
}
```
Will serve files from the `/tmp` directory first, but proxy calls to the next
handler if the request does not match a file on the filesystem.
### Recovery
This middleware catches `panic`s and responds with a `500` response code. If
any other middleware has written a response code or body, this middleware will
fail to properly send a 500 to the client, as the client has already received
the HTTP response code. Additionally, an `ErrorHandlerFunc` can be attached
to report 500's to an error reporting service such as Sentry or Airbrake.
Example:
<!-- { "interrupt": true } -->
``` go
package main
import (
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
panic("oh no")
})
n := negroni.New()
n.Use(negroni.NewRecovery())
n.UseHandler(mux)
http.ListenAndServe(":3003", n)
}
```
Will return a `500 Internal Server Error` to each request. It will also log the
stack traces as well as print the stack trace to the requester if `PrintStack`
is set to `true` (the default).
Example with error handler:
``` go
package main
import (
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
panic("oh no")
})
n := negroni.New()
recovery := negroni.NewRecovery()
recovery.ErrorHandlerFunc = reportToSentry
n.Use(recovery)
n.UseHandler(mux)
http.ListenAndServe(":3003", n)
}
func reportToSentry(error interface{}) {
// write code here to report error to Sentry
}
```
The middleware simply output the informations on STDOUT by default.
You can customize the output process by using the `SetFormatter()` function.
You can use also the `HTMLPanicFormatter` to display a pretty HTML when a crash occurs.
<!-- { "interrupt": true } -->
``` go
package main
import (
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
panic("oh no")
})
n := negroni.New()
recovery := negroni.NewRecovery()
recovery.Formatter = &negroni.HTMLPanicFormatter{}
n.Use(recovery)
n.UseHandler(mux)
http.ListenAndServe(":3003", n)
}
```
## Logger
This middleware logs each incoming request and response.
Example:
<!-- { "interrupt": true } -->
``` go
package main
import (
"fmt"
"net/http"
"github.com/urfave/negroni"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to the home page!")
})
n := negroni.New()
n.Use(negroni.NewLogger())
n.UseHandler(mux)
http.ListenAndServe(":3004", n)
}
```
Will print a log similar to:
```
[negroni] 2017-10-04T14:56:25+02:00 | 200 | 378µs | localhost:3004 | GET /
```
on each request.
You can also set your own log format by calling the `SetFormat` function. The format is a template string with fields as mentioned in the `LoggerEntry` struct. So, as an example -
```go
l.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}")
```
will show something like - `[200 18.263µs] - Go-User-Agent/1.1 `
## Third Party Middleware
Here is a current list of Negroni compatible middlware. Feel free to put up a PR
linking your middleware if you have built one:
| Middleware | Author | Description |
| -----------|--------|-------------|
| [authz](https://github.com/casbin/negroni-authz) | [Yang Luo](https://github.com/hsluoyz) | ACL, RBAC, ABAC Authorization middlware based on [Casbin](https://github.com/casbin/casbin) |
| [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs |
| [cloudwatch](https://github.com/cvillecsteele/negroni-cloudwatch) | [Colin Steele](https://github.com/cvillecsteele) | AWS cloudwatch metrics middleware |
| [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support |
| [csp](https://github.com/awakenetworks/csp) | [Awake Networks](https://github.com/awakenetworks) | [Content Security Policy](https://www.w3.org/TR/CSP2/) (CSP) support |
| [delay](https://github.com/jeffbmartinez/delay) | [Jeff Martinez](https://github.com/jeffbmartinez) | Add delays/latency to endpoints. Useful when testing effects of high latency |
| [New Relic Go Agent](https://github.com/yadvendar/negroni-newrelic-go-agent) | [Yadvendar Champawat](https://github.com/yadvendar) | Official [New Relic Go Agent](https://github.com/newrelic/go-agent) (currently in beta) |
| [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime |
| [Graceful](https://github.com/tylerb/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown |
| [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression |
| [JWT Middleware](https://github.com/auth0/go-jwt-middleware) | [Auth0](https://github.com/auth0) | Middleware checks for a JWT on the `Authorization` header on incoming requests and decodes it|
| [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger |
| [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware |
| [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly |
| [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions |
| [prometheus](https://github.com/zbindenren/negroni-prometheus) | [Rene Zbinden](https://github.com/zbindenren) | Easily create metrics endpoint for the [prometheus](http://prometheus.io) instrumentation tool |
| [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates |
| [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints |
| [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins |
| [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management |
| [stats](https://github.com/thoas/stats) | [Florent Messa](https://github.com/thoas) | Store information about your web application (response time, etc.) |
| [VanGoH](https://github.com/auroratechnologies/vangoh) | [Taylor Wrobel](https://github.com/twrobel3) | Configurable [AWS-Style](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html) HMAC authentication middleware |
| [xrequestid](https://github.com/pilu/xrequestid) | [Andrea Franz](https://github.com/pilu) | Middleware that assigns a random X-Request-Id header to each request |
| [mgo session](https://github.com/joeljames/nigroni-mgo-session) | [Joel James](https://github.com/joeljames) | Middleware that handles creating and closing mgo sessions per request |
| [digits](https://github.com/bamarni/digits) | [Bilal Amarni](https://github.com/bamarni) | Middleware that handles [Twitter Digits](https://get.digits.com/) authentication |
## Examples
[Alexander Rødseth](https://github.com/xyproto) created
[mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a
Negroni middleware handler.
[Prasanga Siripala](https://github.com/pjebs) created an effective skeleton structure for web-based Go/Negroni projects: [Go-Skeleton](https://github.com/pjebs/go-skeleton)
## Live code reload?
[gin](https://github.com/codegangsta/gin) and
[fresh](https://github.com/pilu/fresh) both live reload negroni apps.
## Essential Reading for Beginners of Go & Negroni
* [Using a Context to pass information from middleware to end handler](http://elithrar.github.io/article/map-string-interface/)
* [Understanding middleware](https://mattstauffer.co/blog/laravel-5.0-middleware-filter-style)
## About
Negroni is obsessively designed by none other than the [Code
Gangsta](https://codegangsta.io/)
[Gorilla Mux]: https://github.com/gorilla/mux
[`http.FileSystem`]: https://godoc.org/net/http#FileSystem

View file

@ -1,25 +0,0 @@
// Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers.
//
// If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit.
//
// For a full guide visit http://github.com/urfave/negroni
//
// package main
//
// import (
// "github.com/urfave/negroni"
// "net/http"
// "fmt"
// )
//
// func main() {
// mux := http.NewServeMux()
// mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// fmt.Fprintf(w, "Welcome to the home page!")
// })
//
// n := negroni.Classic()
// n.UseHandler(mux)
// n.Run(":3000")
// }
package negroni

View file

@ -1,82 +0,0 @@
package negroni
import (
"bytes"
"log"
"net/http"
"os"
"text/template"
"time"
)
// LoggerEntry is the structure
// passed to the template.
type LoggerEntry struct {
StartTime string
Status int
Duration time.Duration
Hostname string
Method string
Path string
Request *http.Request
}
// LoggerDefaultFormat is the format
// logged used by the default Logger instance.
var LoggerDefaultFormat = "{{.StartTime}} | {{.Status}} | \t {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}} \n"
// LoggerDefaultDateFormat is the
// format used for date by the
// default Logger instance.
var LoggerDefaultDateFormat = time.RFC3339
// ALogger interface
type ALogger interface {
Println(v ...interface{})
Printf(format string, v ...interface{})
}
// Logger is a middleware handler that logs the request as it goes in and the response as it goes out.
type Logger struct {
// ALogger implements just enough log.Logger interface to be compatible with other implementations
ALogger
dateFormat string
template *template.Template
}
// NewLogger returns a new Logger instance
func NewLogger() *Logger {
logger := &Logger{ALogger: log.New(os.Stdout, "[negroni] ", 0), dateFormat: LoggerDefaultDateFormat}
logger.SetFormat(LoggerDefaultFormat)
return logger
}
func (l *Logger) SetFormat(format string) {
l.template = template.Must(template.New("negroni_parser").Parse(format))
}
func (l *Logger) SetDateFormat(format string) {
l.dateFormat = format
}
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now()
next(rw, r)
res := rw.(ResponseWriter)
log := LoggerEntry{
StartTime: start.Format(l.dateFormat),
Status: res.Status(),
Duration: time.Since(start),
Hostname: r.Host,
Method: r.Method,
Path: r.URL.Path,
Request: r,
}
buff := &bytes.Buffer{}
l.template.Execute(buff, log)
l.Printf(buff.String())
}

View file

@ -1,169 +0,0 @@
package negroni
import (
"log"
"net/http"
"os"
)
const (
// DefaultAddress is used if no other is specified.
DefaultAddress = ":8080"
)
// Handler handler is an interface that objects can implement to be registered to serve as middleware
// in the Negroni middleware stack.
// ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc
// passed in.
//
// If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked.
type Handler interface {
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}
// HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers.
// If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.
type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
h(rw, r, next)
}
type middleware struct {
handler Handler
next *middleware
}
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
}
// Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni
// middleware. The next http.HandlerFunc is automatically called after the Handler
// is executed.
func Wrap(handler http.Handler) Handler {
return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
handler.ServeHTTP(rw, r)
next(rw, r)
})
}
// WrapFunc converts a http.HandlerFunc into a negroni.Handler so it can be used as a Negroni
// middleware. The next http.HandlerFunc is automatically called after the Handler
// is executed.
func WrapFunc(handlerFunc http.HandlerFunc) Handler {
return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
handlerFunc(rw, r)
next(rw, r)
})
}
// Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler.
// Negroni middleware is evaluated in the order that they are added to the stack using
// the Use and UseHandler methods.
type Negroni struct {
middleware middleware
handlers []Handler
}
// New returns a new Negroni instance with no middleware preconfigured.
func New(handlers ...Handler) *Negroni {
return &Negroni{
handlers: handlers,
middleware: build(handlers),
}
}
// With returns a new Negroni instance that is a combination of the negroni
// receiver's handlers and the provided handlers.
func (n *Negroni) With(handlers ...Handler) *Negroni {
return New(
append(n.handlers, handlers...)...,
)
}
// Classic returns a new Negroni instance with the default middleware already
// in the stack.
//
// Recovery - Panic Recovery Middleware
// Logger - Request/Response Logging
// Static - Static File Serving
func Classic() *Negroni {
return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public")))
}
func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
n.middleware.ServeHTTP(NewResponseWriter(rw), r)
}
// Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni.
func (n *Negroni) Use(handler Handler) {
if handler == nil {
panic("handler cannot be nil")
}
n.handlers = append(n.handlers, handler)
n.middleware = build(n.handlers)
}
// UseFunc adds a Negroni-style handler function onto the middleware stack.
func (n *Negroni) UseFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)) {
n.Use(HandlerFunc(handlerFunc))
}
// UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni.
func (n *Negroni) UseHandler(handler http.Handler) {
n.Use(Wrap(handler))
}
// UseHandlerFunc adds a http.HandlerFunc-style handler function onto the middleware stack.
func (n *Negroni) UseHandlerFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request)) {
n.UseHandler(http.HandlerFunc(handlerFunc))
}
// Run is a convenience function that runs the negroni stack as an HTTP
// server. The addr string, if provided, takes the same format as http.ListenAndServe.
// If no address is provided but the PORT environment variable is set, the PORT value is used.
// If neither is provided, the address' value will equal the DefaultAddress constant.
func (n *Negroni) Run(addr ...string) {
l := log.New(os.Stdout, "[negroni] ", 0)
finalAddr := detectAddress(addr...)
l.Printf("listening on %s", finalAddr)
l.Fatal(http.ListenAndServe(finalAddr, n))
}
func detectAddress(addr ...string) string {
if len(addr) > 0 {
return addr[0]
}
if port := os.Getenv("PORT"); port != "" {
return ":" + port
}
return DefaultAddress
}
// Returns a list of all the handlers in the current Negroni middleware chain.
func (n *Negroni) Handlers() []Handler {
return n.handlers
}
func build(handlers []Handler) middleware {
var next middleware
if len(handlers) == 0 {
return voidMiddleware()
} else if len(handlers) > 1 {
next = build(handlers[1:])
} else {
next = voidMiddleware()
}
return middleware{handlers[0], &next}
}
func voidMiddleware() middleware {
return middleware{
HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}),
&middleware{},
}
}

View file

@ -1,179 +0,0 @@
package negroni
import (
"fmt"
"log"
"net/http"
"os"
"runtime"
"runtime/debug"
"text/template"
)
const (
panicText = "PANIC: %s\n%s"
panicHTML = `<html>
<head><title>PANIC: {{.RecoveredPanic}}</title></head>
<style type="text/css">
html, body {
font-family: Helvetica, Arial, Sans;
color: #333333;
background-color: #ffffff;
margin: 0px;
}
h1 {
color: #ffffff;
background-color: #f14c4c;
padding: 20px;
border-bottom: 1px solid #2b3848;
}
.block {
margin: 2em;
}
.panic-interface {
}
.panic-stack-raw pre {
padding: 1em;
background: #f6f8fa;
border: dashed 1px;
}
.panic-interface-title {
font-weight: bold;
}
</style>
<body>
<h1>Negroni - PANIC</h1>
<div class="panic-interface block">
<h3>{{.RequestDescription}}</h3>
<span class="panic-interface-title">Runtime error:</span> <span class="panic-interface-element">{{.RecoveredPanic}}</span>
</div>
{{ if .Stack }}
<div class="panic-stack-raw block">
<h3>Runtime Stack</h3>
<pre>{{.StackAsString}}</pre>
</div>
{{ end }}
</body>
</html>`
nilRequestMessage = "Request is nil"
)
var panicHTMLTemplate = template.Must(template.New("PanicPage").Parse(panicHTML))
// PanicInformation contains all
// elements for printing stack informations.
type PanicInformation struct {
RecoveredPanic interface{}
Stack []byte
Request *http.Request
}
// StackAsString returns a printable version of the stack
func (p *PanicInformation) StackAsString() string {
return string(p.Stack)
}
// RequestDescription returns a printable description of the url
func (p *PanicInformation) RequestDescription() string {
if p.Request == nil {
return nilRequestMessage
}
var queryOutput string
if p.Request.URL.RawQuery != "" {
queryOutput = "?" + p.Request.URL.RawQuery
}
return fmt.Sprintf("%s %s%s", p.Request.Method, p.Request.URL.Path, queryOutput)
}
// PanicFormatter is an interface on object can implement
// to be able to output the stack trace
type PanicFormatter interface {
// FormatPanicError output the stack for a given answer/response.
// In case the the middleware should not output the stack trace,
// the field `Stack` of the passed `PanicInformation` instance equals `[]byte{}`.
FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation)
}
// TextPanicFormatter output the stack
// as simple text on os.Stdout. If no `Content-Type` is set,
// it will output the data as `text/plain; charset=utf-8`.
// Otherwise, the origin `Content-Type` is kept.
type TextPanicFormatter struct{}
func (t *TextPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) {
if rw.Header().Get("Content-Type") == "" {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
}
fmt.Fprintf(rw, panicText, infos.RecoveredPanic, infos.Stack)
}
// HTMLPanicFormatter output the stack inside
// an HTML page. This has been largely inspired by
// https://github.com/go-martini/martini/pull/156/commits.
type HTMLPanicFormatter struct{}
func (t *HTMLPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) {
if rw.Header().Get("Content-Type") == "" {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
}
panicHTMLTemplate.Execute(rw, infos)
}
// Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one.
type Recovery struct {
Logger ALogger
PrintStack bool
ErrorHandlerFunc func(interface{})
StackAll bool
StackSize int
Formatter PanicFormatter
}
// NewRecovery returns a new instance of Recovery
func NewRecovery() *Recovery {
return &Recovery{
Logger: log.New(os.Stdout, "[negroni] ", 0),
PrintStack: true,
StackAll: false,
StackSize: 1024 * 8,
Formatter: &TextPanicFormatter{},
}
}
func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
defer func() {
if err := recover(); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
stack := make([]byte, rec.StackSize)
stack = stack[:runtime.Stack(stack, rec.StackAll)]
infos := &PanicInformation{RecoveredPanic: err, Request: r}
if rec.PrintStack {
infos.Stack = stack
}
rec.Logger.Printf(panicText, err, stack)
rec.Formatter.FormatPanicError(rw, r, infos)
if rec.ErrorHandlerFunc != nil {
func() {
defer func() {
if err := recover(); err != nil {
rec.Logger.Printf("provided ErrorHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack())
rec.Logger.Printf("%s\n", debug.Stack())
}
}()
rec.ErrorHandlerFunc(err)
}()
}
}
}()
next(rw, r)
}

View file

@ -1,113 +0,0 @@
package negroni
import (
"bufio"
"fmt"
"net"
"net/http"
)
// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
// the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
// if the functionality calls for it.
type ResponseWriter interface {
http.ResponseWriter
http.Flusher
// Status returns the status code of the response or 0 if the response has
// not been written
Status() int
// Written returns whether or not the ResponseWriter has been written.
Written() bool
// Size returns the size of the response body.
Size() int
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
Before(func(ResponseWriter))
}
type beforeFunc func(ResponseWriter)
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
nrw := &responseWriter{
ResponseWriter: rw,
}
if _, ok := rw.(http.CloseNotifier); ok {
return &responseWriterCloseNotifer{nrw}
}
return nrw
}
type responseWriter struct {
http.ResponseWriter
status int
size int
beforeFuncs []beforeFunc
}
func (rw *responseWriter) WriteHeader(s int) {
rw.status = s
rw.callBefore()
rw.ResponseWriter.WriteHeader(s)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
if !rw.Written() {
// The status will be StatusOK if WriteHeader has not been called yet
rw.WriteHeader(http.StatusOK)
}
size, err := rw.ResponseWriter.Write(b)
rw.size += size
return size, err
}
func (rw *responseWriter) Status() int {
return rw.status
}
func (rw *responseWriter) Size() int {
return rw.size
}
func (rw *responseWriter) Written() bool {
return rw.status != 0
}
func (rw *responseWriter) Before(before func(ResponseWriter)) {
rw.beforeFuncs = append(rw.beforeFuncs, before)
}
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := rw.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
}
return hijacker.Hijack()
}
func (rw *responseWriter) callBefore() {
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
rw.beforeFuncs[i](rw)
}
}
func (rw *responseWriter) Flush() {
flusher, ok := rw.ResponseWriter.(http.Flusher)
if ok {
if !rw.Written() {
// The status will be StatusOK if WriteHeader has not been called yet
rw.WriteHeader(http.StatusOK)
}
flusher.Flush()
}
}
type responseWriterCloseNotifer struct {
*responseWriter
}
func (rw *responseWriterCloseNotifer) CloseNotify() <-chan bool {
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

View file

@ -1,16 +0,0 @@
//+build go1.8
package negroni
import (
"fmt"
"net/http"
)
func (rw *responseWriter) Push(target string, opts *http.PushOptions) error {
pusher, ok := rw.ResponseWriter.(http.Pusher)
if ok {
return pusher.Push(target, opts)
}
return fmt.Errorf("the ResponseWriter doesn't support the Pusher interface")
}

View file

@ -1,88 +0,0 @@
package negroni
import (
"net/http"
"path"
"strings"
)
// Static is a middleware handler that serves static files in the given
// directory/filesystem. If the file does not exist on the filesystem, it
// passes along to the next middleware in the chain. If you desire "fileserver"
// type behavior where it returns a 404 for unfound files, you should consider
// using http.FileServer from the Go stdlib.
type Static struct {
// Dir is the directory to serve static files from
Dir http.FileSystem
// Prefix is the optional prefix used to serve the static directory content
Prefix string
// IndexFile defines which file to serve as index if it exists.
IndexFile string
}
// NewStatic returns a new instance of Static
func NewStatic(directory http.FileSystem) *Static {
return &Static{
Dir: directory,
Prefix: "",
IndexFile: "index.html",
}
}
func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if r.Method != "GET" && r.Method != "HEAD" {
next(rw, r)
return
}
file := r.URL.Path
// if we have a prefix, filter requests by stripping the prefix
if s.Prefix != "" {
if !strings.HasPrefix(file, s.Prefix) {
next(rw, r)
return
}
file = file[len(s.Prefix):]
if file != "" && file[0] != '/' {
next(rw, r)
return
}
}
f, err := s.Dir.Open(file)
if err != nil {
// discard the error?
next(rw, r)
return
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
next(rw, r)
return
}
// try to serve index file
if fi.IsDir() {
// redirect if missing trailing slash
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound)
return
}
file = path.Join(file, s.IndexFile)
f, err = s.Dir.Open(file)
if err != nil {
next(rw, r)
return
}
defer f.Close()
fi, err = f.Stat()
if err != nil || fi.IsDir() {
next(rw, r)
return
}
}
http.ServeContent(rw, r, file, fi.ModTime(), f)
}

21
vendor/github.com/dustin/go-humanize/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,21 @@
sudo: false
language: go
go:
- 1.3.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...

View file

@ -1,6 +1,4 @@
The MIT License (MIT)
Copyright (c) 2014 Jeremy Saenz
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -9,8 +7,8 @@ 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 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,
@ -19,3 +17,5 @@ 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.
<http://www.opensource.org/licenses/mit-license.php>

124
vendor/github.com/dustin/go-humanize/README.markdown generated vendored Normal file
View file

@ -0,0 +1,124 @@
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
Just a few functions for helping humanize times and sizes.
`go get` it as `github.com/dustin/go-humanize`, import it as
`"github.com/dustin/go-humanize"`, use it as `humanize`.
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
complete documentation.
## Sizes
This lets you take numbers like `82854982` and convert them to useful
strings like, `83 MB` or `79 MiB` (whichever you prefer).
Example:
```go
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
```
## Times
This lets you take a `time.Time` and spit it out in relative terms.
For example, `12 seconds ago` or `3 days from now`.
Example:
```go
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
```
Thanks to Kyle Lemons for the time implementation from an IRC
conversation one day. It's pretty neat.
## Ordinals
From a [mailing list discussion][odisc] where a user wanted to be able
to label ordinals.
0 -> 0th
1 -> 1st
2 -> 2nd
3 -> 3rd
4 -> 4th
[...]
Example:
```go
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
```
## Commas
Want to shove commas into numbers? Be my guest.
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Example:
```go
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
```
## Ftoa
Nicer float64 formatter that removes trailing zeros.
```go
fmt.Printf("%f", 2.24) // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0) // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
```
## SI notation
Format numbers with [SI notation][sinotation].
Example:
```go
humanize.SI(0.00000000223, "M") // 2.23 nM
```
## English-specific functions
The following functions are in the `humanize/english` subpackage.
### Plurals
Simple English pluralization
```go
english.PluralWord(1, "object", "") // object
english.PluralWord(42, "object", "") // objects
english.PluralWord(2, "bus", "") // buses
english.PluralWord(99, "locus", "loci") // loci
english.Plural(1, "object", "") // 1 object
english.Plural(42, "object", "") // 42 objects
english.Plural(2, "bus", "") // 2 buses
english.Plural(99, "locus", "loci") // 99 loci
```
### Word series
Format comma-separated words lists with conjuctions:
```go
english.WordSeries([]string{"foo"}, "and") // foo
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
```
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix

31
vendor/github.com/dustin/go-humanize/big.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package humanize
import (
"math/big"
)
// order of magnitude (to a max order)
func oomm(n, b *big.Int, maxmag int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
if mag == maxmag && maxmag >= 0 {
break
}
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}
// total order of magnitude
// (same as above, but with no upper limit)
func oom(n, b *big.Int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}

173
vendor/github.com/dustin/go-humanize/bigbytes.go generated vendored Normal file
View file

@ -0,0 +1,173 @@
package humanize
import (
"fmt"
"math/big"
"strings"
"unicode"
)
var (
bigIECExp = big.NewInt(1024)
// BigByte is one byte in bit.Ints
BigByte = big.NewInt(1)
// BigKiByte is 1,024 bytes in bit.Ints
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
// BigMiByte is 1,024 k bytes in bit.Ints
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
// BigGiByte is 1,024 m bytes in bit.Ints
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
// BigTiByte is 1,024 g bytes in bit.Ints
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
// BigPiByte is 1,024 t bytes in bit.Ints
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
// BigEiByte is 1,024 p bytes in bit.Ints
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
// BigZiByte is 1,024 e bytes in bit.Ints
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
// BigYiByte is 1,024 z bytes in bit.Ints
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
)
var (
bigSIExp = big.NewInt(1000)
// BigSIByte is one SI byte in big.Ints
BigSIByte = big.NewInt(1)
// BigKByte is 1,000 SI bytes in big.Ints
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
// BigMByte is 1,000 SI k bytes in big.Ints
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
// BigGByte is 1,000 SI m bytes in big.Ints
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
// BigTByte is 1,000 SI g bytes in big.Ints
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
// BigPByte is 1,000 SI t bytes in big.Ints
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
// BigEByte is 1,000 SI p bytes in big.Ints
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
// BigZByte is 1,000 SI e bytes in big.Ints
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
// BigYByte is 1,000 SI z bytes in big.Ints
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
)
var bigBytesSizeTable = map[string]*big.Int{
"b": BigByte,
"kib": BigKiByte,
"kb": BigKByte,
"mib": BigMiByte,
"mb": BigMByte,
"gib": BigGiByte,
"gb": BigGByte,
"tib": BigTiByte,
"tb": BigTByte,
"pib": BigPiByte,
"pb": BigPByte,
"eib": BigEiByte,
"eb": BigEByte,
"zib": BigZiByte,
"zb": BigZByte,
"yib": BigYiByte,
"yb": BigYByte,
// Without suffix
"": BigByte,
"ki": BigKiByte,
"k": BigKByte,
"mi": BigMiByte,
"m": BigMByte,
"gi": BigGiByte,
"g": BigGByte,
"ti": BigTiByte,
"t": BigTByte,
"pi": BigPiByte,
"p": BigPByte,
"ei": BigEiByte,
"e": BigEByte,
"z": BigZByte,
"zi": BigZiByte,
"y": BigYByte,
"yi": BigYiByte,
}
var ten = big.NewInt(10)
func humanateBigBytes(s, base *big.Int, sizes []string) string {
if s.Cmp(ten) < 0 {
return fmt.Sprintf("%d B", s)
}
c := (&big.Int{}).Set(s)
val, mag := oomm(c, base, len(sizes)-1)
suffix := sizes[mag]
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// BigBytes produces a human readable representation of an SI size.
//
// See also: ParseBigBytes.
//
// BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes)
}
// BigIBytes produces a human readable representation of an IEC size.
//
// See also: ParseBigBytes.
//
// BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes)
}
// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See also: BigBytes, BigIBytes.
//
// ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
val := &big.Rat{}
_, err := fmt.Sscanf(num, "%f", val)
if err != nil {
return nil, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bigBytesSizeTable[extra]; ok {
mv := (&big.Rat{}).SetInt(m)
val.Mul(val, mv)
rv := &big.Int{}
rv.Div(val.Num(), val.Denom())
return rv, nil
}
return nil, fmt.Errorf("unhandled size name: %v", extra)
}

143
vendor/github.com/dustin/go-humanize/bytes.go generated vendored Normal file
View file

@ -0,0 +1,143 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// Bytes produces a human readable representation of an SI size.
//
// See also: ParseBytes.
//
// Bytes(82854982) -> 83 MB
func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes)
}
// IBytes produces a human readable representation of an IEC size.
//
// See also: ParseBytes.
//
// IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes)
}
// ParseBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See Also: Bytes, IBytes.
//
// ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bytesSizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

116
vendor/github.com/dustin/go-humanize/comma.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package humanize
import (
"bytes"
"math"
"math/big"
"strconv"
"strings"
)
// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 {
sign = "-"
v = 0 - v
}
parts := []string{"", "", "", "", "", "", ""}
j := len(parts) - 1
for v > 999 {
parts[j] = strconv.FormatInt(v%1000, 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
v = v / 1000
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:], ",")
}
// Commaf produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Commaf(834142.32) -> 834,142.32
func Commaf(v float64) string {
buf := &bytes.Buffer{}
if v < 0 {
buf.Write([]byte{'-'})
v = 0 - v
}
comma := []byte{','}
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
// CommafWithDigits works like the Commaf but limits the resulting
// string to the given number of decimal places.
//
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
func CommafWithDigits(f float64, decimals int) string {
return stripTrailingDigits(Commaf(f), decimals)
}
// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
sign := ""
if b.Sign() < 0 {
sign = "-"
b.Abs(b)
}
athousand := big.NewInt(1000)
c := (&big.Int{}).Set(b)
_, m := oom(c, athousand)
parts := make([]string, m+1)
j := len(parts) - 1
mod := &big.Int{}
for b.Cmp(athousand) >= 0 {
b.DivMod(b, athousand, mod)
parts[j] = strconv.FormatInt(mod.Int64(), 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
j--
}
parts[j] = strconv.Itoa(int(b.Int64()))
return sign + strings.Join(parts[j:], ",")
}

40
vendor/github.com/dustin/go-humanize/commaf.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
// +build go1.6
package humanize
import (
"bytes"
"math/big"
"strings"
)
// BigCommaf produces a string form of the given big.Float in base 10
// with commas after every three orders of magnitude.
func BigCommaf(v *big.Float) string {
buf := &bytes.Buffer{}
if v.Sign() < 0 {
buf.Write([]byte{'-'})
v.Abs(v)
}
comma := []byte{','}
parts := strings.Split(v.Text('f', -1), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}

46
vendor/github.com/dustin/go-humanize/ftoa.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
package humanize
import (
"strconv"
"strings"
)
func stripTrailingZeros(s string) string {
offset := len(s) - 1
for offset > 0 {
if s[offset] == '.' {
offset--
break
}
if s[offset] != '0' {
break
}
offset--
}
return s[:offset+1]
}
func stripTrailingDigits(s string, digits int) string {
if i := strings.Index(s, "."); i >= 0 {
if digits <= 0 {
return s[:i]
}
i++
if i+digits >= len(s) {
return s
}
return s[:i+digits]
}
return s
}
// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
}
// FtoaWithDigits converts a float to a string but limits the resulting string
// to the given number of decimal places, and no trailing zeros.
func FtoaWithDigits(num float64, digits int) string {
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
}

8
vendor/github.com/dustin/go-humanize/humanize.go generated vendored Normal file
View file

@ -0,0 +1,8 @@
/*
Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83 MB" or
"79 MiB" (whichever you prefer).
*/
package humanize

192
vendor/github.com/dustin/go-humanize/number.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
package humanize
/*
Slightly adapted from the source to fit go-humanize.
Author: https://github.com/gorhill
Source: https://gist.github.com/gorhill/5285193
*/
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < -math.MaxFloat64 {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}

25
vendor/github.com/dustin/go-humanize/ordinals.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
package humanize
import "strconv"
// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
suffix := "th"
switch x % 10 {
case 1:
if x%100 != 11 {
suffix = "st"
}
case 2:
if x%100 != 12 {
suffix = "nd"
}
case 3:
if x%100 != 13 {
suffix = "rd"
}
}
return strconv.Itoa(x) + suffix
}

123
vendor/github.com/dustin/go-humanize/si.go generated vendored Normal file
View file

@ -0,0 +1,123 @@
package humanize
import (
"errors"
"math"
"regexp"
"strconv"
)
var siPrefixTable = map[float64]string{
-24: "y", // yocto
-21: "z", // zepto
-18: "a", // atto
-15: "f", // femto
-12: "p", // pico
-9: "n", // nano
-6: "µ", // micro
-3: "m", // milli
0: "",
3: "k", // kilo
6: "M", // mega
9: "G", // giga
12: "T", // tera
15: "P", // peta
18: "E", // exa
21: "Z", // zetta
24: "Y", // yotta
}
var revSIPrefixTable = revfmap(siPrefixTable)
// revfmap reverses the map and precomputes the power multiplier
func revfmap(in map[float64]string) map[string]float64 {
rv := map[string]float64{}
for k, v := range in {
rv[v] = math.Pow(10, k)
}
return rv
}
var riParseRegex *regexp.Regexp
func init() {
ri := `^([\-0-9.]+)\s?([`
for _, v := range siPrefixTable {
ri += v
}
ri += `]?)(.*)`
riParseRegex = regexp.MustCompile(ri)
}
// ComputeSI finds the most appropriate SI prefix for the given number
// and returns the prefix along with the value adjusted to be within
// that prefix.
//
// See also: SI, ParseSI.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
if input == 0 {
return 0, ""
}
mag := math.Abs(input)
exponent := math.Floor(logn(mag, 10))
exponent = math.Floor(exponent/3) * 3
value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0
// Should return 1 M instead of 1000 k
if value == 1000.0 {
exponent += 3
value = mag / math.Pow(10, exponent)
}
value = math.Copysign(value, input)
prefix := siPrefixTable[exponent]
return value, prefix
}
// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// See also: ComputeSI, ParseSI.
//
// e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string {
value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit
}
// SIWithDigits works like SI but limits the resulting string to the
// given number of decimal places.
//
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
func SIWithDigits(input float64, decimals int, unit string) string {
value, prefix := ComputeSI(input)
return FtoaWithDigits(value, decimals) + " " + prefix + unit
}
var errInvalid = errors.New("invalid input")
// ParseSI parses an SI string back into the number and unit.
//
// See also: SI, ComputeSI.
//
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 {
return 0, "", errInvalid
}
mag := revSIPrefixTable[found[2]]
unit := found[3]
base, err := strconv.ParseFloat(found[1], 64)
return base * mag, unit, err
}

117
vendor/github.com/dustin/go-humanize/times.go generated vendored Normal file
View file

@ -0,0 +1,117 @@
package humanize
import (
"fmt"
"math"
"sort"
"time"
)
// Seconds-based time units
const (
Day = 24 * time.Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
LongTime = 37 * Year
)
// Time formats a time into a relative string.
//
// Time(someT) -> "3 weeks ago"
func Time(then time.Time) string {
return RelTime(then, time.Now(), "ago", "from now")
}
// A RelTimeMagnitude struct contains a relative time point at which
// the relative format of time will switch to a new format string. A
// slice of these in ascending order by their "D" field is passed to
// CustomRelTime to format durations.
//
// The Format field is a string that may contain a "%s" which will be
// replaced with the appropriate signed label (e.g. "ago" or "from
// now") and a "%d" that will be replaced by the quantity.
//
// The DivBy field is the amount of time the time difference must be
// divided by in order to display correctly.
//
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
// DivBy should be time.Minute so whatever the duration is will be
// expressed in minutes.
type RelTimeMagnitude struct {
D time.Duration
Format string
DivBy time.Duration
}
var defaultMagnitudes = []RelTimeMagnitude{
{time.Second, "now", time.Second},
{2 * time.Second, "1 second %s", 1},
{time.Minute, "%d seconds %s", time.Second},
{2 * time.Minute, "1 minute %s", 1},
{time.Hour, "%d minutes %s", time.Minute},
{2 * time.Hour, "1 hour %s", 1},
{Day, "%d hours %s", time.Hour},
{2 * Day, "1 day %s", 1},
{Week, "%d days %s", Day},
{2 * Week, "1 week %s", 1},
{Month, "%d weeks %s", Week},
{2 * Month, "1 month %s", 1},
{Year, "%d months %s", Month},
{18 * Month, "1 year %s", 1},
{2 * Year, "2 years %s", 1},
{LongTime, "%d years %s", Year},
{math.MaxInt64, "a long while %s", 1},
}
// RelTime formats a time into a relative string.
//
// It takes two times and two labels. In addition to the generic time
// delta string (e.g. 5 minutes), the labels are used applied so that
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
}
// CustomRelTime formats a time into a relative string.
//
// It takes two times two labels and a table of relative time formats.
// In addition to the generic time delta string (e.g. 5 minutes), the
// labels are used applied so that the label corresponding to the
// smaller time is applied.
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
lbl := albl
diff := b.Sub(a)
if a.After(b) {
lbl = blbl
diff = a.Sub(b)
}
n := sort.Search(len(magnitudes), func(i int) bool {
return magnitudes[i].D > diff
})
if n >= len(magnitudes) {
n = len(magnitudes) - 1
}
mag := magnitudes[n]
args := []interface{}{}
escaped := false
for _, ch := range mag.Format {
if escaped {
switch ch {
case 's':
args = append(args, lbl)
case 'd':
args = append(args, diff/mag.DivBy)
}
escaped = false
} else {
escaped = ch == '%'
}
}
return fmt.Sprintf(mag.Format, args...)
}

3
vendor/github.com/go-chi/chi/.gitignore generated vendored Normal file
View file

@ -0,0 +1,3 @@
.idea
*.sw?
.vscode

18
vendor/github.com/go-chi/chi/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,18 @@
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
script:
- go get -d -t ./...
- go vet ./...
- go test ./...
- >
go_version=$(go version);
if [ ${go_version:13:4} = "1.12" ]; then
go get -u golang.org/x/tools/cmd/goimports;
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
fi

139
vendor/github.com/go-chi/chi/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,139 @@
# Changelog
## v4.0.0 (2019-01-10)
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8
- router: respond with 404 on router with no routes (#362)
- router: additional check to ensure wildcard is at the end of a url pattern (#333)
- middleware: deprecate use of http.CloseNotifier (#347)
- middleware: fix RedirectSlashes to include query params on redirect (#334)
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0
## v3.3.4 (2019-01-07)
- Minor middleware improvements. No changes to core library/router. Moving v3 into its
- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4
## v3.3.3 (2018-08-27)
- Minor release
- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3
## v3.3.2 (2017-12-22)
- Support to route trailing slashes on mounted sub-routers (#281)
- middleware: new `ContentCharset` to check matching charsets. Thank you
@csucu for your community contribution!
## v3.3.1 (2017-11-20)
- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types
- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value
- Minor bug fixes
## v3.3.0 (2017-10-10)
- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage
- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function
## v3.2.1 (2017-08-31)
- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface
and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path
- Add new `RouteMethod` to `*Context`
- Add new `Routes` pointer to `*Context`
- Add new `middleware.GetHead` to route missing HEAD requests to GET handler
- Updated benchmarks (see README)
## v3.1.5 (2017-08-02)
- Setup golint and go vet for the project
- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler`
to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler`
## v3.1.0 (2017-07-10)
- Fix a few minor issues after v3 release
- Move `docgen` sub-pkg to https://github.com/go-chi/docgen
- Move `render` sub-pkg to https://github.com/go-chi/render
- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime
suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in
https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage.
## v3.0.0 (2017-06-21)
- Major update to chi library with many exciting updates, but also some *breaking changes*
- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as
`/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the
same router
- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example:
`r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")`
- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as
`r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like
in `_examples/custom-handler`
- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their
own using file handler with the stdlib, see `_examples/fileserver` for an example
- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()`
- Moved the chi project to its own organization, to allow chi-related community packages to
be easily discovered and supported, at: https://github.com/go-chi
- *NOTE:* please update your import paths to `"github.com/go-chi/chi"`
- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2
## v2.1.0 (2017-03-30)
- Minor improvements and update to the chi core library
- Introduced a brand new `chi/render` sub-package to complete the story of building
APIs to offer a pattern for managing well-defined request / response payloads. Please
check out the updated `_examples/rest` example for how it works.
- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface
## v2.0.0 (2017-01-06)
- After many months of v2 being in an RC state with many companies and users running it in
production, the inclusion of some improvements to the middlewares, we are very pleased to
announce v2.0.0 of chi.
## v2.0.0-rc1 (2016-07-26)
- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular
community `"net/context"` package has been included in the standard library as `"context"` and
utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other
request-scoped values. We're very excited about the new context addition and are proud to
introduce chi v2, a minimal and powerful routing package for building large HTTP services,
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of
stdlib HTTP handlers and middlwares.
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,
which provides direct access to URL routing parameters, the routing path and the matching
routing patterns.
- Users upgrading from chi v1 to v2, need to:
1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to
the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)`
2. Use `chi.URLParam(r *http.Request, paramKey string) string`
or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value
## v1.0.0 (2016-07-01)
- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older.
## v0.9.0 (2016-03-31)
- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33)
- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters
has changed to: `chi.URLParam(ctx, "id")`

31
vendor/github.com/go-chi/chi/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,31 @@
# Contributing
## Prerequisites
1. [Install Go][go-install].
2. Download the sources and switch the working directory:
```bash
go get -u -d github.com/go-chi/chi
cd $GOPATH/src/github.com/go-chi/chi
```
## Submitting a Pull Request
A typical workflow is:
1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip]
2. [Create a topic branch.][branch]
3. Add tests for your change.
4. Run `go test`. If your tests pass, return to the step 3.
5. Implement the change and ensure the steps from the previous step pass.
6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline.
7. [Add, commit and push your changes.][git-help]
8. [Submit a pull request.][pull-req]
[go-install]: https://golang.org/doc/install
[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html
[fork]: https://help.github.com/articles/fork-a-repo
[branch]: http://learn.github.com/p/branching.html
[git-help]: https://guides.github.com
[pull-req]: https://help.github.com/articles/using-pull-requests

20
vendor/github.com/go-chi/chi/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.
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.

438
vendor/github.com/go-chi/chi/README.md generated vendored Normal file
View file

@ -0,0 +1,438 @@
# <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" />
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's
especially good at helping you write large REST API services that are kept maintainable as your
project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to
handle signaling, cancelation and request-scoped values across a handler chain.
The focus of the project has been to seek out an elegant and comfortable design for writing
REST API servers, written during the development of the Pressly API service that powers our
public API service, which in turn powers all of our client-side applications.
The key considerations of chi's design are: project structure, maintainability, standard http
handlers (stdlib-only), developer productivity, and deconstructing a large system into many small
parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
## Install
`go get -u github.com/go-chi/chi`
## Features
* **Lightweight** - cloc'd in ~1000 LOC for the chi router
* **Fast** - yes, see [benchmarks](#benchmarks)
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
* **No external dependencies** - plain ol' Go stdlib + net/http
## Examples
See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples.
**As easy as:**
```go
package main
import (
"net/http"
"github.com/go-chi/chi"
)
func main() {
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
}
```
**REST Preview:**
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed
above, they will show you all the features of chi and serve as a good form of documentation.
```go
import (
//...
"context"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
r := chi.NewRouter()
// A good base middleware stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
r.Use(middleware.Timeout(60 * time.Second))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
// RESTy routes for "articles" resource
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles) // GET /articles
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
r.Post("/", createArticle) // POST /articles
r.Get("/search", searchArticles) // GET /articles/search
// Regexp url parameters:
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// Subrouters:
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle) // GET /articles/123
r.Put("/", updateArticle) // PUT /articles/123
r.Delete("/", deleteArticle) // DELETE /articles/123
})
})
// Mount the admin sub-router
r.Mount("/admin", adminRouter())
http.ListenAndServe(":3333", r)
}
func ArticleCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
articleID := chi.URLParam(r, "articleID")
article, err := dbGetArticle(articleID)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "article", article)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getArticle(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
article, ok := ctx.Value("article").(*Article)
if !ok {
http.Error(w, http.StatusText(422), 422)
return
}
w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}
// A completely separate router for administrator routes
func adminRouter() http.Handler {
r := chi.NewRouter()
r.Use(AdminOnly)
r.Get("/", adminIndex)
r.Get("/accounts", adminListAccounts)
return r
}
func AdminOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
perm, ok := ctx.Value("acl.permission").(YourPermissionType)
if !ok || !perm.IsAdmin() {
http.Error(w, http.StatusText(403), 403)
return
}
next.ServeHTTP(w, r)
})
}
```
## Router design
chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree).
The router is fully compatible with `net/http`.
Built on top of the tree is the `Router` interface:
```go
// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
http.Handler
Routes
// Use appends one of more middlewares onto the Router stack.
Use(middlewares ...func(http.Handler) http.Handler)
// With adds inline middlewares for an endpoint handler.
With(middlewares ...func(http.Handler) http.Handler) Router
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// Route mounts a sub-Router along a `pattern`` string.
Route(pattern string, fn func(r Router)) Router
// Mount attaches another http.Handler along ./pattern/*
Mount(pattern string, h http.Handler)
// Handle and HandleFunc adds routes for `pattern` that matches
// all HTTP methods.
Handle(pattern string, h http.Handler)
HandleFunc(pattern string, h http.HandlerFunc)
// Method and MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
Method(method, pattern string, h http.Handler)
MethodFunc(method, pattern string, h http.HandlerFunc)
// HTTP-method routing along `pattern`
Connect(pattern string, h http.HandlerFunc)
Delete(pattern string, h http.HandlerFunc)
Get(pattern string, h http.HandlerFunc)
Head(pattern string, h http.HandlerFunc)
Options(pattern string, h http.HandlerFunc)
Patch(pattern string, h http.HandlerFunc)
Post(pattern string, h http.HandlerFunc)
Put(pattern string, h http.HandlerFunc)
Trace(pattern string, h http.HandlerFunc)
// NotFound defines a handler to respond whenever a route could
// not be found.
NotFound(h http.HandlerFunc)
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
MethodNotAllowed(h http.HandlerFunc)
}
// Routes interface adds two methods for router traversal, which is also
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
type Routes interface {
// Routes returns the routing tree in an easily traversable structure.
Routes() []Route
// Middlewares returns the list of middlewares in use by the router.
Middlewares() Middlewares
// Match searches the routing tree for a handler that matches
// the method/path - similar to routing a http request, but without
// executing the handler thereafter.
Match(rctx *Context, method, path string) bool
}
```
Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern
supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters
can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters
and `chi.URLParam(r, "*")` for a wildcard parameter.
### Middleware handlers
chi's middlewares are just stdlib net/http middleware handlers. There is nothing special
about them, which means the router and all the tooling is designed to be compatible and
friendly with any middleware in the community. This offers much better extensibility and reuse
of packages and is at the heart of chi's purpose.
Here is an example of a standard net/http middleware handler using the new request context
available in Go. This middleware sets a hypothetical user identifier on the request
context and calls the next handler in the chain.
```go
// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "user", "123")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```
### Request handlers
chi uses standard net/http request handlers. This little snippet is an example of a http.Handler
func that reads a user identifier from the request context - hypothetically, identifying
the user sending an authenticated request, validated+set by a previous middleware handler.
```go
// HTTP handler accessing data from the request context.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(string)
w.Write([]byte(fmt.Sprintf("hi %s", user)))
}
```
### URL parameters
chi's router parses and stores URL parameters right onto the request context. Here is
an example of how to access URL params in your net/http handlers. And of course, middlewares
are able to access the same information.
```go
// HTTP handler accessing the url routing parameters.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID") // from a route like /users/{userID}
ctx := r.Context()
key := ctx.Value("key").(string)
w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}
```
## Middlewares
chi comes equipped with an optional `middleware` package, providing a suite of standard
`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible
with `net/http` can be used with chi's mux.
### Core middlewares
-----------------------------------------------------------------------------------------------------------
| chi/middleware Handler | description |
|:----------------------|:---------------------------------------------------------------------------------
| AllowContentType | Explicit whitelist of accepted request Content-Types |
| Compress | Gzip compression for clients that accept compressed responses |
| GetHead | Automatically route undefined HEAD requests to GET handlers |
| Heartbeat | Monitoring endpoint to check the servers pulse |
| Logger | Logs the start and end of each request with the elapsed processing time |
| NoCache | Sets response headers to prevent clients from caching |
| Profiler | Easily attach net/http/pprof to your routers |
| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP |
| Recoverer | Gracefully absorb panics and prints the stack trace |
| RequestID | Injects a request ID into the context of each request |
| RedirectSlashes | Redirect slashes on routing paths |
| SetHeader | Short-hand middleware to set a response header key/value |
| StripSlashes | Strip slashes on routing paths |
| Throttle | Puts a ceiling on the number of concurrent requests |
| Timeout | Signals to the request context when the timeout deadline is reached |
| URLFormat | Parse extension from url and put it on request context |
| WithValue | Short-hand middleware to set a key/value on the request context |
-----------------------------------------------------------------------------------------------------------
### Auxiliary middlewares & packages
Please see https://github.com/go-chi for additional packages.
--------------------------------------------------------------------------------------------------------------------
| package | description |
|:---------------------------------------------------|:-------------------------------------------------------------
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime |
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin |
| [phi](https://github.com/fate-lovely/phi) | Port chi to [fasthttp](https://github.com/valyala/fasthttp) |
--------------------------------------------------------------------------------------------------------------------
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
## context?
`context` is a tiny pkg that provides simple interface to signal context across call stacks
and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani)
and is available in stdlib since go1.7.
Learn more at https://blog.golang.org/context
and..
* Docs: https://golang.org/pkg/context
* Source: https://github.com/golang/go/tree/master/src/context
## Benchmarks
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
```shell
BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op
BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op
BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op
BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op
```
Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
NOTE: the allocs in the benchmark above are from the calls to http.Request's
`WithContext(context.Context)` method that clones the http.Request, sets the `Context()`
on the duplicated (alloc'd) request and returns it the new request object. This is just
how setting context on a request in Go works.
## Credits
* Carl Jackson for https://github.com/zenazn/goji
* Parts of chi's thinking comes from goji, and chi's middleware package
sources from goji.
* Armon Dadgar for https://github.com/armon/go-radix
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
We'll be more than happy to see [your contributions](./CONTRIBUTING.md)!
## Beyond REST
chi is just a http router that lets you decompose request handling into many smaller layers.
Many companies including Pressly.com (of course) use chi to write REST services for their public
APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces
required to write a complete client-server system or network of microservices.
Looking ahead beyond REST, I also recommend some newer works in the field coming from
[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit)
and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their
own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server
communication feel like a single program on a single computer, no need to hand-write a client library
and the request/response payloads are typed contracts. NATS is pretty amazing too as a super
fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery -
an excellent combination with gRPC.
## License
Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
Licensed under [MIT License](./LICENSE)
[GoDoc]: https://godoc.org/github.com/go-chi/chi
[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg
[Travis]: https://travis-ci.org/go-chi/chi
[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master

49
vendor/github.com/go-chi/chi/chain.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package chi
import "net/http"
// Chain returns a Middlewares type from a slice of middleware handlers.
func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares {
return Middlewares(middlewares)
}
// Handler builds and returns a http.Handler from the chain of middlewares,
// with `h http.Handler` as the final handler.
func (mws Middlewares) Handler(h http.Handler) http.Handler {
return &ChainHandler{mws, h, chain(mws, h)}
}
// HandlerFunc builds and returns a http.Handler from the chain of middlewares,
// with `h http.Handler` as the final handler.
func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler {
return &ChainHandler{mws, h, chain(mws, h)}
}
// ChainHandler is a http.Handler with support for handler composition and
// execution.
type ChainHandler struct {
Middlewares Middlewares
Endpoint http.Handler
chain http.Handler
}
func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.chain.ServeHTTP(w, r)
}
// chain builds a http.Handler composed of an inline middleware stack and endpoint
// handler in the order they are passed.
func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler {
// Return ahead of time if there aren't any middlewares for the chain
if len(middlewares) == 0 {
return endpoint
}
// Wrap the end handler with the middleware chain
h := middlewares[len(middlewares)-1](endpoint)
for i := len(middlewares) - 2; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}

134
vendor/github.com/go-chi/chi/chi.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
//
// Package chi is a small, idiomatic and composable router for building HTTP services.
//
// chi requires Go 1.7 or newer.
//
// Example:
// package main
//
// import (
// "net/http"
//
// "github.com/go-chi/chi"
// "github.com/go-chi/chi/middleware"
// )
//
// func main() {
// r := chi.NewRouter()
// r.Use(middleware.Logger)
// r.Use(middleware.Recoverer)
//
// r.Get("/", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("root."))
// })
//
// http.ListenAndServe(":3333", r)
// }
//
// See github.com/go-chi/chi/_examples/ for more in-depth examples.
//
// URL patterns allow for easy matching of path components in HTTP
// requests. The matching components can then be accessed using
// chi.URLParam(). All patterns must begin with a slash.
//
// A simple named placeholder {name} matches any sequence of characters
// up to the next / or the end of the URL. Trailing slashes on paths must
// be handled explicitly.
//
// A placeholder with a name followed by a colon allows a regular
// expression match, for example {number:\\d+}. The regular expression
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
// including { or } are not supported, and / will never be
// matched. An anonymous regexp pattern is allowed, using an empty string
// before the colon in the placeholder, such as {:\\d+}
//
// The special placeholder of asterisk matches the rest of the requested
// URL. Any trailing characters in the pattern are ignored. This is the only
// placeholder which will match / characters.
//
// Examples:
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
// "/user/{name}/info" matches "/user/jsmith/info"
// "/page/*" matches "/page/intro/latest"
// "/page/*/index" also matches "/page/intro/latest"
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
//
package chi
import "net/http"
// NewRouter returns a new Mux object that implements the Router interface.
func NewRouter() *Mux {
return NewMux()
}
// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
http.Handler
Routes
// Use appends one of more middlewares onto the Router stack.
Use(middlewares ...func(http.Handler) http.Handler)
// With adds inline middlewares for an endpoint handler.
With(middlewares ...func(http.Handler) http.Handler) Router
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// Route mounts a sub-Router along a `pattern`` string.
Route(pattern string, fn func(r Router)) Router
// Mount attaches another http.Handler along ./pattern/*
Mount(pattern string, h http.Handler)
// Handle and HandleFunc adds routes for `pattern` that matches
// all HTTP methods.
Handle(pattern string, h http.Handler)
HandleFunc(pattern string, h http.HandlerFunc)
// Method and MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
Method(method, pattern string, h http.Handler)
MethodFunc(method, pattern string, h http.HandlerFunc)
// HTTP-method routing along `pattern`
Connect(pattern string, h http.HandlerFunc)
Delete(pattern string, h http.HandlerFunc)
Get(pattern string, h http.HandlerFunc)
Head(pattern string, h http.HandlerFunc)
Options(pattern string, h http.HandlerFunc)
Patch(pattern string, h http.HandlerFunc)
Post(pattern string, h http.HandlerFunc)
Put(pattern string, h http.HandlerFunc)
Trace(pattern string, h http.HandlerFunc)
// NotFound defines a handler to respond whenever a route could
// not be found.
NotFound(h http.HandlerFunc)
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
MethodNotAllowed(h http.HandlerFunc)
}
// Routes interface adds two methods for router traversal, which is also
// used by the `docgen` subpackage to generation documentation for Routers.
type Routes interface {
// Routes returns the routing tree in an easily traversable structure.
Routes() []Route
// Middlewares returns the list of middlewares in use by the router.
Middlewares() Middlewares
// Match searches the routing tree for a handler that matches
// the method/path - similar to routing a http request, but without
// executing the handler thereafter.
Match(rctx *Context, method, path string) bool
}
// Middlewares type is a slice of standard middleware handlers with methods
// to compose middleware chains and http.Handler's.
type Middlewares []func(http.Handler) http.Handler

161
vendor/github.com/go-chi/chi/context.go generated vendored Normal file
View file

@ -0,0 +1,161 @@
package chi
import (
"context"
"net"
"net/http"
"strings"
)
var (
// RouteCtxKey is the context.Context key to store the request context.
RouteCtxKey = &contextKey{"RouteContext"}
)
// Context is the default routing context set on the root node of a
// request context to track route patterns, URL parameters and
// an optional routing path.
type Context struct {
Routes Routes
// Routing path/method override used during the route search.
// See Mux#routeHTTP method.
RoutePath string
RouteMethod string
// Routing pattern stack throughout the lifecycle of the request,
// across all connected routers. It is a record of all matching
// patterns across a stack of sub-routers.
RoutePatterns []string
// URLParams are the stack of routeParams captured during the
// routing lifecycle across a stack of sub-routers.
URLParams RouteParams
// The endpoint routing pattern that matched the request URI path
// or `RoutePath` of the current sub-router. This value will update
// during the lifecycle of a request passing through a stack of
// sub-routers.
routePattern string
// Route parameters matched for the current sub-router. It is
// intentionally unexported so it cant be tampered.
routeParams RouteParams
// methodNotAllowed hint
methodNotAllowed bool
}
// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
return &Context{}
}
// Reset a routing context to its initial state.
func (x *Context) Reset() {
x.Routes = nil
x.RoutePath = ""
x.RouteMethod = ""
x.RoutePatterns = x.RoutePatterns[:0]
x.URLParams.Keys = x.URLParams.Keys[:0]
x.URLParams.Values = x.URLParams.Values[:0]
x.routePattern = ""
x.routeParams.Keys = x.routeParams.Keys[:0]
x.routeParams.Values = x.routeParams.Values[:0]
x.methodNotAllowed = false
}
// URLParam returns the corresponding URL parameter value from the request
// routing context.
func (x *Context) URLParam(key string) string {
for k := len(x.URLParams.Keys) - 1; k >= 0; k-- {
if x.URLParams.Keys[k] == key {
return x.URLParams.Values[k]
}
}
return ""
}
// RoutePattern builds the routing pattern string for the particular
// request, at the particular point during routing. This means, the value
// will change throughout the execution of a request in a router. That is
// why its advised to only use this value after calling the next handler.
//
// For example,
//
// func Instrument(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// next.ServeHTTP(w, r)
// routePattern := chi.RouteContext(r.Context()).RoutePattern()
// measure(w, r, routePattern)
// })
// }
func (x *Context) RoutePattern() string {
routePattern := strings.Join(x.RoutePatterns, "")
return strings.Replace(routePattern, "/*/", "/", -1)
}
// RouteContext returns chi's routing Context object from a
// http.Request Context.
func RouteContext(ctx context.Context) *Context {
return ctx.Value(RouteCtxKey).(*Context)
}
// URLParam returns the url parameter from a http.Request object.
func URLParam(r *http.Request, key string) string {
if rctx := RouteContext(r.Context()); rctx != nil {
return rctx.URLParam(key)
}
return ""
}
// URLParamFromCtx returns the url parameter from a http.Request Context.
func URLParamFromCtx(ctx context.Context, key string) string {
if rctx := RouteContext(ctx); rctx != nil {
return rctx.URLParam(key)
}
return ""
}
// RouteParams is a structure to track URL routing parameters efficiently.
type RouteParams struct {
Keys, Values []string
}
// Add will append a URL parameter to the end of the route param
func (s *RouteParams) Add(key, value string) {
(*s).Keys = append((*s).Keys, key)
(*s).Values = append((*s).Values, value)
}
// ServerBaseContext wraps an http.Handler to set the request context to the
// `baseCtx`.
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
baseCtx := baseCtx
// Copy over default net/http server context keys
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
}
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
}
h.ServeHTTP(w, r.WithContext(baseCtx))
})
return fn
}
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "chi context value " + k.name
}

275
vendor/github.com/go-chi/chi/middleware/compress.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
package middleware
import (
"bufio"
"compress/flate"
"compress/gzip"
"errors"
"io"
"net"
"net/http"
"strings"
)
var encoders = map[string]EncoderFunc{}
var encodingPrecedence = []string{"br", "gzip", "deflate"}
func init() {
// TODO:
// lzma: Opera.
// sdch: Chrome, Android. Gzip output + dictionary header.
// br: Brotli, see https://github.com/go-chi/chi/pull/326
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
SetEncoder("gzip", encoderGzip)
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
// checksum compared to CRC-32 used in "gzip" and thus is faster.
//
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
// raw DEFLATE data only, without the mentioned zlib wrapper.
// Because of this major confusion, most modern browsers try it
// both ways, first looking for zlib headers.
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
//
// The list of browsers having problems is quite big, see:
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
//
// That's why we prefer gzip over deflate. It's just more reliable
// and not significantly slower than gzip.
SetEncoder("deflate", encoderDeflate)
// NOTE: Not implemented, intentionally:
// case "compress": // LZW. Deprecated.
// case "bzip2": // Too slow on-the-fly.
// case "zopfli": // Too slow on-the-fly.
// case "xz": // Too slow on-the-fly.
}
// An EncoderFunc is a function that wraps the provided ResponseWriter with a
// streaming compression algorithm and returns it.
//
// In case of failure, the function should return nil.
type EncoderFunc func(w http.ResponseWriter, level int) io.Writer
// SetEncoder can be used to set the implementation of a compression algorithm.
//
// The encoding should be a standardised identifier. See:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
//
// For example, add the Brotli algortithm:
//
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
//
// middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
// params := brotli_enc.NewBrotliParams()
// params.SetQuality(level)
// return brotli_enc.NewBrotliWriter(params, w)
// })
func SetEncoder(encoding string, fn EncoderFunc) {
encoding = strings.ToLower(encoding)
if encoding == "" {
panic("the encoding can not be empty")
}
if fn == nil {
panic("attempted to set a nil encoder function")
}
encoders[encoding] = fn
var e string
for _, v := range encodingPrecedence {
if v == encoding {
e = v
}
}
if e == "" {
encodingPrecedence = append([]string{e}, encodingPrecedence...)
}
}
var defaultContentTypes = map[string]struct{}{
"text/html": {},
"text/css": {},
"text/plain": {},
"text/javascript": {},
"application/javascript": {},
"application/x-javascript": {},
"application/json": {},
"application/atom+xml": {},
"application/rss+xml": {},
"image/svg+xml": {},
}
// DefaultCompress is a middleware that compresses response
// body of predefined content types to a data format based
// on Accept-Encoding request header. It uses a default
// compression level.
func DefaultCompress(next http.Handler) http.Handler {
return Compress(flate.DefaultCompression)(next)
}
// Compress is a middleware that compresses response
// body of a given content types to a data format based
// on Accept-Encoding request header. It uses a given
// compression level.
//
// NOTE: make sure to set the Content-Type header on your response
// otherwise this middleware will not compress the response body. For ex, in
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
// or set it manually.
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
contentTypes := defaultContentTypes
if len(types) > 0 {
contentTypes = make(map[string]struct{}, len(types))
for _, t := range types {
contentTypes[t] = struct{}{}
}
}
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
encoder, encoding := selectEncoder(r.Header)
cw := &compressResponseWriter{
ResponseWriter: w,
w: w,
contentTypes: contentTypes,
encoder: encoder,
encoding: encoding,
level: level,
}
defer cw.Close()
next.ServeHTTP(cw, r)
}
return http.HandlerFunc(fn)
}
}
func selectEncoder(h http.Header) (EncoderFunc, string) {
header := h.Get("Accept-Encoding")
// Parse the names of all accepted algorithms from the header.
accepted := strings.Split(strings.ToLower(header), ",")
// Find supported encoder by accepted list by precedence
for _, name := range encodingPrecedence {
if fn, ok := encoders[name]; ok && matchAcceptEncoding(accepted, name) {
return fn, name
}
}
// No encoder found to match the accepted encoding
return nil, ""
}
func matchAcceptEncoding(accepted []string, encoding string) bool {
for _, v := range accepted {
if strings.Index(v, encoding) >= 0 {
return true
}
}
return false
}
type compressResponseWriter struct {
http.ResponseWriter
w io.Writer
encoder EncoderFunc
encoding string
contentTypes map[string]struct{}
level int
wroteHeader bool
}
func (cw *compressResponseWriter) WriteHeader(code int) {
if cw.wroteHeader {
return
}
cw.wroteHeader = true
defer cw.ResponseWriter.WriteHeader(code)
// Already compressed data?
if cw.Header().Get("Content-Encoding") != "" {
return
}
// Parse the first part of the Content-Type response header.
contentType := ""
parts := strings.Split(cw.Header().Get("Content-Type"), ";")
if len(parts) > 0 {
contentType = parts[0]
}
// Is the content type compressable?
if _, ok := cw.contentTypes[contentType]; !ok {
return
}
if cw.encoder != nil && cw.encoding != "" {
if wr := cw.encoder(cw.ResponseWriter, cw.level); wr != nil {
cw.w = wr
cw.Header().Set("Content-Encoding", cw.encoding)
// The content-length after compression is unknown
cw.Header().Del("Content-Length")
}
}
}
func (cw *compressResponseWriter) Write(p []byte) (int, error) {
if !cw.wroteHeader {
cw.WriteHeader(http.StatusOK)
}
return cw.w.Write(p)
}
func (cw *compressResponseWriter) Flush() {
if f, ok := cw.w.(http.Flusher); ok {
f.Flush()
}
}
func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hj, ok := cw.w.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer")
}
func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error {
if ps, ok := cw.w.(http.Pusher); ok {
return ps.Push(target, opts)
}
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
}
func (cw *compressResponseWriter) Close() error {
if c, ok := cw.w.(io.WriteCloser); ok {
return c.Close()
}
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
}
func encoderGzip(w http.ResponseWriter, level int) io.Writer {
gw, err := gzip.NewWriterLevel(w, level)
if err != nil {
return nil
}
return gw
}
func encoderDeflate(w http.ResponseWriter, level int) io.Writer {
dw, err := flate.NewWriter(w, level)
if err != nil {
return nil
}
return dw
}

View file

@ -0,0 +1,51 @@
package middleware
import (
"net/http"
"strings"
)
// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.
// An empty charset will allow requests with no Content-Type header or no specified charset.
func ContentCharset(charsets ...string) func(next http.Handler) http.Handler {
for i, c := range charsets {
charsets[i] = strings.ToLower(c)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !contentEncoding(r.Header.Get("Content-Type"), charsets...) {
w.WriteHeader(http.StatusUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
})
}
}
// Check the content encoding against a list of acceptable values.
func contentEncoding(ce string, charsets ...string) bool {
_, ce = split(strings.ToLower(ce), ";")
_, ce = split(ce, "charset=")
ce, _ = split(ce, ";")
for _, c := range charsets {
if ce == c {
return true
}
}
return false
}
// Split a string in two parts, cleaning any whitespace.
func split(str, sep string) (string, string) {
var a, b string
var parts = strings.SplitN(str, sep, 2)
a = strings.TrimSpace(parts[0])
if len(parts) == 2 {
b = strings.TrimSpace(parts[1])
}
return a, b
}

Some files were not shown because too many files have changed in this diff Show more