first commit
This commit is contained in:
commit
923461cec1
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# bindrest
|
||||||
|
|
||||||
|
dip is a small webservice designed to return public ip address
|
||||||
|
|
||||||
|
## Howto
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -mod vendor
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
Copyright © Paul Lecuq 2019
|
25
dip.go
Normal file
25
dip.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
flag.Usage = Usage
|
||||||
|
|
||||||
|
var host, port string
|
||||||
|
|
||||||
|
flag.StringVar(&host, "host", "[::]", "Listen host")
|
||||||
|
flag.StringVar(&port, "port", "8080", "Listen port")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
e.GET("/", DipAsIndex)
|
||||||
|
e.GET("/json", DipAsJSON)
|
||||||
|
|
||||||
|
e.Logger.Fatal(e.Start(fmt.Sprintf("%s:%s", host, port)))
|
||||||
|
}
|
110
functions.go
Normal file
110
functions.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/likexian/whois-go"
|
||||||
|
whoisparser "github.com/likexian/whois-parser-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetIPInfo returns IP address informations
|
||||||
|
func GetIPInfo(c echo.Context) (ip IP, err error) {
|
||||||
|
if c.QueryParam("ip") != "" {
|
||||||
|
ip.IP = c.QueryParam("ip")
|
||||||
|
} else {
|
||||||
|
ip.IP = c.RealIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ip.CheckIPAddress()
|
||||||
|
if err != nil {
|
||||||
|
return IP{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ip.GetHostname()
|
||||||
|
if err != nil {
|
||||||
|
return IP{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DipAsIndex ...
|
||||||
|
func DipAsIndex(c echo.Context) (err error) {
|
||||||
|
var ip IP
|
||||||
|
ip, err = GetIPInfo(c)
|
||||||
|
if err != nil {
|
||||||
|
return c.HTML(500, fmt.Sprintf(
|
||||||
|
`<html>
|
||||||
|
<body>
|
||||||
|
<h1>Error occured: %s</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`, err))
|
||||||
|
}
|
||||||
|
return c.HTML(200, fmt.Sprintf(
|
||||||
|
`<html>
|
||||||
|
<body>
|
||||||
|
<h1>IP: %s</h1>
|
||||||
|
<h2>Reverse DNS: %s</h2>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`, ip.IP, ip.Hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DipAsJSON ...
|
||||||
|
func DipAsJSON(c echo.Context) (err error) {
|
||||||
|
var ip IP
|
||||||
|
ip, err = GetIPInfo(c)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(500, fmt.Sprintf("Error occured: %s", err))
|
||||||
|
}
|
||||||
|
return c.JSON(200, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckIPAddress ...
|
||||||
|
func (ip *IP) CheckIPAddress() (err error) {
|
||||||
|
addr := net.ParseIP(ip.IP)
|
||||||
|
if addr == nil {
|
||||||
|
return fmt.Errorf("Supplied string isn't an IP address")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostname assign ip.Hostname value
|
||||||
|
func (ip *IP) GetHostname() (err error) {
|
||||||
|
revip, err := net.LookupAddr(ip.IP)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("Supplied address %s doesn't have a valid reverse DNS entry", ip.IP))
|
||||||
|
}
|
||||||
|
ip.Hostname = revip[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWhois not implemened yet
|
||||||
|
func (ip *IP) GetWhois() (err error) {
|
||||||
|
resultraw, err := whois.Whois("exemple.com")
|
||||||
|
//resultraw, err := whois.Whois(ip.IP)
|
||||||
|
fmt.Println(resultraw)
|
||||||
|
result, err := whoisparser.Parse(resultraw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage displays possible arguments
|
||||||
|
func Usage() {
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP ...
|
||||||
|
type IP struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
}
|
13
go.mod
Normal file
13
go.mod
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module dip
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/labstack/echo/v4 v4.1.11
|
||||||
|
github.com/likexian/whois-go v1.3.1
|
||||||
|
github.com/likexian/whois-parser-go v1.10.2
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe // indirect
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
)
|
53
go.sum
Normal file
53
go.sum
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
|
||||||
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
|
github.com/likexian/gokit v0.21.11 h1:tBA2U/5e9Pq24dsFuDZ2ykjsaSznjNnovOOK3ljU1ww=
|
||||||
|
github.com/likexian/gokit v0.21.11/go.mod h1:0WlTw7IPdiMtrwu0t5zrLM7XXik27Ey6MhUJHio2fVo=
|
||||||
|
github.com/likexian/whois-go v1.3.1 h1:aKk3ZSwE5X4UHHXf6tzHbzc2BN6ih4DsrXQ6qk667M8=
|
||||||
|
github.com/likexian/whois-go v1.3.1/go.mod h1:6aBrKuJZ66dDhXG7/BiZ6uxPzmBocgapf+04i4VjXdU=
|
||||||
|
github.com/likexian/whois-parser-go v1.10.2 h1:j4qDwPuv7Qnmg3CcSEAywqcTnXCFETMAMn+jhRkTaJs=
|
||||||
|
github.com/likexian/whois-parser-go v1.10.2/go.mod h1:I3zHrhbq4XRmc3nn4xtWNafclqmZXJzW7tSx9KXlCwk=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
25
vendor/github.com/labstack/echo/v4/.editorconfig
generated
vendored
Normal file
25
vendor/github.com/labstack/echo/v4/.editorconfig
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# EditorConfig coding styles definitions. For more information about the
|
||||||
|
# properties used in this file, please see the EditorConfig documentation:
|
||||||
|
# http://editorconfig.org/
|
||||||
|
|
||||||
|
# indicate this is the root of the project
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
end_of_line = LF
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
20
vendor/github.com/labstack/echo/v4/.gitattributes
generated
vendored
Normal file
20
vendor/github.com/labstack/echo/v4/.gitattributes
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Automatically normalize line endings for all text-based files
|
||||||
|
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# For the following file types, normalize line endings to LF on checking and
|
||||||
|
# prevent conversion to CRLF when they are checked out (this is required in
|
||||||
|
# order to prevent newline related issues)
|
||||||
|
.* text eol=lf
|
||||||
|
*.go text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.html text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
LICENSE text eol=lf
|
||||||
|
|
||||||
|
# Exclude `website` and `cookbook` from GitHub's language statistics
|
||||||
|
# https://github.com/github/linguist#using-gitattributes
|
||||||
|
cookbook/* linguist-documentation
|
||||||
|
website/* linguist-documentation
|
7
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
Normal file
7
vendor/github.com/labstack/echo/v4/.gitignore
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
coverage.txt
|
||||||
|
_test
|
||||||
|
vendor
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.out
|
17
vendor/github.com/labstack/echo/v4/.travis.yml
generated
vendored
Normal file
17
vendor/github.com/labstack/echo/v4/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- tip
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
install:
|
||||||
|
- go get -v golang.org/x/lint/golint
|
||||||
|
script:
|
||||||
|
- golint -set_exit_status ./...
|
||||||
|
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
21
vendor/github.com/labstack/echo/v4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/labstack/echo/v4/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 LabStack
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
3
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
Normal file
3
vendor/github.com/labstack/echo/v4/Makefile
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
tag:
|
||||||
|
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
|
||||||
|
@git tag|grep -v ^v
|
113
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
Normal file
113
vendor/github.com/labstack/echo/v4/README.md
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
|
||||||
|
|
||||||
|
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
|
||||||
|
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
|
||||||
|
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
|
||||||
|
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
|
||||||
|
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
|
||||||
|
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com)
|
||||||
|
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
|
||||||
|
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
|
||||||
|
|
||||||
|
## Supported Go versions
|
||||||
|
|
||||||
|
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
|
||||||
|
Therefore a Go version capable of understanding /vN suffixed imports is required:
|
||||||
|
|
||||||
|
- 1.9.7+
|
||||||
|
- 1.10.3+
|
||||||
|
- 1.11+
|
||||||
|
|
||||||
|
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
|
||||||
|
way of using Echo going forward.
|
||||||
|
|
||||||
|
For older versions, please use the latest v3 tag.
|
||||||
|
|
||||||
|
## Feature Overview
|
||||||
|
|
||||||
|
- Optimized HTTP router which smartly prioritize routes
|
||||||
|
- Build robust and scalable RESTful APIs
|
||||||
|
- Group APIs
|
||||||
|
- Extensible middleware framework
|
||||||
|
- Define middleware at root, group or route level
|
||||||
|
- Data binding for JSON, XML and form payload
|
||||||
|
- Handy functions to send variety of HTTP responses
|
||||||
|
- Centralized HTTP error handling
|
||||||
|
- Template rendering with any template engine
|
||||||
|
- Define your format for the logger
|
||||||
|
- Highly customizable
|
||||||
|
- Automatic TLS via Let’s Encrypt
|
||||||
|
- HTTP/2 support
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Date: 2018/03/15<br>
|
||||||
|
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||||
|
Lower is better!
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/I32VdMJ.png">
|
||||||
|
|
||||||
|
## [Guide](https://echo.labstack.com/guide)
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Echo instance
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
e.Use(middleware.Logger())
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
e.GET("/", hello)
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
e.Logger.Fatal(e.Start(":1323"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
func hello(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "Hello, World!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Help
|
||||||
|
|
||||||
|
- [Forum](https://forum.labstack.com)
|
||||||
|
- [Chat](https://gitter.im/labstack/echo)
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
**Use issues for everything**
|
||||||
|
|
||||||
|
- For a small change, just send a PR.
|
||||||
|
- For bigger changes open an issue for discussion before sending a PR.
|
||||||
|
- PR should have:
|
||||||
|
- Test case
|
||||||
|
- Documentation
|
||||||
|
- Example (If it makes sense)
|
||||||
|
- You can also contribute by:
|
||||||
|
- Reporting issues
|
||||||
|
- Suggesting new features or enhancements
|
||||||
|
- Improve/fix documentation
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [Vishal Rana](https://github.com/vishr) - Author
|
||||||
|
- [Nitin Rana](https://github.com/nr17) - Consultant
|
||||||
|
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
|
311
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
Normal file
311
vendor/github.com/labstack/echo/v4/bind.go
generated
vendored
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Binder is the interface that wraps the Bind method.
|
||||||
|
Binder interface {
|
||||||
|
Bind(i interface{}, c Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBinder is the default implementation of the Binder interface.
|
||||||
|
DefaultBinder struct{}
|
||||||
|
|
||||||
|
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||||
|
// Types that don't implement this, but do implement encoding.TextUnmarshaler
|
||||||
|
// will use that interface instead.
|
||||||
|
BindUnmarshaler interface {
|
||||||
|
// UnmarshalParam decodes and assigns a value from an form or query param.
|
||||||
|
UnmarshalParam(param string) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bind implements the `Binder#Bind` function.
|
||||||
|
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||||
|
req := c.Request()
|
||||||
|
|
||||||
|
names := c.ParamNames()
|
||||||
|
values := c.ParamValues()
|
||||||
|
params := map[string][]string{}
|
||||||
|
for i, name := range names {
|
||||||
|
params[name] = []string{values[i]}
|
||||||
|
}
|
||||||
|
if err := b.bindData(i, params, "param"); err != nil {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
if req.ContentLength == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctype := req.Header.Get(HeaderContentType)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(ctype, MIMEApplicationJSON):
|
||||||
|
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
|
||||||
|
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
|
||||||
|
} else if se, ok := err.(*json.SyntaxError); ok {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
|
||||||
|
}
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
|
||||||
|
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
||||||
|
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
|
||||||
|
} else if se, ok := err.(*xml.SyntaxError); ok {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
|
||||||
|
}
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||||
|
params, err := c.FormParams()
|
||||||
|
if err != nil {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
if err = b.bindData(i, params, "form"); err != nil {
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrUnsupportedMediaType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
|
||||||
|
if ptr == nil || len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
typ := reflect.TypeOf(ptr).Elem()
|
||||||
|
val := reflect.ValueOf(ptr).Elem()
|
||||||
|
|
||||||
|
if m, ok := ptr.(*map[string]interface{}); ok {
|
||||||
|
for k, v := range data {
|
||||||
|
(*m)[k] = v[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ.Kind() != reflect.Struct {
|
||||||
|
return errors.New("binding element must be a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
typeField := typ.Field(i)
|
||||||
|
structField := val.Field(i)
|
||||||
|
if !structField.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
structFieldKind := structField.Kind()
|
||||||
|
inputFieldName := typeField.Tag.Get(tag)
|
||||||
|
|
||||||
|
if inputFieldName == "" {
|
||||||
|
inputFieldName = typeField.Name
|
||||||
|
// If tag is nil, we inspect if the field is a struct.
|
||||||
|
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
|
||||||
|
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputValue, exists := data[inputFieldName]
|
||||||
|
if !exists {
|
||||||
|
// Go json.Unmarshal supports case insensitive binding. However the
|
||||||
|
// url params are bound case sensitive which is inconsistent. To
|
||||||
|
// fix this we must check all of the map values in a
|
||||||
|
// case-insensitive search.
|
||||||
|
inputFieldName = strings.ToLower(inputFieldName)
|
||||||
|
for k, v := range data {
|
||||||
|
if strings.ToLower(k) == inputFieldName {
|
||||||
|
inputValue = v
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this first, in case we're dealing with an alias to an array type
|
||||||
|
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numElems := len(inputValue)
|
||||||
|
if structFieldKind == reflect.Slice && numElems > 0 {
|
||||||
|
sliceOf := structField.Type().Elem().Kind()
|
||||||
|
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||||
|
for j := 0; j < numElems; j++ {
|
||||||
|
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val.Field(i).Set(slice)
|
||||||
|
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
||||||
|
// But also call it here, in case we're dealing with an array of BindUnmarshalers
|
||||||
|
if ok, err := unmarshalField(valueKind, val, structField); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch valueKind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
|
||||||
|
case reflect.Int:
|
||||||
|
return setIntField(val, 0, structField)
|
||||||
|
case reflect.Int8:
|
||||||
|
return setIntField(val, 8, structField)
|
||||||
|
case reflect.Int16:
|
||||||
|
return setIntField(val, 16, structField)
|
||||||
|
case reflect.Int32:
|
||||||
|
return setIntField(val, 32, structField)
|
||||||
|
case reflect.Int64:
|
||||||
|
return setIntField(val, 64, structField)
|
||||||
|
case reflect.Uint:
|
||||||
|
return setUintField(val, 0, structField)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return setUintField(val, 8, structField)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return setUintField(val, 16, structField)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return setUintField(val, 32, structField)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return setUintField(val, 64, structField)
|
||||||
|
case reflect.Bool:
|
||||||
|
return setBoolField(val, structField)
|
||||||
|
case reflect.Float32:
|
||||||
|
return setFloatField(val, 32, structField)
|
||||||
|
case reflect.Float64:
|
||||||
|
return setFloatField(val, 64, structField)
|
||||||
|
case reflect.String:
|
||||||
|
structField.SetString(val)
|
||||||
|
default:
|
||||||
|
return errors.New("unknown type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
|
||||||
|
switch valueKind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return unmarshalFieldPtr(val, field)
|
||||||
|
default:
|
||||||
|
return unmarshalFieldNonPtr(val, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
|
||||||
|
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
|
||||||
|
ptr := reflect.New(field.Type())
|
||||||
|
if ptr.CanInterface() {
|
||||||
|
iface := ptr.Interface()
|
||||||
|
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
|
||||||
|
return unmarshaler, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler
|
||||||
|
func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) {
|
||||||
|
ptr := reflect.New(field.Type())
|
||||||
|
if ptr.CanInterface() {
|
||||||
|
iface := ptr.Interface()
|
||||||
|
if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok {
|
||||||
|
return unmarshaler, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
|
||||||
|
if unmarshaler, ok := bindUnmarshaler(field); ok {
|
||||||
|
err := unmarshaler.UnmarshalParam(value)
|
||||||
|
field.Set(reflect.ValueOf(unmarshaler).Elem())
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if unmarshaler, ok := textUnmarshaler(field); ok {
|
||||||
|
err := unmarshaler.UnmarshalText([]byte(value))
|
||||||
|
field.Set(reflect.ValueOf(unmarshaler).Elem())
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
|
||||||
|
if field.IsNil() {
|
||||||
|
// Initialize the pointer to a nil value
|
||||||
|
field.Set(reflect.New(field.Type().Elem()))
|
||||||
|
}
|
||||||
|
return unmarshalFieldNonPtr(value, field.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIntField(value string, bitSize int, field reflect.Value) error {
|
||||||
|
if value == "" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
intVal, err := strconv.ParseInt(value, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetInt(intVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUintField(value string, bitSize int, field reflect.Value) error {
|
||||||
|
if value == "" {
|
||||||
|
value = "0"
|
||||||
|
}
|
||||||
|
uintVal, err := strconv.ParseUint(value, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetUint(uintVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoolField(value string, field reflect.Value) error {
|
||||||
|
if value == "" {
|
||||||
|
value = "false"
|
||||||
|
}
|
||||||
|
boolVal, err := strconv.ParseBool(value)
|
||||||
|
if err == nil {
|
||||||
|
field.SetBool(boolVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFloatField(value string, bitSize int, field reflect.Value) error {
|
||||||
|
if value == "" {
|
||||||
|
value = "0.0"
|
||||||
|
}
|
||||||
|
floatVal, err := strconv.ParseFloat(value, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetFloat(floatVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
613
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
Normal file
613
vendor/github.com/labstack/echo/v4/context.go
generated
vendored
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Context represents the context of the current HTTP request. It holds request and
|
||||||
|
// response objects, path, path parameters, data and registered handler.
|
||||||
|
Context interface {
|
||||||
|
// Request returns `*http.Request`.
|
||||||
|
Request() *http.Request
|
||||||
|
|
||||||
|
// SetRequest sets `*http.Request`.
|
||||||
|
SetRequest(r *http.Request)
|
||||||
|
|
||||||
|
// SetResponse sets `*Response`.
|
||||||
|
SetResponse(r *Response)
|
||||||
|
|
||||||
|
// Response returns `*Response`.
|
||||||
|
Response() *Response
|
||||||
|
|
||||||
|
// IsTLS returns true if HTTP connection is TLS otherwise false.
|
||||||
|
IsTLS() bool
|
||||||
|
|
||||||
|
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
|
||||||
|
IsWebSocket() bool
|
||||||
|
|
||||||
|
// Scheme returns the HTTP protocol scheme, `http` or `https`.
|
||||||
|
Scheme() string
|
||||||
|
|
||||||
|
// RealIP returns the client's network address based on `X-Forwarded-For`
|
||||||
|
// or `X-Real-IP` request header.
|
||||||
|
RealIP() string
|
||||||
|
|
||||||
|
// Path returns the registered path for the handler.
|
||||||
|
Path() string
|
||||||
|
|
||||||
|
// SetPath sets the registered path for the handler.
|
||||||
|
SetPath(p string)
|
||||||
|
|
||||||
|
// Param returns path parameter by name.
|
||||||
|
Param(name string) string
|
||||||
|
|
||||||
|
// ParamNames returns path parameter names.
|
||||||
|
ParamNames() []string
|
||||||
|
|
||||||
|
// SetParamNames sets path parameter names.
|
||||||
|
SetParamNames(names ...string)
|
||||||
|
|
||||||
|
// ParamValues returns path parameter values.
|
||||||
|
ParamValues() []string
|
||||||
|
|
||||||
|
// SetParamValues sets path parameter values.
|
||||||
|
SetParamValues(values ...string)
|
||||||
|
|
||||||
|
// QueryParam returns the query param for the provided name.
|
||||||
|
QueryParam(name string) string
|
||||||
|
|
||||||
|
// QueryParams returns the query parameters as `url.Values`.
|
||||||
|
QueryParams() url.Values
|
||||||
|
|
||||||
|
// QueryString returns the URL query string.
|
||||||
|
QueryString() string
|
||||||
|
|
||||||
|
// FormValue returns the form field value for the provided name.
|
||||||
|
FormValue(name string) string
|
||||||
|
|
||||||
|
// FormParams returns the form parameters as `url.Values`.
|
||||||
|
FormParams() (url.Values, error)
|
||||||
|
|
||||||
|
// FormFile returns the multipart form file for the provided name.
|
||||||
|
FormFile(name string) (*multipart.FileHeader, error)
|
||||||
|
|
||||||
|
// MultipartForm returns the multipart form.
|
||||||
|
MultipartForm() (*multipart.Form, error)
|
||||||
|
|
||||||
|
// Cookie returns the named cookie provided in the request.
|
||||||
|
Cookie(name string) (*http.Cookie, error)
|
||||||
|
|
||||||
|
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||||
|
SetCookie(cookie *http.Cookie)
|
||||||
|
|
||||||
|
// Cookies returns the HTTP cookies sent with the request.
|
||||||
|
Cookies() []*http.Cookie
|
||||||
|
|
||||||
|
// Get retrieves data from the context.
|
||||||
|
Get(key string) interface{}
|
||||||
|
|
||||||
|
// Set saves data in the context.
|
||||||
|
Set(key string, val interface{})
|
||||||
|
|
||||||
|
// Bind binds the request body into provided type `i`. The default binder
|
||||||
|
// does it based on Content-Type header.
|
||||||
|
Bind(i interface{}) error
|
||||||
|
|
||||||
|
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
|
||||||
|
// Validator must be registered using `Echo#Validator`.
|
||||||
|
Validate(i interface{}) error
|
||||||
|
|
||||||
|
// Render renders a template with data and sends a text/html response with status
|
||||||
|
// code. Renderer must be registered using `Echo.Renderer`.
|
||||||
|
Render(code int, name string, data interface{}) error
|
||||||
|
|
||||||
|
// HTML sends an HTTP response with status code.
|
||||||
|
HTML(code int, html string) error
|
||||||
|
|
||||||
|
// HTMLBlob sends an HTTP blob response with status code.
|
||||||
|
HTMLBlob(code int, b []byte) error
|
||||||
|
|
||||||
|
// String sends a string response with status code.
|
||||||
|
String(code int, s string) error
|
||||||
|
|
||||||
|
// JSON sends a JSON response with status code.
|
||||||
|
JSON(code int, i interface{}) error
|
||||||
|
|
||||||
|
// JSONPretty sends a pretty-print JSON with status code.
|
||||||
|
JSONPretty(code int, i interface{}, indent string) error
|
||||||
|
|
||||||
|
// JSONBlob sends a JSON blob response with status code.
|
||||||
|
JSONBlob(code int, b []byte) error
|
||||||
|
|
||||||
|
// JSONP sends a JSONP response with status code. It uses `callback` to construct
|
||||||
|
// the JSONP payload.
|
||||||
|
JSONP(code int, callback string, i interface{}) error
|
||||||
|
|
||||||
|
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
|
||||||
|
// to construct the JSONP payload.
|
||||||
|
JSONPBlob(code int, callback string, b []byte) error
|
||||||
|
|
||||||
|
// XML sends an XML response with status code.
|
||||||
|
XML(code int, i interface{}) error
|
||||||
|
|
||||||
|
// XMLPretty sends a pretty-print XML with status code.
|
||||||
|
XMLPretty(code int, i interface{}, indent string) error
|
||||||
|
|
||||||
|
// XMLBlob sends an XML blob response with status code.
|
||||||
|
XMLBlob(code int, b []byte) error
|
||||||
|
|
||||||
|
// Blob sends a blob response with status code and content type.
|
||||||
|
Blob(code int, contentType string, b []byte) error
|
||||||
|
|
||||||
|
// Stream sends a streaming response with status code and content type.
|
||||||
|
Stream(code int, contentType string, r io.Reader) error
|
||||||
|
|
||||||
|
// File sends a response with the content of the file.
|
||||||
|
File(file string) error
|
||||||
|
|
||||||
|
// Attachment sends a response as attachment, prompting client to save the
|
||||||
|
// file.
|
||||||
|
Attachment(file string, name string) error
|
||||||
|
|
||||||
|
// Inline sends a response as inline, opening the file in the browser.
|
||||||
|
Inline(file string, name string) error
|
||||||
|
|
||||||
|
// NoContent sends a response with no body and a status code.
|
||||||
|
NoContent(code int) error
|
||||||
|
|
||||||
|
// Redirect redirects the request to a provided URL with status code.
|
||||||
|
Redirect(code int, url string) error
|
||||||
|
|
||||||
|
// Error invokes the registered HTTP error handler. Generally used by middleware.
|
||||||
|
Error(err error)
|
||||||
|
|
||||||
|
// Handler returns the matched handler by router.
|
||||||
|
Handler() HandlerFunc
|
||||||
|
|
||||||
|
// SetHandler sets the matched handler by router.
|
||||||
|
SetHandler(h HandlerFunc)
|
||||||
|
|
||||||
|
// Logger returns the `Logger` instance.
|
||||||
|
Logger() Logger
|
||||||
|
|
||||||
|
// Echo returns the `Echo` instance.
|
||||||
|
Echo() *Echo
|
||||||
|
|
||||||
|
// Reset resets the context after request completes. It must be called along
|
||||||
|
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
|
||||||
|
// See `Echo#ServeHTTP()`
|
||||||
|
Reset(r *http.Request, w http.ResponseWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
context struct {
|
||||||
|
request *http.Request
|
||||||
|
response *Response
|
||||||
|
path string
|
||||||
|
pnames []string
|
||||||
|
pvalues []string
|
||||||
|
query url.Values
|
||||||
|
handler HandlerFunc
|
||||||
|
store Map
|
||||||
|
echo *Echo
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMemory = 32 << 20 // 32 MB
|
||||||
|
indexPage = "index.html"
|
||||||
|
defaultIndent = " "
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *context) writeContentType(value string) {
|
||||||
|
header := c.Response().Header()
|
||||||
|
if header.Get(HeaderContentType) == "" {
|
||||||
|
header.Set(HeaderContentType, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Request() *http.Request {
|
||||||
|
return c.request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetRequest(r *http.Request) {
|
||||||
|
c.request = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Response() *Response {
|
||||||
|
return c.response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetResponse(r *Response) {
|
||||||
|
c.response = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) IsTLS() bool {
|
||||||
|
return c.request.TLS != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) IsWebSocket() bool {
|
||||||
|
upgrade := c.request.Header.Get(HeaderUpgrade)
|
||||||
|
return strings.ToLower(upgrade) == "websocket"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Scheme() string {
|
||||||
|
// Can't use `r.Request.URL.Scheme`
|
||||||
|
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
|
||||||
|
if c.IsTLS() {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) RealIP() string {
|
||||||
|
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||||
|
return strings.Split(ip, ", ")[0]
|
||||||
|
}
|
||||||
|
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Path() string {
|
||||||
|
return c.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetPath(p string) {
|
||||||
|
c.path = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Param(name string) string {
|
||||||
|
for i, n := range c.pnames {
|
||||||
|
if i < len(c.pvalues) {
|
||||||
|
if n == name {
|
||||||
|
return c.pvalues[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) ParamNames() []string {
|
||||||
|
return c.pnames
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetParamNames(names ...string) {
|
||||||
|
c.pnames = names
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) ParamValues() []string {
|
||||||
|
return c.pvalues[:len(c.pnames)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetParamValues(values ...string) {
|
||||||
|
c.pvalues = values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) QueryParam(name string) string {
|
||||||
|
if c.query == nil {
|
||||||
|
c.query = c.request.URL.Query()
|
||||||
|
}
|
||||||
|
return c.query.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) QueryParams() url.Values {
|
||||||
|
if c.query == nil {
|
||||||
|
c.query = c.request.URL.Query()
|
||||||
|
}
|
||||||
|
return c.query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) QueryString() string {
|
||||||
|
return c.request.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) FormValue(name string) string {
|
||||||
|
return c.request.FormValue(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) FormParams() (url.Values, error) {
|
||||||
|
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
|
||||||
|
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := c.request.ParseForm(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.request.Form, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||||
|
_, fh, err := c.request.FormFile(name)
|
||||||
|
return fh, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) MultipartForm() (*multipart.Form, error) {
|
||||||
|
err := c.request.ParseMultipartForm(defaultMemory)
|
||||||
|
return c.request.MultipartForm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Cookie(name string) (*http.Cookie, error) {
|
||||||
|
return c.request.Cookie(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetCookie(cookie *http.Cookie) {
|
||||||
|
http.SetCookie(c.Response(), cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Cookies() []*http.Cookie {
|
||||||
|
return c.request.Cookies()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Get(key string) interface{} {
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
return c.store[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Set(key string, val interface{}) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if c.store == nil {
|
||||||
|
c.store = make(Map)
|
||||||
|
}
|
||||||
|
c.store[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Bind(i interface{}) error {
|
||||||
|
return c.echo.Binder.Bind(i, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Validate(i interface{}) error {
|
||||||
|
if c.echo.Validator == nil {
|
||||||
|
return ErrValidatorNotRegistered
|
||||||
|
}
|
||||||
|
return c.echo.Validator.Validate(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Render(code int, name string, data interface{}) (err error) {
|
||||||
|
if c.echo.Renderer == nil {
|
||||||
|
return ErrRendererNotRegistered
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.HTMLBlob(code, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) HTML(code int, html string) (err error) {
|
||||||
|
return c.HTMLBlob(code, []byte(html))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) HTMLBlob(code int, b []byte) (err error) {
|
||||||
|
return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) String(code int, s string) (err error) {
|
||||||
|
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
|
||||||
|
enc := json.NewEncoder(c.response)
|
||||||
|
_, pretty := c.QueryParams()["pretty"]
|
||||||
|
if c.echo.Debug || pretty {
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
}
|
||||||
|
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = enc.Encode(i); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = c.response.Write([]byte(");")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) json(code int, i interface{}, indent string) error {
|
||||||
|
enc := json.NewEncoder(c.response)
|
||||||
|
if indent != "" {
|
||||||
|
enc.SetIndent("", indent)
|
||||||
|
}
|
||||||
|
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
|
||||||
|
c.response.Status = code
|
||||||
|
return enc.Encode(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSON(code int, i interface{}) (err error) {
|
||||||
|
indent := ""
|
||||||
|
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||||
|
indent = defaultIndent
|
||||||
|
}
|
||||||
|
return c.json(code, i, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
|
||||||
|
return c.json(code, i, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSONBlob(code int, b []byte) (err error) {
|
||||||
|
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
||||||
|
return c.jsonPBlob(code, callback, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||||
|
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = c.response.Write(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = c.response.Write([]byte(");"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) xml(code int, i interface{}, indent string) (err error) {
|
||||||
|
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
enc := xml.NewEncoder(c.response)
|
||||||
|
if indent != "" {
|
||||||
|
enc.Indent("", indent)
|
||||||
|
}
|
||||||
|
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return enc.Encode(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) XML(code int, i interface{}) (err error) {
|
||||||
|
indent := ""
|
||||||
|
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||||
|
indent = defaultIndent
|
||||||
|
}
|
||||||
|
return c.xml(code, i, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
|
||||||
|
return c.xml(code, i, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||||
|
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = c.response.Write(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
|
||||||
|
c.writeContentType(contentType)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
_, err = c.response.Write(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
|
||||||
|
c.writeContentType(contentType)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
_, err = io.Copy(c.response, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) File(file string) (err error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return NotFoundHandler(c)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fi, _ := f.Stat()
|
||||||
|
if fi.IsDir() {
|
||||||
|
file = filepath.Join(file, indexPage)
|
||||||
|
f, err = os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return NotFoundHandler(c)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if fi, err = f.Stat(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Attachment(file, name string) error {
|
||||||
|
return c.contentDisposition(file, name, "attachment")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Inline(file, name string) error {
|
||||||
|
return c.contentDisposition(file, name, "inline")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) contentDisposition(file, name, dispositionType string) error {
|
||||||
|
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
|
||||||
|
return c.File(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) NoContent(code int) error {
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Redirect(code int, url string) error {
|
||||||
|
if code < 300 || code > 308 {
|
||||||
|
return ErrInvalidRedirectCode
|
||||||
|
}
|
||||||
|
c.response.Header().Set(HeaderLocation, url)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Error(err error) {
|
||||||
|
c.echo.HTTPErrorHandler(err, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Echo() *Echo {
|
||||||
|
return c.echo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Handler() HandlerFunc {
|
||||||
|
return c.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) SetHandler(h HandlerFunc) {
|
||||||
|
c.handler = h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Logger() Logger {
|
||||||
|
return c.echo.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
|
||||||
|
c.request = r
|
||||||
|
c.response.reset(w)
|
||||||
|
c.query = nil
|
||||||
|
c.handler = NotFoundHandler
|
||||||
|
c.store = nil
|
||||||
|
c.path = ""
|
||||||
|
c.pnames = nil
|
||||||
|
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
|
||||||
|
// c.pvalues = nil
|
||||||
|
}
|
844
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
Normal file
844
vendor/github.com/labstack/echo/v4/echo.go
generated
vendored
Normal file
@ -0,0 +1,844 @@
|
|||||||
|
/*
|
||||||
|
Package echo implements high performance, minimalist Go web framework.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
func hello(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "Hello, World!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Echo instance
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
e.Use(middleware.Logger())
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
e.GET("/", hello)
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
e.Logger.Fatal(e.Start(":1323"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Learn more at https://echo.labstack.com
|
||||||
|
*/
|
||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
stdContext "context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
stdLog "log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/color"
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Echo is the top-level framework instance.
|
||||||
|
Echo struct {
|
||||||
|
common
|
||||||
|
StdLogger *stdLog.Logger
|
||||||
|
colorer *color.Color
|
||||||
|
premiddleware []MiddlewareFunc
|
||||||
|
middleware []MiddlewareFunc
|
||||||
|
maxParam *int
|
||||||
|
router *Router
|
||||||
|
routers map[string]*Router
|
||||||
|
notFoundHandler HandlerFunc
|
||||||
|
pool sync.Pool
|
||||||
|
Server *http.Server
|
||||||
|
TLSServer *http.Server
|
||||||
|
Listener net.Listener
|
||||||
|
TLSListener net.Listener
|
||||||
|
AutoTLSManager autocert.Manager
|
||||||
|
DisableHTTP2 bool
|
||||||
|
Debug bool
|
||||||
|
HideBanner bool
|
||||||
|
HidePort bool
|
||||||
|
HTTPErrorHandler HTTPErrorHandler
|
||||||
|
Binder Binder
|
||||||
|
Validator Validator
|
||||||
|
Renderer Renderer
|
||||||
|
Logger Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route contains a handler and information for matching against requests.
|
||||||
|
Route struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPError represents an error that occurred while handling a request.
|
||||||
|
HTTPError struct {
|
||||||
|
Code int `json:"-"`
|
||||||
|
Message interface{} `json:"message"`
|
||||||
|
Internal error `json:"-"` // Stores the error returned by an external dependency
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewareFunc defines a function to process middleware.
|
||||||
|
MiddlewareFunc func(HandlerFunc) HandlerFunc
|
||||||
|
|
||||||
|
// HandlerFunc defines a function to serve HTTP requests.
|
||||||
|
HandlerFunc func(Context) error
|
||||||
|
|
||||||
|
// HTTPErrorHandler is a centralized HTTP error handler.
|
||||||
|
HTTPErrorHandler func(error, Context)
|
||||||
|
|
||||||
|
// Validator is the interface that wraps the Validate function.
|
||||||
|
Validator interface {
|
||||||
|
Validate(i interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer is the interface that wraps the Render function.
|
||||||
|
Renderer interface {
|
||||||
|
Render(io.Writer, string, interface{}, Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map defines a generic map of type `map[string]interface{}`.
|
||||||
|
Map map[string]interface{}
|
||||||
|
|
||||||
|
// Common struct for Echo & Group.
|
||||||
|
common struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTP methods
|
||||||
|
// NOTE: Deprecated, please use the stdlib constants directly instead.
|
||||||
|
const (
|
||||||
|
CONNECT = http.MethodConnect
|
||||||
|
DELETE = http.MethodDelete
|
||||||
|
GET = http.MethodGet
|
||||||
|
HEAD = http.MethodHead
|
||||||
|
OPTIONS = http.MethodOptions
|
||||||
|
PATCH = http.MethodPatch
|
||||||
|
POST = http.MethodPost
|
||||||
|
// PROPFIND = "PROPFIND"
|
||||||
|
PUT = http.MethodPut
|
||||||
|
TRACE = http.MethodTrace
|
||||||
|
)
|
||||||
|
|
||||||
|
// MIME types
|
||||||
|
const (
|
||||||
|
MIMEApplicationJSON = "application/json"
|
||||||
|
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
|
||||||
|
MIMEApplicationJavaScript = "application/javascript"
|
||||||
|
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
|
||||||
|
MIMEApplicationXML = "application/xml"
|
||||||
|
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
|
||||||
|
MIMETextXML = "text/xml"
|
||||||
|
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
|
||||||
|
MIMEApplicationForm = "application/x-www-form-urlencoded"
|
||||||
|
MIMEApplicationProtobuf = "application/protobuf"
|
||||||
|
MIMEApplicationMsgpack = "application/msgpack"
|
||||||
|
MIMETextHTML = "text/html"
|
||||||
|
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
|
||||||
|
MIMETextPlain = "text/plain"
|
||||||
|
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
|
||||||
|
MIMEMultipartForm = "multipart/form-data"
|
||||||
|
MIMEOctetStream = "application/octet-stream"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
charsetUTF8 = "charset=UTF-8"
|
||||||
|
// PROPFIND Method can be used on collection and property resources.
|
||||||
|
PROPFIND = "PROPFIND"
|
||||||
|
// REPORT Method can be used to get information about a resource, see rfc 3253
|
||||||
|
REPORT = "REPORT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
const (
|
||||||
|
HeaderAccept = "Accept"
|
||||||
|
HeaderAcceptEncoding = "Accept-Encoding"
|
||||||
|
HeaderAllow = "Allow"
|
||||||
|
HeaderAuthorization = "Authorization"
|
||||||
|
HeaderContentDisposition = "Content-Disposition"
|
||||||
|
HeaderContentEncoding = "Content-Encoding"
|
||||||
|
HeaderContentLength = "Content-Length"
|
||||||
|
HeaderContentType = "Content-Type"
|
||||||
|
HeaderCookie = "Cookie"
|
||||||
|
HeaderSetCookie = "Set-Cookie"
|
||||||
|
HeaderIfModifiedSince = "If-Modified-Since"
|
||||||
|
HeaderLastModified = "Last-Modified"
|
||||||
|
HeaderLocation = "Location"
|
||||||
|
HeaderUpgrade = "Upgrade"
|
||||||
|
HeaderVary = "Vary"
|
||||||
|
HeaderWWWAuthenticate = "WWW-Authenticate"
|
||||||
|
HeaderXForwardedFor = "X-Forwarded-For"
|
||||||
|
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||||
|
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
|
||||||
|
HeaderXForwardedSsl = "X-Forwarded-Ssl"
|
||||||
|
HeaderXUrlScheme = "X-Url-Scheme"
|
||||||
|
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
||||||
|
HeaderXRealIP = "X-Real-IP"
|
||||||
|
HeaderXRequestID = "X-Request-ID"
|
||||||
|
HeaderXRequestedWith = "X-Requested-With"
|
||||||
|
HeaderServer = "Server"
|
||||||
|
HeaderOrigin = "Origin"
|
||||||
|
|
||||||
|
// Access control
|
||||||
|
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
|
||||||
|
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
|
||||||
|
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||||
|
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||||
|
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||||
|
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||||
|
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||||
|
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
||||||
|
|
||||||
|
// Security
|
||||||
|
HeaderStrictTransportSecurity = "Strict-Transport-Security"
|
||||||
|
HeaderXContentTypeOptions = "X-Content-Type-Options"
|
||||||
|
HeaderXXSSProtection = "X-XSS-Protection"
|
||||||
|
HeaderXFrameOptions = "X-Frame-Options"
|
||||||
|
HeaderContentSecurityPolicy = "Content-Security-Policy"
|
||||||
|
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
|
||||||
|
HeaderXCSRFToken = "X-CSRF-Token"
|
||||||
|
HeaderReferrerPolicy = "Referrer-Policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Version of Echo
|
||||||
|
Version = "4.1.11"
|
||||||
|
website = "https://echo.labstack.com"
|
||||||
|
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
||||||
|
banner = `
|
||||||
|
____ __
|
||||||
|
/ __/___/ / ___
|
||||||
|
/ _// __/ _ \/ _ \
|
||||||
|
/___/\__/_//_/\___/ %s
|
||||||
|
High performance, minimalist Go web framework
|
||||||
|
%s
|
||||||
|
____________________________________O/_______
|
||||||
|
O\
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
methods = [...]string{
|
||||||
|
http.MethodConnect,
|
||||||
|
http.MethodDelete,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodHead,
|
||||||
|
http.MethodOptions,
|
||||||
|
http.MethodPatch,
|
||||||
|
http.MethodPost,
|
||||||
|
PROPFIND,
|
||||||
|
http.MethodPut,
|
||||||
|
http.MethodTrace,
|
||||||
|
REPORT,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
var (
|
||||||
|
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
|
||||||
|
ErrNotFound = NewHTTPError(http.StatusNotFound)
|
||||||
|
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
|
||||||
|
ErrForbidden = NewHTTPError(http.StatusForbidden)
|
||||||
|
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
|
||||||
|
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
||||||
|
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests)
|
||||||
|
ErrBadRequest = NewHTTPError(http.StatusBadRequest)
|
||||||
|
ErrBadGateway = NewHTTPError(http.StatusBadGateway)
|
||||||
|
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError)
|
||||||
|
ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout)
|
||||||
|
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable)
|
||||||
|
ErrValidatorNotRegistered = errors.New("validator not registered")
|
||||||
|
ErrRendererNotRegistered = errors.New("renderer not registered")
|
||||||
|
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
|
||||||
|
ErrCookieNotFound = errors.New("cookie not found")
|
||||||
|
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error handlers
|
||||||
|
var (
|
||||||
|
NotFoundHandler = func(c Context) error {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodNotAllowedHandler = func(c Context) error {
|
||||||
|
return ErrMethodNotAllowed
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates an instance of Echo.
|
||||||
|
func New() (e *Echo) {
|
||||||
|
e = &Echo{
|
||||||
|
Server: new(http.Server),
|
||||||
|
TLSServer: new(http.Server),
|
||||||
|
AutoTLSManager: autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
},
|
||||||
|
Logger: log.New("echo"),
|
||||||
|
colorer: color.New(),
|
||||||
|
maxParam: new(int),
|
||||||
|
}
|
||||||
|
e.Server.Handler = e
|
||||||
|
e.TLSServer.Handler = e
|
||||||
|
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
|
||||||
|
e.Binder = &DefaultBinder{}
|
||||||
|
e.Logger.SetLevel(log.ERROR)
|
||||||
|
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
|
||||||
|
e.pool.New = func() interface{} {
|
||||||
|
return e.NewContext(nil, nil)
|
||||||
|
}
|
||||||
|
e.router = NewRouter(e)
|
||||||
|
e.routers = map[string]*Router{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a Context instance.
|
||||||
|
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
|
||||||
|
return &context{
|
||||||
|
request: r,
|
||||||
|
response: NewResponse(w, e),
|
||||||
|
store: make(Map),
|
||||||
|
echo: e,
|
||||||
|
pvalues: make([]string, *e.maxParam),
|
||||||
|
handler: NotFoundHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router returns the default router.
|
||||||
|
func (e *Echo) Router() *Router {
|
||||||
|
return e.router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routers returns the map of host => router.
|
||||||
|
func (e *Echo) Routers() map[string]*Router {
|
||||||
|
return e.routers
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
|
||||||
|
// with status code.
|
||||||
|
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||||
|
he, ok := err.(*HTTPError)
|
||||||
|
if ok {
|
||||||
|
if he.Internal != nil {
|
||||||
|
if herr, ok := he.Internal.(*HTTPError); ok {
|
||||||
|
he = herr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
he = &HTTPError{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Message: http.StatusText(http.StatusInternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.Debug {
|
||||||
|
he.Message = err.Error()
|
||||||
|
} else if m, ok := he.Message.(string); ok {
|
||||||
|
he.Message = Map{"message": m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
if !c.Response().Committed {
|
||||||
|
if c.Request().Method == http.MethodHead { // Issue #608
|
||||||
|
err = c.NoContent(he.Code)
|
||||||
|
} else {
|
||||||
|
err = c.JSON(he.Code, he.Message)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre adds middleware to the chain which is run before router.
|
||||||
|
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
|
||||||
|
e.premiddleware = append(e.premiddleware, middleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds middleware to the chain which is run after router.
|
||||||
|
func (e *Echo) Use(middleware ...MiddlewareFunc) {
|
||||||
|
e.middleware = append(e.middleware, middleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONNECT registers a new CONNECT route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodConnect, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE registers a new DELETE route for a path with matching handler in the router
|
||||||
|
// with optional route-level middleware.
|
||||||
|
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodDelete, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET registers a new GET route for a path with matching handler in the router
|
||||||
|
// with optional route-level middleware.
|
||||||
|
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodGet, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD registers a new HEAD route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodHead, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodOptions, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH registers a new PATCH route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodPatch, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST registers a new POST route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodPost, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT registers a new PUT route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodPut, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRACE registers a new TRACE route for a path with matching handler in the
|
||||||
|
// router with optional route-level middleware.
|
||||||
|
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.Add(http.MethodTrace, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any registers a new route for all HTTP methods and path with matching handler
|
||||||
|
// in the router with optional route-level middleware.
|
||||||
|
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||||
|
routes := make([]*Route, len(methods))
|
||||||
|
for i, m := range methods {
|
||||||
|
routes[i] = e.Add(m, path, handler, middleware...)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match registers a new route for multiple HTTP methods and path with matching
|
||||||
|
// handler in the router with optional route-level middleware.
|
||||||
|
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||||
|
routes := make([]*Route, len(methods))
|
||||||
|
for i, m := range methods {
|
||||||
|
routes[i] = e.Add(m, path, handler, middleware...)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static registers a new route with path prefix to serve static files from the
|
||||||
|
// provided root directory.
|
||||||
|
func (e *Echo) Static(prefix, root string) *Route {
|
||||||
|
if root == "" {
|
||||||
|
root = "." // For security we want to restrict to CWD.
|
||||||
|
}
|
||||||
|
return e.static(prefix, root, e.GET)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
|
||||||
|
h := func(c Context) error {
|
||||||
|
p, err := url.PathUnescape(c.Param("*"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
|
||||||
|
return c.File(name)
|
||||||
|
}
|
||||||
|
if prefix == "/" {
|
||||||
|
return get(prefix+"*", h)
|
||||||
|
}
|
||||||
|
return get(prefix+"/*", h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
|
||||||
|
m ...MiddlewareFunc) *Route {
|
||||||
|
return get(path, func(c Context) error {
|
||||||
|
return c.File(file)
|
||||||
|
}, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File registers a new route with path to serve a static file with optional route-level middleware.
|
||||||
|
func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
|
||||||
|
return e.file(path, file, e.GET, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||||
|
name := handlerName(handler)
|
||||||
|
router := e.findRouter(host)
|
||||||
|
router.Add(method, path, func(c Context) error {
|
||||||
|
h := handler
|
||||||
|
// Chain middleware
|
||||||
|
for i := len(middleware) - 1; i >= 0; i-- {
|
||||||
|
h = middleware[i](h)
|
||||||
|
}
|
||||||
|
return h(c)
|
||||||
|
})
|
||||||
|
r := &Route{
|
||||||
|
Method: method,
|
||||||
|
Path: path,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
e.router.routes[method+path] = r
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add registers a new route for an HTTP method and path with matching handler
|
||||||
|
// in the router with optional route-level middleware.
|
||||||
|
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||||
|
return e.add("", method, path, handler, middleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host creates a new router group for the provided host and optional host-level middleware.
|
||||||
|
func (e *Echo) Host(name string, m ...MiddlewareFunc) (g *Group) {
|
||||||
|
e.routers[name] = NewRouter(e)
|
||||||
|
g = &Group{host: name, echo: e}
|
||||||
|
g.Use(m...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group creates a new router group with prefix and optional group-level middleware.
|
||||||
|
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
|
||||||
|
g = &Group{prefix: prefix, echo: e}
|
||||||
|
g.Use(m...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// URI generates a URI from handler.
|
||||||
|
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
|
||||||
|
name := handlerName(handler)
|
||||||
|
return e.Reverse(name, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL is an alias for `URI` function.
|
||||||
|
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
|
||||||
|
return e.URI(h, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse generates an URL from route name and provided parameters.
|
||||||
|
func (e *Echo) Reverse(name string, params ...interface{}) string {
|
||||||
|
uri := new(bytes.Buffer)
|
||||||
|
ln := len(params)
|
||||||
|
n := 0
|
||||||
|
for _, r := range e.router.routes {
|
||||||
|
if r.Name == name {
|
||||||
|
for i, l := 0, len(r.Path); i < l; i++ {
|
||||||
|
if r.Path[i] == ':' && n < ln {
|
||||||
|
for ; i < l && r.Path[i] != '/'; i++ {
|
||||||
|
}
|
||||||
|
uri.WriteString(fmt.Sprintf("%v", params[n]))
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if i < l {
|
||||||
|
uri.WriteByte(r.Path[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uri.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the registered routes.
|
||||||
|
func (e *Echo) Routes() []*Route {
|
||||||
|
routes := make([]*Route, 0, len(e.router.routes))
|
||||||
|
for _, v := range e.router.routes {
|
||||||
|
routes = append(routes, v)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireContext returns an empty `Context` instance from the pool.
|
||||||
|
// You must return the context by calling `ReleaseContext()`.
|
||||||
|
func (e *Echo) AcquireContext() Context {
|
||||||
|
return e.pool.Get().(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseContext returns the `Context` instance back to the pool.
|
||||||
|
// You must call it after `AcquireContext()`.
|
||||||
|
func (e *Echo) ReleaseContext(c Context) {
|
||||||
|
e.pool.Put(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
||||||
|
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Acquire context
|
||||||
|
c := e.pool.Get().(*context)
|
||||||
|
c.Reset(r, w)
|
||||||
|
|
||||||
|
h := NotFoundHandler
|
||||||
|
|
||||||
|
if e.premiddleware == nil {
|
||||||
|
e.findRouter(r.Host).Find(r.Method, getPath(r), c)
|
||||||
|
h = c.Handler()
|
||||||
|
h = applyMiddleware(h, e.middleware...)
|
||||||
|
} else {
|
||||||
|
h = func(c Context) error {
|
||||||
|
e.findRouter(r.Host).Find(r.Method, getPath(r), c)
|
||||||
|
h := c.Handler()
|
||||||
|
h = applyMiddleware(h, e.middleware...)
|
||||||
|
return h(c)
|
||||||
|
}
|
||||||
|
h = applyMiddleware(h, e.premiddleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute chain
|
||||||
|
if err := h(c); err != nil {
|
||||||
|
e.HTTPErrorHandler(err, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release context
|
||||||
|
e.pool.Put(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts an HTTP server.
|
||||||
|
func (e *Echo) Start(address string) error {
|
||||||
|
e.Server.Addr = address
|
||||||
|
return e.StartServer(e.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTLS starts an HTTPS server.
|
||||||
|
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
|
||||||
|
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
|
||||||
|
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
|
||||||
|
var cert []byte
|
||||||
|
if cert, err = filepathOrContent(certFile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var key []byte
|
||||||
|
if key, err = filepathOrContent(keyFile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := e.TLSServer
|
||||||
|
s.TLSConfig = new(tls.Config)
|
||||||
|
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||||
|
if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.startTLS(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
|
||||||
|
switch v := fileOrContent.(type) {
|
||||||
|
case string:
|
||||||
|
return ioutil.ReadFile(v)
|
||||||
|
case []byte:
|
||||||
|
return v, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrInvalidCertOrKeyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
|
||||||
|
func (e *Echo) StartAutoTLS(address string) error {
|
||||||
|
s := e.TLSServer
|
||||||
|
s.TLSConfig = new(tls.Config)
|
||||||
|
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
|
||||||
|
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
|
||||||
|
return e.startTLS(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Echo) startTLS(address string) error {
|
||||||
|
s := e.TLSServer
|
||||||
|
s.Addr = address
|
||||||
|
if !e.DisableHTTP2 {
|
||||||
|
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
|
||||||
|
}
|
||||||
|
return e.StartServer(e.TLSServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartServer starts a custom http server.
|
||||||
|
func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||||
|
// Setup
|
||||||
|
e.colorer.SetOutput(e.Logger.Output())
|
||||||
|
s.ErrorLog = e.StdLogger
|
||||||
|
s.Handler = e
|
||||||
|
if e.Debug {
|
||||||
|
e.Logger.SetLevel(log.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.HideBanner {
|
||||||
|
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TLSConfig == nil {
|
||||||
|
if e.Listener == nil {
|
||||||
|
e.Listener, err = newListener(s.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !e.HidePort {
|
||||||
|
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
|
||||||
|
}
|
||||||
|
return s.Serve(e.Listener)
|
||||||
|
}
|
||||||
|
if e.TLSListener == nil {
|
||||||
|
l, err := newListener(s.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.TLSListener = tls.NewListener(l, s.TLSConfig)
|
||||||
|
}
|
||||||
|
if !e.HidePort {
|
||||||
|
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
|
||||||
|
}
|
||||||
|
return s.Serve(e.TLSListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close immediately stops the server.
|
||||||
|
// It internally calls `http.Server#Close()`.
|
||||||
|
func (e *Echo) Close() error {
|
||||||
|
if err := e.TLSServer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Server.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stops the server gracefully.
|
||||||
|
// It internally calls `http.Server#Shutdown()`.
|
||||||
|
func (e *Echo) Shutdown(ctx stdContext.Context) error {
|
||||||
|
if err := e.TLSServer.Shutdown(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPError creates a new HTTPError instance.
|
||||||
|
func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
||||||
|
he := &HTTPError{Code: code, Message: http.StatusText(code)}
|
||||||
|
if len(message) > 0 {
|
||||||
|
he.Message = message[0]
|
||||||
|
}
|
||||||
|
return he
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error makes it compatible with `error` interface.
|
||||||
|
func (he *HTTPError) Error() string {
|
||||||
|
return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInternal sets error to HTTPError.Internal
|
||||||
|
func (he *HTTPError) SetInternal(err error) *HTTPError {
|
||||||
|
he.Internal = err
|
||||||
|
return he
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
|
||||||
|
func WrapHandler(h http.Handler) HandlerFunc {
|
||||||
|
return func(c Context) error {
|
||||||
|
h.ServeHTTP(c.Response(), c.Request())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
|
||||||
|
func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
|
||||||
|
return func(next HandlerFunc) HandlerFunc {
|
||||||
|
return func(c Context) (err error) {
|
||||||
|
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.SetRequest(r)
|
||||||
|
c.SetResponse(NewResponse(w, c.Echo()))
|
||||||
|
err = next(c)
|
||||||
|
})).ServeHTTP(c.Response(), c.Request())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPath(r *http.Request) string {
|
||||||
|
path := r.URL.RawPath
|
||||||
|
if path == "" {
|
||||||
|
path = r.URL.Path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Echo) findRouter(host string) *Router {
|
||||||
|
if len(e.routers) > 0 {
|
||||||
|
if r, ok := e.routers[host]; ok {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.router
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerName(h HandlerFunc) string {
|
||||||
|
t := reflect.ValueOf(h).Type()
|
||||||
|
if t.Kind() == reflect.Func {
|
||||||
|
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
|
||||||
|
}
|
||||||
|
return t.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// // PathUnescape is wraps `url.PathUnescape`
|
||||||
|
// func PathUnescape(s string) (string, error) {
|
||||||
|
// return url.PathUnescape(s)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||||
|
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||||
|
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||||
|
// go away.
|
||||||
|
type tcpKeepAliveListener struct {
|
||||||
|
*net.TCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||||
|
if c, err = ln.AcceptTCP(); err != nil {
|
||||||
|
return
|
||||||
|
} else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil {
|
||||||
|
return
|
||||||
|
} else if err = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListener(address string) (*tcpKeepAliveListener, error) {
|
||||||
|
l, err := net.Listen("tcp", address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
|
||||||
|
for i := len(middleware) - 1; i >= 0; i-- {
|
||||||
|
h = middleware[i](h)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
11
vendor/github.com/labstack/echo/v4/go.mod
generated
vendored
Normal file
11
vendor/github.com/labstack/echo/v4/go.mod
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module github.com/labstack/echo/v4
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
github.com/labstack/gommon v0.3.0
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
|
github.com/valyala/fasttemplate v1.0.1
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||||
|
)
|
34
vendor/github.com/labstack/echo/v4/go.sum
generated
vendored
Normal file
34
vendor/github.com/labstack/echo/v4/go.sum
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
124
vendor/github.com/labstack/echo/v4/group.go
generated
vendored
Normal file
124
vendor/github.com/labstack/echo/v4/group.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Group is a set of sub-routes for a specified route. It can be used for inner
|
||||||
|
// routes that share a common middleware or functionality that should be separate
|
||||||
|
// from the parent echo instance while still inheriting from it.
|
||||||
|
Group struct {
|
||||||
|
common
|
||||||
|
host string
|
||||||
|
prefix string
|
||||||
|
middleware []MiddlewareFunc
|
||||||
|
echo *Echo
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use implements `Echo#Use()` for sub-routes within the Group.
|
||||||
|
func (g *Group) Use(middleware ...MiddlewareFunc) {
|
||||||
|
g.middleware = append(g.middleware, middleware...)
|
||||||
|
if len(g.middleware) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Allow all requests to reach the group as they might get dropped if router
|
||||||
|
// doesn't find a match, making none of the group middleware process.
|
||||||
|
g.Any("", NotFoundHandler)
|
||||||
|
g.Any("/*", NotFoundHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
|
||||||
|
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodConnect, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
|
||||||
|
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodDelete, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET implements `Echo#GET()` for sub-routes within the Group.
|
||||||
|
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodGet, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
|
||||||
|
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodHead, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
|
||||||
|
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodOptions, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
|
||||||
|
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodPatch, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST implements `Echo#POST()` for sub-routes within the Group.
|
||||||
|
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodPost, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT implements `Echo#PUT()` for sub-routes within the Group.
|
||||||
|
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodPut, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
|
||||||
|
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||||
|
return g.Add(http.MethodTrace, path, h, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any implements `Echo#Any()` for sub-routes within the Group.
|
||||||
|
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||||
|
routes := make([]*Route, len(methods))
|
||||||
|
for i, m := range methods {
|
||||||
|
routes[i] = g.Add(m, path, handler, middleware...)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements `Echo#Match()` for sub-routes within the Group.
|
||||||
|
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||||
|
routes := make([]*Route, len(methods))
|
||||||
|
for i, m := range methods {
|
||||||
|
routes[i] = g.Add(m, path, handler, middleware...)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
|
||||||
|
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
|
||||||
|
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||||
|
m = append(m, g.middleware...)
|
||||||
|
m = append(m, middleware...)
|
||||||
|
sg = g.echo.Group(g.prefix+prefix, m...)
|
||||||
|
sg.host = g.host
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static implements `Echo#Static()` for sub-routes within the Group.
|
||||||
|
func (g *Group) Static(prefix, root string) {
|
||||||
|
g.static(prefix, root, g.GET)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File implements `Echo#File()` for sub-routes within the Group.
|
||||||
|
func (g *Group) File(path, file string) {
|
||||||
|
g.file(g.prefix+path, file, g.GET)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add implements `Echo#Add()` for sub-routes within the Group.
|
||||||
|
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||||
|
// Combine into a new slice to avoid accidentally passing the same slice for
|
||||||
|
// multiple routes, which would lead to later add() calls overwriting the
|
||||||
|
// middleware from earlier calls.
|
||||||
|
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||||
|
m = append(m, g.middleware...)
|
||||||
|
m = append(m, middleware...)
|
||||||
|
return g.echo.add(g.host, method, g.prefix+path, handler, m...)
|
||||||
|
}
|
41
vendor/github.com/labstack/echo/v4/log.go
generated
vendored
Normal file
41
vendor/github.com/labstack/echo/v4/log.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Logger defines the logging interface.
|
||||||
|
Logger interface {
|
||||||
|
Output() io.Writer
|
||||||
|
SetOutput(w io.Writer)
|
||||||
|
Prefix() string
|
||||||
|
SetPrefix(p string)
|
||||||
|
Level() log.Lvl
|
||||||
|
SetLevel(v log.Lvl)
|
||||||
|
SetHeader(h string)
|
||||||
|
Print(i ...interface{})
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
Printj(j log.JSON)
|
||||||
|
Debug(i ...interface{})
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
Debugj(j log.JSON)
|
||||||
|
Info(i ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
Infoj(j log.JSON)
|
||||||
|
Warn(i ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Warnj(j log.JSON)
|
||||||
|
Error(i ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Errorj(j log.JSON)
|
||||||
|
Fatal(i ...interface{})
|
||||||
|
Fatalj(j log.JSON)
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Panic(i ...interface{})
|
||||||
|
Panicj(j log.JSON)
|
||||||
|
Panicf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
)
|
104
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
Normal file
104
vendor/github.com/labstack/echo/v4/response.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Response wraps an http.ResponseWriter and implements its interface to be used
|
||||||
|
// by an HTTP handler to construct an HTTP response.
|
||||||
|
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||||
|
Response struct {
|
||||||
|
echo *Echo
|
||||||
|
beforeFuncs []func()
|
||||||
|
afterFuncs []func()
|
||||||
|
Writer http.ResponseWriter
|
||||||
|
Status int
|
||||||
|
Size int64
|
||||||
|
Committed bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewResponse creates a new instance of Response.
|
||||||
|
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
|
||||||
|
return &Response{Writer: w, echo: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns the header map for the writer that will be sent by
|
||||||
|
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
|
||||||
|
// no effect unless the modified headers were declared as trailers by setting
|
||||||
|
// the "Trailer" header before the call to WriteHeader (see example)
|
||||||
|
// To suppress implicit response headers, set their value to nil.
|
||||||
|
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||||
|
func (r *Response) Header() http.Header {
|
||||||
|
return r.Writer.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before registers a function which is called just before the response is written.
|
||||||
|
func (r *Response) Before(fn func()) {
|
||||||
|
r.beforeFuncs = append(r.beforeFuncs, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After registers a function which is called just after the response is written.
|
||||||
|
// If the `Content-Length` is unknown, none of the after function is executed.
|
||||||
|
func (r *Response) After(fn func()) {
|
||||||
|
r.afterFuncs = append(r.afterFuncs, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
|
||||||
|
// not called explicitly, the first call to Write will trigger an implicit
|
||||||
|
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
|
||||||
|
// used to send error codes.
|
||||||
|
func (r *Response) WriteHeader(code int) {
|
||||||
|
if r.Committed {
|
||||||
|
r.echo.Logger.Warn("response already committed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fn := range r.beforeFuncs {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
r.Status = code
|
||||||
|
r.Writer.WriteHeader(code)
|
||||||
|
r.Committed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the data to the connection as part of an HTTP reply.
|
||||||
|
func (r *Response) Write(b []byte) (n int, err error) {
|
||||||
|
if !r.Committed {
|
||||||
|
if r.Status == 0 {
|
||||||
|
r.Status = http.StatusOK
|
||||||
|
}
|
||||||
|
r.WriteHeader(r.Status)
|
||||||
|
}
|
||||||
|
n, err = r.Writer.Write(b)
|
||||||
|
r.Size += int64(n)
|
||||||
|
for _, fn := range r.afterFuncs {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
|
||||||
|
// buffered data to the client.
|
||||||
|
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
|
||||||
|
func (r *Response) Flush() {
|
||||||
|
r.Writer.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
|
||||||
|
// take over the connection.
|
||||||
|
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
|
||||||
|
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return r.Writer.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) reset(w http.ResponseWriter) {
|
||||||
|
r.beforeFuncs = nil
|
||||||
|
r.afterFuncs = nil
|
||||||
|
r.Writer = w
|
||||||
|
r.Size = 0
|
||||||
|
r.Status = http.StatusOK
|
||||||
|
r.Committed = false
|
||||||
|
}
|
443
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
Normal file
443
vendor/github.com/labstack/echo/v4/router.go
generated
vendored
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Router is the registry of all registered routes for an `Echo` instance for
|
||||||
|
// request matching and URL path parameter parsing.
|
||||||
|
Router struct {
|
||||||
|
tree *node
|
||||||
|
routes map[string]*Route
|
||||||
|
echo *Echo
|
||||||
|
}
|
||||||
|
node struct {
|
||||||
|
kind kind
|
||||||
|
label byte
|
||||||
|
prefix string
|
||||||
|
parent *node
|
||||||
|
children children
|
||||||
|
ppath string
|
||||||
|
pnames []string
|
||||||
|
methodHandler *methodHandler
|
||||||
|
}
|
||||||
|
kind uint8
|
||||||
|
children []*node
|
||||||
|
methodHandler struct {
|
||||||
|
connect HandlerFunc
|
||||||
|
delete HandlerFunc
|
||||||
|
get HandlerFunc
|
||||||
|
head HandlerFunc
|
||||||
|
options HandlerFunc
|
||||||
|
patch HandlerFunc
|
||||||
|
post HandlerFunc
|
||||||
|
propfind HandlerFunc
|
||||||
|
put HandlerFunc
|
||||||
|
trace HandlerFunc
|
||||||
|
report HandlerFunc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
skind kind = iota
|
||||||
|
pkind
|
||||||
|
akind
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter returns a new Router instance.
|
||||||
|
func NewRouter(e *Echo) *Router {
|
||||||
|
return &Router{
|
||||||
|
tree: &node{
|
||||||
|
methodHandler: new(methodHandler),
|
||||||
|
},
|
||||||
|
routes: map[string]*Route{},
|
||||||
|
echo: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add registers a new route for method and path with matching handler.
|
||||||
|
func (r *Router) Add(method, path string, h HandlerFunc) {
|
||||||
|
// Validate path
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
if path[0] != '/' {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
pnames := []string{} // Param names
|
||||||
|
ppath := path // Pristine path
|
||||||
|
|
||||||
|
for i, l := 0, len(path); i < l; i++ {
|
||||||
|
if path[i] == ':' {
|
||||||
|
j := i + 1
|
||||||
|
|
||||||
|
r.insert(method, path[:i], nil, skind, "", nil)
|
||||||
|
for ; i < l && path[i] != '/'; i++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
pnames = append(pnames, path[j:i])
|
||||||
|
path = path[:j] + path[i:]
|
||||||
|
i, l = j, len(path)
|
||||||
|
|
||||||
|
if i == l {
|
||||||
|
r.insert(method, path[:i], h, pkind, ppath, pnames)
|
||||||
|
} else {
|
||||||
|
r.insert(method, path[:i], nil, pkind, "", nil)
|
||||||
|
}
|
||||||
|
} else if path[i] == '*' {
|
||||||
|
r.insert(method, path[:i], nil, skind, "", nil)
|
||||||
|
pnames = append(pnames, "*")
|
||||||
|
r.insert(method, path[:i+1], h, akind, ppath, pnames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.insert(method, path, h, skind, ppath, pnames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
|
||||||
|
// Adjust max param
|
||||||
|
l := len(pnames)
|
||||||
|
if *r.echo.maxParam < l {
|
||||||
|
*r.echo.maxParam = l
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := r.tree // Current node as root
|
||||||
|
if cn == nil {
|
||||||
|
panic("echo: invalid method")
|
||||||
|
}
|
||||||
|
search := path
|
||||||
|
|
||||||
|
for {
|
||||||
|
sl := len(search)
|
||||||
|
pl := len(cn.prefix)
|
||||||
|
l := 0
|
||||||
|
|
||||||
|
// LCP
|
||||||
|
max := pl
|
||||||
|
if sl < max {
|
||||||
|
max = sl
|
||||||
|
}
|
||||||
|
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
if l == 0 {
|
||||||
|
// At root node
|
||||||
|
cn.label = search[0]
|
||||||
|
cn.prefix = search
|
||||||
|
if h != nil {
|
||||||
|
cn.kind = t
|
||||||
|
cn.addHandler(method, h)
|
||||||
|
cn.ppath = ppath
|
||||||
|
cn.pnames = pnames
|
||||||
|
}
|
||||||
|
} else if l < pl {
|
||||||
|
// Split node
|
||||||
|
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
|
||||||
|
|
||||||
|
// Reset parent node
|
||||||
|
cn.kind = skind
|
||||||
|
cn.label = cn.prefix[0]
|
||||||
|
cn.prefix = cn.prefix[:l]
|
||||||
|
cn.children = nil
|
||||||
|
cn.methodHandler = new(methodHandler)
|
||||||
|
cn.ppath = ""
|
||||||
|
cn.pnames = nil
|
||||||
|
|
||||||
|
cn.addChild(n)
|
||||||
|
|
||||||
|
if l == sl {
|
||||||
|
// At parent node
|
||||||
|
cn.kind = t
|
||||||
|
cn.addHandler(method, h)
|
||||||
|
cn.ppath = ppath
|
||||||
|
cn.pnames = pnames
|
||||||
|
} else {
|
||||||
|
// Create child node
|
||||||
|
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
|
||||||
|
n.addHandler(method, h)
|
||||||
|
cn.addChild(n)
|
||||||
|
}
|
||||||
|
} else if l < sl {
|
||||||
|
search = search[l:]
|
||||||
|
c := cn.findChildWithLabel(search[0])
|
||||||
|
if c != nil {
|
||||||
|
// Go deeper
|
||||||
|
cn = c
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Create child node
|
||||||
|
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
|
||||||
|
n.addHandler(method, h)
|
||||||
|
cn.addChild(n)
|
||||||
|
} else {
|
||||||
|
// Node already exists
|
||||||
|
if h != nil {
|
||||||
|
cn.addHandler(method, h)
|
||||||
|
cn.ppath = ppath
|
||||||
|
if len(cn.pnames) == 0 { // Issue #729
|
||||||
|
cn.pnames = pnames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
|
||||||
|
return &node{
|
||||||
|
kind: t,
|
||||||
|
label: pre[0],
|
||||||
|
prefix: pre,
|
||||||
|
parent: p,
|
||||||
|
children: c,
|
||||||
|
ppath: ppath,
|
||||||
|
pnames: pnames,
|
||||||
|
methodHandler: mh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) addChild(c *node) {
|
||||||
|
n.children = append(n.children, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) findChild(l byte, t kind) *node {
|
||||||
|
for _, c := range n.children {
|
||||||
|
if c.label == l && c.kind == t {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) findChildWithLabel(l byte) *node {
|
||||||
|
for _, c := range n.children {
|
||||||
|
if c.label == l {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) findChildByKind(t kind) *node {
|
||||||
|
for _, c := range n.children {
|
||||||
|
if c.kind == t {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) addHandler(method string, h HandlerFunc) {
|
||||||
|
switch method {
|
||||||
|
case http.MethodConnect:
|
||||||
|
n.methodHandler.connect = h
|
||||||
|
case http.MethodDelete:
|
||||||
|
n.methodHandler.delete = h
|
||||||
|
case http.MethodGet:
|
||||||
|
n.methodHandler.get = h
|
||||||
|
case http.MethodHead:
|
||||||
|
n.methodHandler.head = h
|
||||||
|
case http.MethodOptions:
|
||||||
|
n.methodHandler.options = h
|
||||||
|
case http.MethodPatch:
|
||||||
|
n.methodHandler.patch = h
|
||||||
|
case http.MethodPost:
|
||||||
|
n.methodHandler.post = h
|
||||||
|
case PROPFIND:
|
||||||
|
n.methodHandler.propfind = h
|
||||||
|
case http.MethodPut:
|
||||||
|
n.methodHandler.put = h
|
||||||
|
case http.MethodTrace:
|
||||||
|
n.methodHandler.trace = h
|
||||||
|
case REPORT:
|
||||||
|
n.methodHandler.report = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) findHandler(method string) HandlerFunc {
|
||||||
|
switch method {
|
||||||
|
case http.MethodConnect:
|
||||||
|
return n.methodHandler.connect
|
||||||
|
case http.MethodDelete:
|
||||||
|
return n.methodHandler.delete
|
||||||
|
case http.MethodGet:
|
||||||
|
return n.methodHandler.get
|
||||||
|
case http.MethodHead:
|
||||||
|
return n.methodHandler.head
|
||||||
|
case http.MethodOptions:
|
||||||
|
return n.methodHandler.options
|
||||||
|
case http.MethodPatch:
|
||||||
|
return n.methodHandler.patch
|
||||||
|
case http.MethodPost:
|
||||||
|
return n.methodHandler.post
|
||||||
|
case PROPFIND:
|
||||||
|
return n.methodHandler.propfind
|
||||||
|
case http.MethodPut:
|
||||||
|
return n.methodHandler.put
|
||||||
|
case http.MethodTrace:
|
||||||
|
return n.methodHandler.trace
|
||||||
|
case REPORT:
|
||||||
|
return n.methodHandler.report
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) checkMethodNotAllowed() HandlerFunc {
|
||||||
|
for _, m := range methods {
|
||||||
|
if h := n.findHandler(m); h != nil {
|
||||||
|
return MethodNotAllowedHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NotFoundHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find lookup a handler registered for method and path. It also parses URL for path
|
||||||
|
// parameters and load them into context.
|
||||||
|
//
|
||||||
|
// For performance:
|
||||||
|
//
|
||||||
|
// - Get context from `Echo#AcquireContext()`
|
||||||
|
// - Reset it `Context#Reset()`
|
||||||
|
// - Return it `Echo#ReleaseContext()`.
|
||||||
|
func (r *Router) Find(method, path string, c Context) {
|
||||||
|
ctx := c.(*context)
|
||||||
|
ctx.path = path
|
||||||
|
cn := r.tree // Current node as root
|
||||||
|
|
||||||
|
var (
|
||||||
|
search = path
|
||||||
|
child *node // Child node
|
||||||
|
n int // Param counter
|
||||||
|
nk kind // Next kind
|
||||||
|
nn *node // Next node
|
||||||
|
ns string // Next search
|
||||||
|
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search order static > param > any
|
||||||
|
for {
|
||||||
|
if search == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pl := 0 // Prefix length
|
||||||
|
l := 0 // LCP length
|
||||||
|
|
||||||
|
if cn.label != ':' {
|
||||||
|
sl := len(search)
|
||||||
|
pl = len(cn.prefix)
|
||||||
|
|
||||||
|
// LCP
|
||||||
|
max := pl
|
||||||
|
if sl < max {
|
||||||
|
max = sl
|
||||||
|
}
|
||||||
|
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if l == pl {
|
||||||
|
// Continue search
|
||||||
|
search = search[l:]
|
||||||
|
} else {
|
||||||
|
if nn == nil { // Issue #1348
|
||||||
|
return // Not found
|
||||||
|
}
|
||||||
|
cn = nn
|
||||||
|
search = ns
|
||||||
|
if nk == pkind {
|
||||||
|
goto Param
|
||||||
|
} else if nk == akind {
|
||||||
|
goto Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static node
|
||||||
|
if child = cn.findChild(search[0], skind); child != nil {
|
||||||
|
// Save next
|
||||||
|
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||||
|
nk = pkind
|
||||||
|
nn = cn
|
||||||
|
ns = search
|
||||||
|
}
|
||||||
|
cn = child
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param node
|
||||||
|
Param:
|
||||||
|
if child = cn.findChildByKind(pkind); child != nil {
|
||||||
|
// Issue #378
|
||||||
|
if len(pvalues) == n {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save next
|
||||||
|
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||||
|
nk = akind
|
||||||
|
nn = cn
|
||||||
|
ns = search
|
||||||
|
}
|
||||||
|
|
||||||
|
cn = child
|
||||||
|
i, l := 0, len(search)
|
||||||
|
for ; i < l && search[i] != '/'; i++ {
|
||||||
|
}
|
||||||
|
pvalues[n] = search[:i]
|
||||||
|
n++
|
||||||
|
search = search[i:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any node
|
||||||
|
Any:
|
||||||
|
if cn = cn.findChildByKind(akind); cn == nil {
|
||||||
|
if nn != nil {
|
||||||
|
cn = nn
|
||||||
|
nn = cn.parent // Next (Issue #954)
|
||||||
|
if nn != nil {
|
||||||
|
nk = nn.kind
|
||||||
|
}
|
||||||
|
search = ns
|
||||||
|
if nk == pkind {
|
||||||
|
goto Param
|
||||||
|
} else if nk == akind {
|
||||||
|
goto Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return // Not found
|
||||||
|
}
|
||||||
|
pvalues[len(cn.pnames)-1] = search
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.handler = cn.findHandler(method)
|
||||||
|
ctx.path = cn.ppath
|
||||||
|
ctx.pnames = cn.pnames
|
||||||
|
|
||||||
|
// NOTE: Slow zone...
|
||||||
|
if ctx.handler == nil {
|
||||||
|
ctx.handler = cn.checkMethodNotAllowed()
|
||||||
|
|
||||||
|
// Dig further for any, might have an empty value for *, e.g.
|
||||||
|
// serving a directory. Issue #207.
|
||||||
|
if cn = cn.findChildByKind(akind); cn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h := cn.findHandler(method); h != nil {
|
||||||
|
ctx.handler = h
|
||||||
|
} else {
|
||||||
|
ctx.handler = cn.checkMethodNotAllowed()
|
||||||
|
}
|
||||||
|
ctx.path = cn.ppath
|
||||||
|
ctx.pnames = cn.pnames
|
||||||
|
pvalues[len(cn.pnames)-1] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
22
vendor/github.com/labstack/gommon/LICENSE
generated
vendored
Normal file
22
vendor/github.com/labstack/gommon/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 labstack
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
86
vendor/github.com/labstack/gommon/color/README.md
generated
vendored
Normal file
86
vendor/github.com/labstack/gommon/color/README.md
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Color
|
||||||
|
|
||||||
|
Style terminal text.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/labstack/gommon/color
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows?
|
||||||
|
|
||||||
|
Try [cmder](http://bliker.github.io/cmder) or https://github.com/mattn/go-colorable
|
||||||
|
|
||||||
|
## [Usage](https://github.com/labstack/gommon/blob/master/color/color_test.go)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
import github.com/labstack/gommon/color
|
||||||
|
```
|
||||||
|
|
||||||
|
### Colored text
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.Println(color.Black("black"))
|
||||||
|
color.Println(color.Red("red"))
|
||||||
|
color.Println(color.Green("green"))
|
||||||
|
color.Println(color.Yellow("yellow"))
|
||||||
|
color.Println(color.Blue("blue"))
|
||||||
|
color.Println(color.Magenta("magenta"))
|
||||||
|
color.Println(color.Cyan("cyan"))
|
||||||
|
color.Println(color.White("white"))
|
||||||
|
color.Println(color.Grey("grey"))
|
||||||
|
```
|
||||||
|
![Colored Text](http://i.imgur.com/8RtY1QR.png)
|
||||||
|
|
||||||
|
### Colored background
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.Println(color.BlackBg("black background", color.Wht))
|
||||||
|
color.Println(color.RedBg("red background"))
|
||||||
|
color.Println(color.GreenBg("green background"))
|
||||||
|
color.Println(color.YellowBg("yellow background"))
|
||||||
|
color.Println(color.BlueBg("blue background"))
|
||||||
|
color.Println(color.MagentaBg("magenta background"))
|
||||||
|
color.Println(color.CyanBg("cyan background"))
|
||||||
|
color.Println(color.WhiteBg("white background"))
|
||||||
|
```
|
||||||
|
![Colored Background](http://i.imgur.com/SrrS6lw.png)
|
||||||
|
|
||||||
|
### Emphasis
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.Println(color.Bold("bold"))
|
||||||
|
color.Println(color.Dim("dim"))
|
||||||
|
color.Println(color.Italic("italic"))
|
||||||
|
color.Println(color.Underline("underline"))
|
||||||
|
color.Println(color.Inverse("inverse"))
|
||||||
|
color.Println(color.Hidden("hidden"))
|
||||||
|
color.Println(color.Strikeout("strikeout"))
|
||||||
|
```
|
||||||
|
![Emphasis](http://i.imgur.com/3RSJBbc.png)
|
||||||
|
|
||||||
|
### Mix and match
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.Println(color.Green("bold green with white background", color.B, color.WhtBg))
|
||||||
|
color.Println(color.Red("underline red", color.U))
|
||||||
|
color.Println(color.Yellow("dim yellow", color.D))
|
||||||
|
color.Println(color.Cyan("inverse cyan", color.In))
|
||||||
|
color.Println(color.Blue("bold underline dim blue", color.B, color.U, color.D))
|
||||||
|
```
|
||||||
|
![Mix and match](http://i.imgur.com/jWGq9Ca.png)
|
||||||
|
|
||||||
|
### Enable/Disable the package
|
||||||
|
|
||||||
|
```go
|
||||||
|
color.Disable()
|
||||||
|
color.Enable()
|
||||||
|
```
|
||||||
|
|
||||||
|
### New instance
|
||||||
|
|
||||||
|
```go
|
||||||
|
c := New()
|
||||||
|
c.Green("green")
|
||||||
|
```
|
407
vendor/github.com/labstack/gommon/color/color.go
generated
vendored
Normal file
407
vendor/github.com/labstack/gommon/color/color.go
generated
vendored
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
inner func(interface{}, []string, *Color) string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Color styles
|
||||||
|
const (
|
||||||
|
// Blk Black text style
|
||||||
|
Blk = "30"
|
||||||
|
// Rd red text style
|
||||||
|
Rd = "31"
|
||||||
|
// Grn green text style
|
||||||
|
Grn = "32"
|
||||||
|
// Yel yellow text style
|
||||||
|
Yel = "33"
|
||||||
|
// Blu blue text style
|
||||||
|
Blu = "34"
|
||||||
|
// Mgn magenta text style
|
||||||
|
Mgn = "35"
|
||||||
|
// Cyn cyan text style
|
||||||
|
Cyn = "36"
|
||||||
|
// Wht white text style
|
||||||
|
Wht = "37"
|
||||||
|
// Gry grey text style
|
||||||
|
Gry = "90"
|
||||||
|
|
||||||
|
// BlkBg black background style
|
||||||
|
BlkBg = "40"
|
||||||
|
// RdBg red background style
|
||||||
|
RdBg = "41"
|
||||||
|
// GrnBg green background style
|
||||||
|
GrnBg = "42"
|
||||||
|
// YelBg yellow background style
|
||||||
|
YelBg = "43"
|
||||||
|
// BluBg blue background style
|
||||||
|
BluBg = "44"
|
||||||
|
// MgnBg magenta background style
|
||||||
|
MgnBg = "45"
|
||||||
|
// CynBg cyan background style
|
||||||
|
CynBg = "46"
|
||||||
|
// WhtBg white background style
|
||||||
|
WhtBg = "47"
|
||||||
|
|
||||||
|
// R reset emphasis style
|
||||||
|
R = "0"
|
||||||
|
// B bold emphasis style
|
||||||
|
B = "1"
|
||||||
|
// D dim emphasis style
|
||||||
|
D = "2"
|
||||||
|
// I italic emphasis style
|
||||||
|
I = "3"
|
||||||
|
// U underline emphasis style
|
||||||
|
U = "4"
|
||||||
|
// In inverse emphasis style
|
||||||
|
In = "7"
|
||||||
|
// H hidden emphasis style
|
||||||
|
H = "8"
|
||||||
|
// S strikeout emphasis style
|
||||||
|
S = "9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
black = outer(Blk)
|
||||||
|
red = outer(Rd)
|
||||||
|
green = outer(Grn)
|
||||||
|
yellow = outer(Yel)
|
||||||
|
blue = outer(Blu)
|
||||||
|
magenta = outer(Mgn)
|
||||||
|
cyan = outer(Cyn)
|
||||||
|
white = outer(Wht)
|
||||||
|
grey = outer(Gry)
|
||||||
|
|
||||||
|
blackBg = outer(BlkBg)
|
||||||
|
redBg = outer(RdBg)
|
||||||
|
greenBg = outer(GrnBg)
|
||||||
|
yellowBg = outer(YelBg)
|
||||||
|
blueBg = outer(BluBg)
|
||||||
|
magentaBg = outer(MgnBg)
|
||||||
|
cyanBg = outer(CynBg)
|
||||||
|
whiteBg = outer(WhtBg)
|
||||||
|
|
||||||
|
reset = outer(R)
|
||||||
|
bold = outer(B)
|
||||||
|
dim = outer(D)
|
||||||
|
italic = outer(I)
|
||||||
|
underline = outer(U)
|
||||||
|
inverse = outer(In)
|
||||||
|
hidden = outer(H)
|
||||||
|
strikeout = outer(S)
|
||||||
|
|
||||||
|
global = New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func outer(n string) inner {
|
||||||
|
return func(msg interface{}, styles []string, c *Color) string {
|
||||||
|
// TODO: Drop fmt to boost performance?
|
||||||
|
if c.disabled {
|
||||||
|
return fmt.Sprintf("%v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
b.WriteString("\x1b[")
|
||||||
|
b.WriteString(n)
|
||||||
|
for _, s := range styles {
|
||||||
|
b.WriteString(";")
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
b.WriteString("m")
|
||||||
|
return fmt.Sprintf("%s%v\x1b[0m", b.String(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Color struct {
|
||||||
|
output io.Writer
|
||||||
|
disabled bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a Color instance.
|
||||||
|
func New() (c *Color) {
|
||||||
|
c = new(Color)
|
||||||
|
c.SetOutput(colorable.NewColorableStdout())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output returns the output.
|
||||||
|
func (c *Color) Output() io.Writer {
|
||||||
|
return c.output
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the output.
|
||||||
|
func (c *Color) SetOutput(w io.Writer) {
|
||||||
|
c.output = w
|
||||||
|
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
||||||
|
c.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable disables the colors and styles.
|
||||||
|
func (c *Color) Disable() {
|
||||||
|
c.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable enables the colors and styles.
|
||||||
|
func (c *Color) Enable() {
|
||||||
|
c.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is analogous to `fmt.Print` with termial detection.
|
||||||
|
func (c *Color) Print(args ...interface{}) {
|
||||||
|
fmt.Fprint(c.output, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is analogous to `fmt.Println` with termial detection.
|
||||||
|
func (c *Color) Println(args ...interface{}) {
|
||||||
|
fmt.Fprintln(c.output, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is analogous to `fmt.Printf` with termial detection.
|
||||||
|
func (c *Color) Printf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(c.output, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Black(msg interface{}, styles ...string) string {
|
||||||
|
return black(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Red(msg interface{}, styles ...string) string {
|
||||||
|
return red(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Green(msg interface{}, styles ...string) string {
|
||||||
|
return green(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Yellow(msg interface{}, styles ...string) string {
|
||||||
|
return yellow(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Blue(msg interface{}, styles ...string) string {
|
||||||
|
return blue(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Magenta(msg interface{}, styles ...string) string {
|
||||||
|
return magenta(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Cyan(msg interface{}, styles ...string) string {
|
||||||
|
return cyan(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) White(msg interface{}, styles ...string) string {
|
||||||
|
return white(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Grey(msg interface{}, styles ...string) string {
|
||||||
|
return grey(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) BlackBg(msg interface{}, styles ...string) string {
|
||||||
|
return blackBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) RedBg(msg interface{}, styles ...string) string {
|
||||||
|
return redBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) GreenBg(msg interface{}, styles ...string) string {
|
||||||
|
return greenBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) YellowBg(msg interface{}, styles ...string) string {
|
||||||
|
return yellowBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) BlueBg(msg interface{}, styles ...string) string {
|
||||||
|
return blueBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) MagentaBg(msg interface{}, styles ...string) string {
|
||||||
|
return magentaBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) CyanBg(msg interface{}, styles ...string) string {
|
||||||
|
return cyanBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) WhiteBg(msg interface{}, styles ...string) string {
|
||||||
|
return whiteBg(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Reset(msg interface{}, styles ...string) string {
|
||||||
|
return reset(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Bold(msg interface{}, styles ...string) string {
|
||||||
|
return bold(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Dim(msg interface{}, styles ...string) string {
|
||||||
|
return dim(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Italic(msg interface{}, styles ...string) string {
|
||||||
|
return italic(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Underline(msg interface{}, styles ...string) string {
|
||||||
|
return underline(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Inverse(msg interface{}, styles ...string) string {
|
||||||
|
return inverse(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Hidden(msg interface{}, styles ...string) string {
|
||||||
|
return hidden(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) Strikeout(msg interface{}, styles ...string) string {
|
||||||
|
return strikeout(msg, styles, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output returns the output.
|
||||||
|
func Output() io.Writer {
|
||||||
|
return global.output
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the output.
|
||||||
|
func SetOutput(w io.Writer) {
|
||||||
|
global.SetOutput(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Disable() {
|
||||||
|
global.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Enable() {
|
||||||
|
global.Enable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is analogous to `fmt.Print` with termial detection.
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
global.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is analogous to `fmt.Println` with termial detection.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
global.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is analogous to `fmt.Printf` with termial detection.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
global.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Black(msg interface{}, styles ...string) string {
|
||||||
|
return global.Black(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Red(msg interface{}, styles ...string) string {
|
||||||
|
return global.Red(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Green(msg interface{}, styles ...string) string {
|
||||||
|
return global.Green(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Yellow(msg interface{}, styles ...string) string {
|
||||||
|
return global.Yellow(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Blue(msg interface{}, styles ...string) string {
|
||||||
|
return global.Blue(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Magenta(msg interface{}, styles ...string) string {
|
||||||
|
return global.Magenta(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cyan(msg interface{}, styles ...string) string {
|
||||||
|
return global.Cyan(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func White(msg interface{}, styles ...string) string {
|
||||||
|
return global.White(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Grey(msg interface{}, styles ...string) string {
|
||||||
|
return global.Grey(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BlackBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.BlackBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RedBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.RedBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GreenBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.GreenBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func YellowBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.YellowBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BlueBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.BlueBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MagentaBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.MagentaBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CyanBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.CyanBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WhiteBg(msg interface{}, styles ...string) string {
|
||||||
|
return global.WhiteBg(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reset(msg interface{}, styles ...string) string {
|
||||||
|
return global.Reset(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bold(msg interface{}, styles ...string) string {
|
||||||
|
return global.Bold(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dim(msg interface{}, styles ...string) string {
|
||||||
|
return global.Dim(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Italic(msg interface{}, styles ...string) string {
|
||||||
|
return global.Italic(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Underline(msg interface{}, styles ...string) string {
|
||||||
|
return global.Underline(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Inverse(msg interface{}, styles ...string) string {
|
||||||
|
return global.Inverse(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hidden(msg interface{}, styles ...string) string {
|
||||||
|
return global.Hidden(msg, styles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Strikeout(msg interface{}, styles ...string) string {
|
||||||
|
return global.Strikeout(msg, styles...)
|
||||||
|
}
|
5
vendor/github.com/labstack/gommon/log/README.md
generated
vendored
Normal file
5
vendor/github.com/labstack/gommon/log/README.md
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
## WORK IN PROGRESS
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
[log_test.go](log_test.go)
|
13
vendor/github.com/labstack/gommon/log/color.go
generated
vendored
Normal file
13
vendor/github.com/labstack/gommon/log/color.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
)
|
||||||
|
|
||||||
|
func output() io.Writer {
|
||||||
|
return colorable.NewColorableStdout()
|
||||||
|
}
|
416
vendor/github.com/labstack/gommon/log/log.go
generated
vendored
Normal file
416
vendor/github.com/labstack/gommon/log/log.go
generated
vendored
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/valyala/fasttemplate"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Logger struct {
|
||||||
|
prefix string
|
||||||
|
level uint32
|
||||||
|
skip int
|
||||||
|
output io.Writer
|
||||||
|
template *fasttemplate.Template
|
||||||
|
levels []string
|
||||||
|
color *color.Color
|
||||||
|
bufferPool sync.Pool
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
Lvl uint8
|
||||||
|
|
||||||
|
JSON map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEBUG Lvl = iota + 1
|
||||||
|
INFO
|
||||||
|
WARN
|
||||||
|
ERROR
|
||||||
|
OFF
|
||||||
|
panicLevel
|
||||||
|
fatalLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
global = New("-")
|
||||||
|
defaultHeader = `{"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}",` +
|
||||||
|
`"file":"${short_file}","line":"${line}"}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
global.skip = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(prefix string) (l *Logger) {
|
||||||
|
l = &Logger{
|
||||||
|
level: uint32(INFO),
|
||||||
|
skip: 2,
|
||||||
|
prefix: prefix,
|
||||||
|
template: l.newTemplate(defaultHeader),
|
||||||
|
color: color.New(),
|
||||||
|
bufferPool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return bytes.NewBuffer(make([]byte, 256))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
l.initLevels()
|
||||||
|
l.SetOutput(output())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) initLevels() {
|
||||||
|
l.levels = []string{
|
||||||
|
"-",
|
||||||
|
l.color.Blue("DEBUG"),
|
||||||
|
l.color.Green("INFO"),
|
||||||
|
l.color.Yellow("WARN"),
|
||||||
|
l.color.Red("ERROR"),
|
||||||
|
"",
|
||||||
|
l.color.Yellow("PANIC", color.U),
|
||||||
|
l.color.Red("FATAL", color.U),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) newTemplate(format string) *fasttemplate.Template {
|
||||||
|
return fasttemplate.New(format, "${", "}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) DisableColor() {
|
||||||
|
l.color.Disable()
|
||||||
|
l.initLevels()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) EnableColor() {
|
||||||
|
l.color.Enable()
|
||||||
|
l.initLevels()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Prefix() string {
|
||||||
|
return l.prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetPrefix(p string) {
|
||||||
|
l.prefix = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Level() Lvl {
|
||||||
|
return Lvl(atomic.LoadUint32(&l.level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetLevel(level Lvl) {
|
||||||
|
atomic.StoreUint32(&l.level, uint32(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Output() io.Writer {
|
||||||
|
return l.output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetOutput(w io.Writer) {
|
||||||
|
l.output = w
|
||||||
|
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
||||||
|
l.DisableColor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Color() *color.Color {
|
||||||
|
return l.color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetHeader(h string) {
|
||||||
|
l.template = l.newTemplate(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Print(i ...interface{}) {
|
||||||
|
l.log(0, "", i...)
|
||||||
|
// fmt.Fprintln(l.output, i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
l.log(0, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Printj(j JSON) {
|
||||||
|
l.log(0, "json", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(i ...interface{}) {
|
||||||
|
l.log(DEBUG, "", i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
l.log(DEBUG, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debugj(j JSON) {
|
||||||
|
l.log(DEBUG, "json", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(i ...interface{}) {
|
||||||
|
l.log(INFO, "", i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
l.log(INFO, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Infoj(j JSON) {
|
||||||
|
l.log(INFO, "json", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(i ...interface{}) {
|
||||||
|
l.log(WARN, "", i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
l.log(WARN, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warnj(j JSON) {
|
||||||
|
l.log(WARN, "json", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(i ...interface{}) {
|
||||||
|
l.log(ERROR, "", i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
l.log(ERROR, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Errorj(j JSON) {
|
||||||
|
l.log(ERROR, "json", j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Fatal(i ...interface{}) {
|
||||||
|
l.log(fatalLevel, "", i...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
l.log(fatalLevel, format, args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Fatalj(j JSON) {
|
||||||
|
l.log(fatalLevel, "json", j)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Panic(i ...interface{}) {
|
||||||
|
l.log(panicLevel, "", i...)
|
||||||
|
panic(fmt.Sprint(i...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
l.log(panicLevel, format, args...)
|
||||||
|
panic(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Panicj(j JSON) {
|
||||||
|
l.log(panicLevel, "json", j)
|
||||||
|
panic(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableColor() {
|
||||||
|
global.DisableColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableColor() {
|
||||||
|
global.EnableColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Prefix() string {
|
||||||
|
return global.Prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetPrefix(p string) {
|
||||||
|
global.SetPrefix(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Level() Lvl {
|
||||||
|
return global.Level()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLevel(level Lvl) {
|
||||||
|
global.SetLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Output() io.Writer {
|
||||||
|
return global.Output()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetOutput(w io.Writer) {
|
||||||
|
global.SetOutput(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetHeader(h string) {
|
||||||
|
global.SetHeader(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Print(i ...interface{}) {
|
||||||
|
global.Print(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
global.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Printj(j JSON) {
|
||||||
|
global.Printj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(i ...interface{}) {
|
||||||
|
global.Debug(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
global.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugj(j JSON) {
|
||||||
|
global.Debugj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(i ...interface{}) {
|
||||||
|
global.Info(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
global.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infoj(j JSON) {
|
||||||
|
global.Infoj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(i ...interface{}) {
|
||||||
|
global.Warn(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
global.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnj(j JSON) {
|
||||||
|
global.Warnj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(i ...interface{}) {
|
||||||
|
global.Error(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
global.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorj(j JSON) {
|
||||||
|
global.Errorj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(i ...interface{}) {
|
||||||
|
global.Fatal(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
global.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalj(j JSON) {
|
||||||
|
global.Fatalj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panic(i ...interface{}) {
|
||||||
|
global.Panic(i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
global.Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panicj(j JSON) {
|
||||||
|
global.Panicj(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) log(level Lvl, format string, args ...interface{}) {
|
||||||
|
if level >= l.Level() || level == 0 {
|
||||||
|
buf := l.bufferPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
defer l.bufferPool.Put(buf)
|
||||||
|
_, file, line, _ := runtime.Caller(l.skip)
|
||||||
|
message := ""
|
||||||
|
|
||||||
|
if format == "" {
|
||||||
|
message = fmt.Sprint(args...)
|
||||||
|
} else if format == "json" {
|
||||||
|
b, err := json.Marshal(args[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
message = string(b)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := l.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
|
||||||
|
switch tag {
|
||||||
|
case "time_rfc3339":
|
||||||
|
return w.Write([]byte(time.Now().Format(time.RFC3339)))
|
||||||
|
case "time_rfc3339_nano":
|
||||||
|
return w.Write([]byte(time.Now().Format(time.RFC3339Nano)))
|
||||||
|
case "level":
|
||||||
|
return w.Write([]byte(l.levels[level]))
|
||||||
|
case "prefix":
|
||||||
|
return w.Write([]byte(l.prefix))
|
||||||
|
case "long_file":
|
||||||
|
return w.Write([]byte(file))
|
||||||
|
case "short_file":
|
||||||
|
return w.Write([]byte(path.Base(file)))
|
||||||
|
case "line":
|
||||||
|
return w.Write([]byte(strconv.Itoa(line)))
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
s := buf.String()
|
||||||
|
i := buf.Len() - 1
|
||||||
|
if s[i] == '}' {
|
||||||
|
// JSON header
|
||||||
|
buf.Truncate(i)
|
||||||
|
buf.WriteByte(',')
|
||||||
|
if format == "json" {
|
||||||
|
buf.WriteString(message[1:])
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`"message":`)
|
||||||
|
buf.WriteString(strconv.Quote(message))
|
||||||
|
buf.WriteString(`}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Text header
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
buf.WriteString(message)
|
||||||
|
}
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
l.output.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
vendor/github.com/labstack/gommon/log/white.go
generated
vendored
Normal file
12
vendor/github.com/labstack/gommon/log/white.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func output() io.Writer {
|
||||||
|
return os.Stdout
|
||||||
|
}
|
205
vendor/github.com/likexian/gokit/LICENSE
generated
vendored
Normal file
205
vendor/github.com/likexian/gokit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2012-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
APPENDIX: Copyright 2012-2019 Li Kexian
|
||||||
|
|
||||||
|
https://www.likexian.com/
|
98
vendor/github.com/likexian/gokit/assert/README.md
generated
vendored
Normal file
98
vendor/github.com/likexian/gokit/assert/README.md
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# GoKit - assert
|
||||||
|
|
||||||
|
Assert kits for Golang development.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/likexian/gokit
|
||||||
|
|
||||||
|
## Importing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/likexian/gokit/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/assert)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
### assert panic
|
||||||
|
|
||||||
|
```go
|
||||||
|
func willItPanic() {
|
||||||
|
panic("failed")
|
||||||
|
}
|
||||||
|
assert.Panic(t, willItPanic)
|
||||||
|
```
|
||||||
|
|
||||||
|
### assert err is nil
|
||||||
|
|
||||||
|
```go
|
||||||
|
fp, err := os.Open("/data/dev/gokit/LICENSE")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
```
|
||||||
|
|
||||||
|
### assert equal
|
||||||
|
|
||||||
|
```go
|
||||||
|
x := map[string]int{"a": 1, "b": 2}
|
||||||
|
y := map[string]int{"a": 1, "b": 2}
|
||||||
|
assert.Equal(t, x, y, "x shall equal to y")
|
||||||
|
```
|
||||||
|
|
||||||
|
### check string in array
|
||||||
|
|
||||||
|
```go
|
||||||
|
ok := assert.IsContains([]string{"a", "b", "c"}, "b")
|
||||||
|
if ok {
|
||||||
|
fmt.Println("value in array")
|
||||||
|
} else {
|
||||||
|
fmt.Println("value not in array")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### check string in interface array
|
||||||
|
|
||||||
|
```go
|
||||||
|
ok := assert.IsContains([]interface{}{0, "1", 2}, "1")
|
||||||
|
if ok {
|
||||||
|
fmt.Println("value in array")
|
||||||
|
} else {
|
||||||
|
fmt.Println("value not in array")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### check object in struct array
|
||||||
|
|
||||||
|
```go
|
||||||
|
ok := assert.IsContains([]A{A{0, 1}, A{1, 2}, A{1, 3}}, A{1, 2})
|
||||||
|
if ok {
|
||||||
|
fmt.Println("value in array")
|
||||||
|
} else {
|
||||||
|
fmt.Println("value not in array")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### a := c ? x : y
|
||||||
|
|
||||||
|
```go
|
||||||
|
a := 1
|
||||||
|
// b := a == 1 ? true : false
|
||||||
|
b := assert.If(a == 1, true, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
Copyright 2012-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License 2.0
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
- [Li Kexian](https://www.likexian.com/)
|
||||||
|
|
||||||
|
## DONATE
|
||||||
|
|
||||||
|
- [Help me make perfect](https://www.likexian.com/donate/)
|
202
vendor/github.com/likexian/gokit/assert/assert.go
generated
vendored
Normal file
202
vendor/github.com/likexian/gokit/assert/assert.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* A toolkit for Golang development
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns package version
|
||||||
|
func Version() string {
|
||||||
|
return "0.10.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author returns package author
|
||||||
|
func Author() string {
|
||||||
|
return "[Li Kexian](https://www.likexian.com/)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// License returns package license
|
||||||
|
func License() string {
|
||||||
|
return "Licensed under the Apache License 2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal assert test value to be equal
|
||||||
|
func Equal(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, got, exp, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqual assert test value to be not equal
|
||||||
|
func NotEqual(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, got, exp, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nil assert test value to be nil
|
||||||
|
func Nil(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
equal(t, got, nil, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotNil assert test value to be not nil
|
||||||
|
func NotNil(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, got, nil, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// True assert test value to be true
|
||||||
|
func True(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
equal(t, got, true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// False assert test value to be false
|
||||||
|
func False(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, got, true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero assert test value to be zero value
|
||||||
|
func Zero(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsZero(got), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotZero assert test value to be not zero value
|
||||||
|
func NotZero(t *testing.T, got interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, IsZero(got), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len assert length of test vaue to be exp
|
||||||
|
func Len(t *testing.T, got interface{}, exp int, args ...interface{}) {
|
||||||
|
equal(t, Length(got), exp, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotLen assert length of test vaue to be not exp
|
||||||
|
func NotLen(t *testing.T, got interface{}, exp int, args ...interface{}) {
|
||||||
|
notEqual(t, Length(got), exp, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains assert test value to be contains
|
||||||
|
func Contains(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsContains(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotContains assert test value to be contains
|
||||||
|
func NotContains(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, IsContains(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match assert test value match exp pattern
|
||||||
|
func Match(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsMatch(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotMatch assert test value not match exp pattern
|
||||||
|
func NotMatch(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
notEqual(t, IsMatch(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lt assert test value less than exp
|
||||||
|
func Lt(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsLt(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Le assert test value less than exp or equal
|
||||||
|
func Le(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsLe(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gt assert test value greater than exp
|
||||||
|
func Gt(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsGt(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ge assert test value greater than exp or equal
|
||||||
|
func Ge(t *testing.T, got, exp interface{}, args ...interface{}) {
|
||||||
|
equal(t, IsGe(got, exp), true, 1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic assert testing to be panic
|
||||||
|
func Panic(t *testing.T, fn func(), args ...interface{}) {
|
||||||
|
defer func() {
|
||||||
|
ff := func() {
|
||||||
|
t.Error("! -", "assert expected to be panic")
|
||||||
|
if len(args) > 0 {
|
||||||
|
t.Error("! -", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok := recover() != nil
|
||||||
|
assert(t, ok, ff, 2)
|
||||||
|
}()
|
||||||
|
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotPanic assert testing to be panic
|
||||||
|
func NotPanic(t *testing.T, fn func(), args ...interface{}) {
|
||||||
|
defer func() {
|
||||||
|
ff := func() {
|
||||||
|
t.Error("! -", "assert expected to be not panic")
|
||||||
|
if len(args) > 0 {
|
||||||
|
t.Error("! -", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok := recover() == nil
|
||||||
|
assert(t, ok, ff, 3)
|
||||||
|
}()
|
||||||
|
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func equal(t *testing.T, got, exp interface{}, step int, args ...interface{}) {
|
||||||
|
fn := func() {
|
||||||
|
switch got.(type) {
|
||||||
|
case error:
|
||||||
|
t.Errorf("! unexpected error: \"%s\"", got)
|
||||||
|
default:
|
||||||
|
t.Errorf("! expected %#v, but got %#v", exp, got)
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
t.Error("! -", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok := reflect.DeepEqual(exp, got)
|
||||||
|
assert(t, ok, fn, step+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notEqual(t *testing.T, got, exp interface{}, step int, args ...interface{}) {
|
||||||
|
fn := func() {
|
||||||
|
t.Errorf("! unexpected: %#v", got)
|
||||||
|
if len(args) > 0 {
|
||||||
|
t.Error("! -", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok := !reflect.DeepEqual(exp, got)
|
||||||
|
assert(t, ok, fn, step+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assert(t *testing.T, pass bool, fn func(), step int) {
|
||||||
|
if !pass {
|
||||||
|
_, file, line, ok := runtime.Caller(step + 1)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("%s:%d", file, line)
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
334
vendor/github.com/likexian/gokit/assert/values.go
generated
vendored
Normal file
334
vendor/github.com/likexian/gokit/assert/values.go
generated
vendored
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* A toolkit for Golang development
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalid is value invalid for operation
|
||||||
|
var ErrInvalid = errors.New("value if invalid")
|
||||||
|
|
||||||
|
// ErrLess is expect to be greater error
|
||||||
|
var ErrLess = errors.New("left is less the right")
|
||||||
|
|
||||||
|
// ErrGreater is expect to be less error
|
||||||
|
var ErrGreater = errors.New("left is greater then right")
|
||||||
|
|
||||||
|
// CMP is compare operation
|
||||||
|
var CMP = struct {
|
||||||
|
LT string
|
||||||
|
LE string
|
||||||
|
GT string
|
||||||
|
GE string
|
||||||
|
}{
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">",
|
||||||
|
">=",
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns value is zero value
|
||||||
|
func IsZero(v interface{}) bool {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return true
|
||||||
|
case reflect.Bool:
|
||||||
|
return !vv.Bool()
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return vv.IsNil()
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
|
return vv.Len() == 0
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return vv.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return vv.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return vv.Float() == 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsContains returns whether value is within array
|
||||||
|
func IsContains(array interface{}, value interface{}) bool {
|
||||||
|
vv := reflect.ValueOf(array)
|
||||||
|
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
|
||||||
|
if vv.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return false
|
||||||
|
case reflect.Slice:
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
if reflect.DeepEqual(value, vv.Index(i).Interface()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Map:
|
||||||
|
s := vv.MapKeys()
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if reflect.DeepEqual(value, s[i].Interface()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.String:
|
||||||
|
ss := reflect.ValueOf(value)
|
||||||
|
switch ss.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return strings.Contains(vv.String(), ss.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return reflect.DeepEqual(array, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMatch returns if value v contains any match of pattern r
|
||||||
|
// IsMatch(regexp.MustCompile("v\d+"), "v100")
|
||||||
|
// IsMatch("v\d+", "v100")
|
||||||
|
// IsMatch("\d+\.\d+", 100.1)
|
||||||
|
func IsMatch(r interface{}, v interface{}) bool {
|
||||||
|
var re *regexp.Regexp
|
||||||
|
|
||||||
|
if v, ok := r.(*regexp.Regexp); ok {
|
||||||
|
re = v
|
||||||
|
} else {
|
||||||
|
re = regexp.MustCompile(fmt.Sprint(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
return re.MatchString(fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length returns length of value
|
||||||
|
func Length(v interface{}) int {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
|
||||||
|
if vv.IsNil() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return 0
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return 0
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
|
return vv.Len()
|
||||||
|
default:
|
||||||
|
return len(fmt.Sprintf("%#v", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLt returns if x less than y, value invalid will returns false
|
||||||
|
func IsLt(x, y interface{}) bool {
|
||||||
|
return Compare(x, y, CMP.LT) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLe returns if x less than or equal to y, value invalid will returns false
|
||||||
|
func IsLe(x, y interface{}) bool {
|
||||||
|
return Compare(x, y, CMP.LE) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGt returns if x greater than y, value invalid will returns false
|
||||||
|
func IsGt(x, y interface{}) bool {
|
||||||
|
return Compare(x, y, CMP.GT) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGe returns if x greater than or equal to y, value invalid will returns false
|
||||||
|
func IsGe(x, y interface{}) bool {
|
||||||
|
return Compare(x, y, CMP.GE) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare compare x and y, by operation
|
||||||
|
// It returns nil for true, ErrInvalid for invalid operation, err for false
|
||||||
|
// Compare(1, 2, ">") // number compare -> true
|
||||||
|
// Compare("a", "a", ">=") // string compare -> true
|
||||||
|
// Compare([]string{"a", "b"}, []string{"a"}, "<") // slice len compare -> false
|
||||||
|
func Compare(x, y interface{}, op string) error {
|
||||||
|
if !IsContains([]string{CMP.LT, CMP.LE, CMP.GT, CMP.GE}, op) {
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := reflect.ValueOf(x)
|
||||||
|
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
|
||||||
|
if vv.IsNil() {
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
var c float64
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return ErrInvalid
|
||||||
|
case reflect.String:
|
||||||
|
yy := reflect.ValueOf(y)
|
||||||
|
switch yy.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
c = float64(strings.Compare(vv.String(), yy.String()))
|
||||||
|
default:
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
yy := reflect.ValueOf(y)
|
||||||
|
switch yy.Kind() {
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
c = float64(vv.Len() - yy.Len())
|
||||||
|
default:
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
yy, err := ToInt64(y)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
c = float64(vv.Int() - yy)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
yy, err := ToUint64(y)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
c = float64(vv.Uint()) - float64(yy)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
yy, err := ToFloat64(y)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
c = float64(vv.Float() - yy)
|
||||||
|
default:
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c < 0:
|
||||||
|
switch op {
|
||||||
|
case CMP.LT, CMP.LE:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrLess
|
||||||
|
}
|
||||||
|
case c > 0:
|
||||||
|
switch op {
|
||||||
|
case CMP.GT, CMP.GE:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrGreater
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
switch op {
|
||||||
|
case CMP.LT:
|
||||||
|
return ErrGreater
|
||||||
|
case CMP.GT:
|
||||||
|
return ErrLess
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64 returns int value for int or uint or float
|
||||||
|
func ToInt64(v interface{}) (int64, error) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return int64(vv.Int()), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return int64(vv.Uint()), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return int64(vv.Float()), nil
|
||||||
|
case reflect.String:
|
||||||
|
r, err := strconv.ParseInt(vv.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
default:
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64 returns uint value for int or uint or float
|
||||||
|
func ToUint64(v interface{}) (uint64, error) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return uint64(vv.Int()), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return uint64(vv.Uint()), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return uint64(vv.Float()), nil
|
||||||
|
case reflect.String:
|
||||||
|
r, err := strconv.ParseUint(vv.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
default:
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 returns float64 value for int or uint or float
|
||||||
|
func ToFloat64(v interface{}) (float64, error) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return float64(vv.Int()), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return float64(vv.Uint()), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return float64(vv.Float()), nil
|
||||||
|
case reflect.String:
|
||||||
|
r, err := strconv.ParseFloat(vv.String(), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
default:
|
||||||
|
return 0, ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If returns x if c is true, else y
|
||||||
|
// z = If(c, x, y)
|
||||||
|
// equal to:
|
||||||
|
// z = c ? x : y
|
||||||
|
func If(c bool, x, y interface{}) interface{} {
|
||||||
|
if c {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
return y
|
||||||
|
}
|
47
vendor/github.com/likexian/gokit/xslice/README.md
generated
vendored
Normal file
47
vendor/github.com/likexian/gokit/xslice/README.md
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# GoKit - xslice
|
||||||
|
|
||||||
|
Slice kits for Golang development.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/likexian/gokit
|
||||||
|
|
||||||
|
## Importing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/likexian/gokit/xslice"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xslice)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
### Get unique of string array
|
||||||
|
|
||||||
|
```go
|
||||||
|
array := xslice.Unique([]string{"a", "a", "b", "b", "b", "c"})
|
||||||
|
fmt.Println("new array:", array)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get unique of int array
|
||||||
|
|
||||||
|
```go
|
||||||
|
array := xslice.Unique([]int{0, 0, 1, 1, 1, 2, 2, 3})
|
||||||
|
fmt.Println("new array:", array)
|
||||||
|
```
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
Copyright 2012-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License 2.0
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
- [Li Kexian](https://www.likexian.com/)
|
||||||
|
|
||||||
|
## DONATE
|
||||||
|
|
||||||
|
- [Help me make perfect](https://www.likexian.com/donate/)
|
379
vendor/github.com/likexian/gokit/xslice/xslice.go
generated
vendored
Normal file
379
vendor/github.com/likexian/gokit/xslice/xslice.go
generated
vendored
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* A toolkit for Golang development
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xslice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns package version
|
||||||
|
func Version() string {
|
||||||
|
return "0.21.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author returns package author
|
||||||
|
func Author() string {
|
||||||
|
return "[Li Kexian](https://www.likexian.com/)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// License returns package license
|
||||||
|
func License() string {
|
||||||
|
return "Licensed under the Apache License 2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSlice returns whether value is slice
|
||||||
|
func IsSlice(v interface{}) bool {
|
||||||
|
return reflect.ValueOf(v).Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique returns unique values of slice
|
||||||
|
func Unique(v interface{}) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make(map[interface{}]bool)
|
||||||
|
r := reflect.MakeSlice(reflect.TypeOf(v), 0, vv.Cap())
|
||||||
|
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
hv := hashValue(vv.Index(i).Interface())
|
||||||
|
if _, ok := h[hv]; !ok {
|
||||||
|
h[hv] = true
|
||||||
|
r = reflect.Append(r, vv.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUnique returns whether slice values is unique
|
||||||
|
func IsUnique(v interface{}) bool {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv.Len() <= 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make(map[interface{}]bool)
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
hv := hashValue(vv.Index(i).Interface())
|
||||||
|
if _, ok := h[hv]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h[hv] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect returns values in both slices
|
||||||
|
func Intersect(x, y interface{}) interface{} {
|
||||||
|
xx := reflect.ValueOf(x)
|
||||||
|
if xx.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: x expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
yy := reflect.ValueOf(y)
|
||||||
|
if yy.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: y expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make(map[interface{}]bool)
|
||||||
|
for i := 0; i < xx.Len(); i++ {
|
||||||
|
h[hashValue(xx.Index(i).Interface())] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.TypeOf(x), 0, 0)
|
||||||
|
for i := 0; i < yy.Len(); i++ {
|
||||||
|
if _, ok := h[hashValue(yy.Index(i).Interface())]; ok {
|
||||||
|
r = reflect.Append(r, yy.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different returns values in x but not in y
|
||||||
|
func Different(x, y interface{}) interface{} {
|
||||||
|
xx := reflect.ValueOf(x)
|
||||||
|
if xx.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: x expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
yy := reflect.ValueOf(y)
|
||||||
|
if yy.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: y expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make(map[interface{}]bool)
|
||||||
|
for i := 0; i < yy.Len(); i++ {
|
||||||
|
h[hashValue(yy.Index(i).Interface())] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.TypeOf(x), 0, 0)
|
||||||
|
for i := 0; i < xx.Len(); i++ {
|
||||||
|
if _, ok := h[hashValue(xx.Index(i).Interface())]; !ok {
|
||||||
|
r = reflect.Append(r, xx.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge append y values to x if not exists in
|
||||||
|
func Merge(x, y interface{}) interface{} {
|
||||||
|
xx := reflect.ValueOf(x)
|
||||||
|
if xx.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: x expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
yy := reflect.ValueOf(y)
|
||||||
|
if yy.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: y expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make(map[interface{}]bool)
|
||||||
|
for i := 0; i < xx.Len(); i++ {
|
||||||
|
h[hashValue(xx.Index(i).Interface())] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r := xx.Slice(0, xx.Len())
|
||||||
|
for i := 0; i < yy.Len(); i++ {
|
||||||
|
if _, ok := h[hashValue(yy.Index(i).Interface())]; !ok {
|
||||||
|
r = reflect.Append(r, yy.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse returns a slice with elements in reverse order
|
||||||
|
func Reverse(v interface{}) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := reflect.Swapper(v)
|
||||||
|
for i, j := 0, vv.Len()-1; i < j; i, j = i+1, j-1 {
|
||||||
|
swap(i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle shuffle a slice
|
||||||
|
func Shuffle(v interface{}) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := reflect.Swapper(v)
|
||||||
|
for i := vv.Len() - 1; i >= 1; i-- {
|
||||||
|
j := rand.Intn(i + 1)
|
||||||
|
swap(i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill returns a slice with count number of v values
|
||||||
|
func Fill(v interface{}, count int) interface{} {
|
||||||
|
if count <= 0 {
|
||||||
|
panic("xslice: count expected to be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(v)), 0, 0)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
r = reflect.Append(r, reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk split slice into chunks
|
||||||
|
func Chunk(v interface{}, size int) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
panic("xslice: size expected to be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(math.Ceil(float64(vv.Len()) / float64(size)))
|
||||||
|
r := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(v)), 0, 0)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
rr := reflect.MakeSlice(reflect.TypeOf(v), 0, 0)
|
||||||
|
for j := 0; j < size; j++ {
|
||||||
|
if i*size+j >= vv.Len() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rr = reflect.Append(rr, vv.Index(i*size+j))
|
||||||
|
}
|
||||||
|
r = reflect.Append(r, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concat returns a new flatten slice of []slice
|
||||||
|
func Concat(v interface{}) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv.Len() == 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
vt := reflect.TypeOf(v)
|
||||||
|
if vt.Elem().Kind() != reflect.Slice {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.TypeOf(v).Elem(), 0, 0)
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
for j := 0; j < vv.Index(i).Len(); j++ {
|
||||||
|
r = reflect.Append(r, vv.Index(i).Index(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter filter slice values using callback function fn
|
||||||
|
func Filter(v interface{}, fn interface{}) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := CheckIsFunc(fn, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
panic("xslice: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fv := reflect.ValueOf(fn)
|
||||||
|
ot := fv.Type().Out(0)
|
||||||
|
if ot.Kind() != reflect.Bool {
|
||||||
|
panic("xslice: fn expected to return bool but got " + ot.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.TypeOf(v), 0, 0)
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
if fv.Call([]reflect.Value{vv.Index(i)})[0].Interface().(bool) {
|
||||||
|
r = reflect.Append(r, vv.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map apply callback function fn to elements of slice
|
||||||
|
func Map(v interface{}, fn interface{}) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := CheckIsFunc(fn, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
panic("xslice: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fv := reflect.ValueOf(fn)
|
||||||
|
ot := fv.Type().Out(0)
|
||||||
|
|
||||||
|
r := reflect.MakeSlice(reflect.SliceOf(ot), 0, 0)
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
r = reflect.Append(r, fv.Call([]reflect.Value{vv.Index(i)})[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce reduce the slice values using callback function fn
|
||||||
|
func Reduce(v interface{}, fn interface{}) interface{} {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if vv.Kind() != reflect.Slice {
|
||||||
|
panic("xslice: v expected to be a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv.Len() == 0 {
|
||||||
|
panic("xslice: v expected to be not empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := CheckIsFunc(fn, 2, 1)
|
||||||
|
if err != nil {
|
||||||
|
panic("xslice: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fv := reflect.ValueOf(fn)
|
||||||
|
if vv.Type().Elem() != fv.Type().In(0) || vv.Type().Elem() != fv.Type().In(1) {
|
||||||
|
panic(fmt.Sprintf("xslice: fn expected to have (%s, %s) arguments but got (%s, %s)",
|
||||||
|
vv.Type().Elem(), vv.Type().Elem(), fv.Type().In(0), fv.Type().In(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv.Type().Elem() != fv.Type().Out(0) {
|
||||||
|
panic(fmt.Sprintf("xslice: fn expected to return %s but got %s",
|
||||||
|
vv.Type().Elem(), fv.Type().Out(0).String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
r := vv.Index(0)
|
||||||
|
for i := 1; i < vv.Len(); i++ {
|
||||||
|
r = fv.Call([]reflect.Value{r, vv.Index(i)})[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckIsFunc check if fn is a function with n[0] arguments and n[1] returns
|
||||||
|
func CheckIsFunc(fn interface{}, n ...int) error {
|
||||||
|
if fn == nil {
|
||||||
|
return fmt.Errorf("fn excepted to be a function but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ft := reflect.TypeOf(fn)
|
||||||
|
if ft.Kind() != reflect.Func {
|
||||||
|
return fmt.Errorf("fn excepted to be a function but got " + ft.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n) >= 1 && n[0] != ft.NumIn() {
|
||||||
|
return fmt.Errorf("fn expected to have %d arguments but got %d", n[0], ft.NumIn())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n) >= 2 && n[1] != ft.NumOut() {
|
||||||
|
return fmt.Errorf("fn expected to have %d returns but got %d", n[1], ft.NumOut())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashValue returns a hashable value
|
||||||
|
func hashValue(x interface{}) interface{} {
|
||||||
|
return fmt.Sprintf("%#v", x)
|
||||||
|
}
|
1
vendor/github.com/likexian/whois-go/.gitignore
generated
vendored
Normal file
1
vendor/github.com/likexian/whois-go/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
7
vendor/github.com/likexian/whois-go/.travis.yml
generated
vendored
Normal file
7
vendor/github.com/likexian/whois-go/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
205
vendor/github.com/likexian/whois-go/LICENSE
generated
vendored
Normal file
205
vendor/github.com/likexian/whois-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
APPENDIX: Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
https://www.likexian.com/
|
69
vendor/github.com/likexian/whois-go/README.md
generated
vendored
Normal file
69
vendor/github.com/likexian/whois-go/README.md
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# whois.go
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/likexian/whois-go?status.svg)](https://godoc.org/github.com/likexian/whois-go)
|
||||||
|
[![Build Status](https://travis-ci.org/likexian/whois-go.svg?branch=master)](https://travis-ci.org/likexian/whois-go)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/likexian/whois-go)](https://goreportcard.com/report/github.com/likexian/whois-go)
|
||||||
|
[![Code Cover](https://codecov.io/gh/likexian/whois-go/graph/badge.svg)](https://codecov.io/gh/likexian/whois-go)
|
||||||
|
|
||||||
|
whois-go is a simple Go module for domain and ip whois info query.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
You can directly using the binary distributions whois, follow [whois release tool](cmd/whois).
|
||||||
|
|
||||||
|
Or you can do development by using the golang module as below.
|
||||||
|
|
||||||
|
*Works for most domain extensions and most ip most of the time.*
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/likexian/whois-go
|
||||||
|
|
||||||
|
## Importing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/likexian/whois-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/whois-go)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
### whois query for domain
|
||||||
|
|
||||||
|
```go
|
||||||
|
result, err := whois.Whois("example.com")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### whois query for ip
|
||||||
|
|
||||||
|
```go
|
||||||
|
result, err := whois.Whois("1.1.1.1")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Whois info parser in Go
|
||||||
|
|
||||||
|
Please refer to [whois-parser-go](https://github.com/likexian/whois-parser-go)
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License 2.0
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
- [Li Kexian](https://www.likexian.com/)
|
||||||
|
|
||||||
|
## DONATE
|
||||||
|
|
||||||
|
- [Help me make perfect](https://www.likexian.com/donate/)
|
5
vendor/github.com/likexian/whois-go/go.mod
generated
vendored
Normal file
5
vendor/github.com/likexian/whois-go/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/likexian/whois-go
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require github.com/likexian/gokit v0.21.11
|
2
vendor/github.com/likexian/whois-go/go.sum
generated
vendored
Normal file
2
vendor/github.com/likexian/whois-go/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/likexian/gokit v0.21.11 h1:tBA2U/5e9Pq24dsFuDZ2ykjsaSznjNnovOOK3ljU1ww=
|
||||||
|
github.com/likexian/gokit v0.21.11/go.mod h1:0WlTw7IPdiMtrwu0t5zrLM7XXik27Ey6MhUJHio2fVo=
|
142
vendor/github.com/likexian/whois-go/whois.go
generated
vendored
Normal file
142
vendor/github.com/likexian/whois-go/whois.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain and ip whois info query
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whois
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IANA_WHOIS_SERVER is iana whois server
|
||||||
|
IANA_WHOIS_SERVER = "whois.iana.org"
|
||||||
|
// DOMAIN_WHOIS_SERVER is tld whois server
|
||||||
|
DOMAIN_WHOIS_SERVER = "whois-servers.net"
|
||||||
|
// WHOIS_PORT is default whois port
|
||||||
|
WHOIS_PORT = "43"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns package version
|
||||||
|
func Version() string {
|
||||||
|
return "1.3.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author returns package author
|
||||||
|
func Author() string {
|
||||||
|
return "[Li Kexian](https://www.likexian.com/)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// License returns package license
|
||||||
|
func License() string {
|
||||||
|
return "Licensed under the Apache License 2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whois do the whois query and returns whois info
|
||||||
|
func Whois(domain string, servers ...string) (result string, err error) {
|
||||||
|
domain = strings.Trim(strings.TrimSpace(domain), ".")
|
||||||
|
if domain == "" {
|
||||||
|
err = fmt.Errorf("Domain is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = query(domain, servers...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := "Registrar WHOIS Server:"
|
||||||
|
if IsIpv4(domain) {
|
||||||
|
token = "whois:"
|
||||||
|
}
|
||||||
|
|
||||||
|
start := strings.Index(result, token)
|
||||||
|
if start == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
start += len(token)
|
||||||
|
end := strings.Index(result[start:], "\n")
|
||||||
|
server := strings.TrimSpace(result[start : start+end])
|
||||||
|
if server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpResult, err := query(domain, server)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result += tmpResult
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// query do the query
|
||||||
|
func query(domain string, servers ...string) (result string, err error) {
|
||||||
|
server := IANA_WHOIS_SERVER
|
||||||
|
if len(servers) == 0 || servers[0] == "" {
|
||||||
|
if !IsIpv4(domain) {
|
||||||
|
domains := strings.Split(domain, ".")
|
||||||
|
if len(domains) > 1 {
|
||||||
|
ext := domains[len(domains)-1]
|
||||||
|
if strings.Contains(ext, "/") {
|
||||||
|
ext = strings.Split(ext, "/")[0]
|
||||||
|
}
|
||||||
|
server = ext + "." + DOMAIN_WHOIS_SERVER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server = strings.ToLower(servers[0])
|
||||||
|
if server == "whois.arin.net" {
|
||||||
|
domain = "n + " + domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, e := net.DialTimeout("tcp", net.JoinHostPort(server, WHOIS_PORT), time.Second*30)
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
||||||
|
_, err = conn.Write([]byte(domain + "\r\n"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer, err := ioutil.ReadAll(conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = string(buffer)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIpv4 returns string is an ipv4 ip
|
||||||
|
func IsIpv4(ip string) bool {
|
||||||
|
i := net.ParseIP(ip)
|
||||||
|
return i.To4() != nil
|
||||||
|
}
|
2
vendor/github.com/likexian/whois-parser-go/.gitignore
generated
vendored
Normal file
2
vendor/github.com/likexian/whois-parser-go/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.DS_Store
|
||||||
|
rule
|
7
vendor/github.com/likexian/whois-parser-go/.travis.yml
generated
vendored
Normal file
7
vendor/github.com/likexian/whois-parser-go/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
205
vendor/github.com/likexian/whois-parser-go/LICENSE
generated
vendored
Normal file
205
vendor/github.com/likexian/whois-parser-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
APPENDIX: Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
https://www.likexian.com/
|
76
vendor/github.com/likexian/whois-parser-go/README.md
generated
vendored
Normal file
76
vendor/github.com/likexian/whois-parser-go/README.md
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# whois-parser.go
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/likexian/whois-parser-go?status.svg)](https://godoc.org/github.com/likexian/whois-parser-go)
|
||||||
|
[![Build Status](https://travis-ci.org/likexian/whois-parser-go.svg?branch=master)](https://travis-ci.org/likexian/whois-parser-go)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/likexian/whois-parser-go)](https://goreportcard.com/report/github.com/likexian/whois-parser-go)
|
||||||
|
[![Code Cover](https://codecov.io/gh/likexian/whois-parser-go/graph/badge.svg)](https://codecov.io/gh/likexian/whois-parser-go)
|
||||||
|
|
||||||
|
whois-parser-go is a simple Go module for domain whois info parse.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This module parses the provided whois information and return a readable data struct.
|
||||||
|
|
||||||
|
## Verified Extensions
|
||||||
|
|
||||||
|
It is supposed to be working with all domain extensions, but [verified extensions](examples/README.md) must works, because I have checked them one by one manually.
|
||||||
|
|
||||||
|
If there is any problems, please feel free to open a new issue.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get github.com/likexian/whois-parser-go
|
||||||
|
|
||||||
|
## Importing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/likexian/whois-parser-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/whois-parser-go)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
result, err := whoisparser.Parse(whois_raw)
|
||||||
|
if err == nil {
|
||||||
|
// Print the domain status
|
||||||
|
fmt.Println(result.Domain.Status)
|
||||||
|
|
||||||
|
// Print the domain created date
|
||||||
|
fmt.Println(result.Domain.CreatedDate)
|
||||||
|
|
||||||
|
// Print the domain expiration date
|
||||||
|
fmt.Println(result.Domain.ExpirationDate)
|
||||||
|
|
||||||
|
// Print the registrar name
|
||||||
|
fmt.Println(result.Registrar.Name)
|
||||||
|
|
||||||
|
// Print the registrant name
|
||||||
|
fmt.Println(result.Registrant.Name)
|
||||||
|
|
||||||
|
// Print the registrant email address
|
||||||
|
fmt.Println(result.Registrant.Email)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Whois info query in Go
|
||||||
|
|
||||||
|
Please refer to [whois-go](https://github.com/likexian/whois-go)
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
Copyright 2014-2019 Li Kexian
|
||||||
|
|
||||||
|
Licensed under the Apache License 2.0
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
- [Li Kexian](https://www.likexian.com/)
|
||||||
|
|
||||||
|
## DONATE
|
||||||
|
|
||||||
|
- [Help me make perfect](https://www.likexian.com/donate/)
|
5
vendor/github.com/likexian/whois-parser-go/go.mod
generated
vendored
Normal file
5
vendor/github.com/likexian/whois-parser-go/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/likexian/whois-parser-go
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require github.com/likexian/gokit v0.21.11
|
2
vendor/github.com/likexian/whois-parser-go/go.sum
generated
vendored
Normal file
2
vendor/github.com/likexian/whois-parser-go/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/likexian/gokit v0.21.11 h1:tBA2U/5e9Pq24dsFuDZ2ykjsaSznjNnovOOK3ljU1ww=
|
||||||
|
github.com/likexian/gokit v0.21.11/go.mod h1:0WlTw7IPdiMtrwu0t5zrLM7XXik27Ey6MhUJHio2fVo=
|
234
vendor/github.com/likexian/whois-parser-go/parser.go
generated
vendored
Normal file
234
vendor/github.com/likexian/whois-parser-go/parser.go
generated
vendored
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain whois info parse
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whoisparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/likexian/gokit/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Domain info error and replacer variables
|
||||||
|
var (
|
||||||
|
ErrDomainNotFound = errors.New("Domain is not found.")
|
||||||
|
ErrDomainInvalidData = errors.New("Domain whois data invalid.")
|
||||||
|
ErrDomainLimitExceed = errors.New("Domain query limit exceeded.")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns package version
|
||||||
|
func Version() string {
|
||||||
|
return "1.10.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author returns package author
|
||||||
|
func Author() string {
|
||||||
|
return "[Li Kexian](https://www.likexian.com/)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// License returns package license
|
||||||
|
func License() string {
|
||||||
|
return "Licensed under the Apache License 2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns parsed whois info
|
||||||
|
func Parse(text string) (whoisInfo WhoisInfo, err error) {
|
||||||
|
name, extension := searchDomain(text)
|
||||||
|
if name == "" {
|
||||||
|
err = ErrDomainInvalidData
|
||||||
|
if IsNotFound(text) {
|
||||||
|
err = ErrDomainNotFound
|
||||||
|
} else if IsLimitExceeded(text) {
|
||||||
|
err = ErrDomainLimitExceed
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain Domain
|
||||||
|
var registrar Contact
|
||||||
|
var registrant Contact
|
||||||
|
var administrative Contact
|
||||||
|
var technical Contact
|
||||||
|
var billing Contact
|
||||||
|
|
||||||
|
domain.Name = name
|
||||||
|
domain.Extension = extension
|
||||||
|
|
||||||
|
whoisText, _ := Prepare(text, extension)
|
||||||
|
whoisLines := strings.Split(whoisText, "\n")
|
||||||
|
for i := 0; i < len(whoisLines); i++ {
|
||||||
|
line := strings.TrimSpace(whoisLines[i])
|
||||||
|
if len(line) < 5 || !strings.Contains(line, ":") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fChar := line[:1]
|
||||||
|
if assert.IsContains([]string{"-", "*", "%", ">", ";"}, fChar) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if line[len(line)-1:] == ":" {
|
||||||
|
i += 1
|
||||||
|
for ; i < len(whoisLines); i++ {
|
||||||
|
thisLine := strings.TrimSpace(whoisLines[i])
|
||||||
|
if strings.Contains(thisLine, ":") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line += thisLine + ","
|
||||||
|
}
|
||||||
|
line = strings.Trim(line, ",")
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.SplitN(line, ":", 2)
|
||||||
|
name := strings.TrimSpace(lines[0])
|
||||||
|
value := strings.TrimSpace(lines[1])
|
||||||
|
value = strings.TrimSpace(strings.Trim(value, ":"))
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keyName := FindKeyName(name)
|
||||||
|
switch keyName {
|
||||||
|
case "domain_id":
|
||||||
|
domain.ID = value
|
||||||
|
case "domain_name":
|
||||||
|
domain.Domain = value
|
||||||
|
case "domain_status":
|
||||||
|
domain.Status += value + ","
|
||||||
|
case "domain_dnssec":
|
||||||
|
if domain.DNSSEC == "" {
|
||||||
|
domain.DNSSEC = value
|
||||||
|
}
|
||||||
|
case "whois_server":
|
||||||
|
if domain.WhoisServer == "" {
|
||||||
|
domain.WhoisServer = value
|
||||||
|
}
|
||||||
|
case "name_servers":
|
||||||
|
domain.NameServers += value + ","
|
||||||
|
case "created_date":
|
||||||
|
if domain.CreatedDate == "" {
|
||||||
|
domain.CreatedDate = value
|
||||||
|
}
|
||||||
|
case "updated_date":
|
||||||
|
if domain.UpdatedDate == "" {
|
||||||
|
domain.UpdatedDate = value
|
||||||
|
}
|
||||||
|
case "expired_date":
|
||||||
|
if domain.ExpirationDate == "" {
|
||||||
|
domain.ExpirationDate = value
|
||||||
|
}
|
||||||
|
case "referral_url":
|
||||||
|
registrar.ReferralURL = value
|
||||||
|
default:
|
||||||
|
name = ClearName(name)
|
||||||
|
if !strings.Contains(name, " ") {
|
||||||
|
name += " name"
|
||||||
|
}
|
||||||
|
ns := strings.SplitN(name, " ", 2)
|
||||||
|
name = strings.TrimSpace("registrant " + ns[1])
|
||||||
|
if ns[0] == "registrar" || ns[0] == "registration" {
|
||||||
|
registrar = parseContact(registrar, name, value)
|
||||||
|
} else if ns[0] == "registrant" || ns[0] == "holder" {
|
||||||
|
registrant = parseContact(registrant, name, value)
|
||||||
|
} else if ns[0] == "admin" || ns[0] == "administrative" {
|
||||||
|
administrative = parseContact(administrative, name, value)
|
||||||
|
} else if ns[0] == "tech" || ns[0] == "technical" {
|
||||||
|
technical = parseContact(technical, name, value)
|
||||||
|
} else if ns[0] == "bill" || ns[0] == "billing" {
|
||||||
|
billing = parseContact(billing, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.NameServers = FixNameServers(strings.ToLower(domain.NameServers))
|
||||||
|
domain.Status = FixDomainStatus(strings.ToLower(domain.Status))
|
||||||
|
|
||||||
|
domain.NameServers = RemoveDuplicateField(domain.NameServers)
|
||||||
|
domain.Status = RemoveDuplicateField(domain.Status)
|
||||||
|
|
||||||
|
whoisInfo.Domain = domain
|
||||||
|
whoisInfo.Registrar = registrar
|
||||||
|
whoisInfo.Registrant = registrant
|
||||||
|
whoisInfo.Administrative = administrative
|
||||||
|
whoisInfo.Technical = technical
|
||||||
|
whoisInfo.Billing = billing
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseContact do parse contact info
|
||||||
|
func parseContact(contact Contact, name, value string) Contact {
|
||||||
|
keyName := FindKeyName(name)
|
||||||
|
|
||||||
|
switch keyName {
|
||||||
|
case "registrant_id":
|
||||||
|
contact.ID = value
|
||||||
|
case "registrant_name":
|
||||||
|
contact.Name = value
|
||||||
|
case "registrant_organization":
|
||||||
|
contact.Organization = value
|
||||||
|
case "registrant_street":
|
||||||
|
if contact.Street == "" {
|
||||||
|
contact.Street = value
|
||||||
|
} else {
|
||||||
|
contact.Street += ", " + value
|
||||||
|
}
|
||||||
|
case "registrant_city":
|
||||||
|
contact.City = value
|
||||||
|
case "registrant_state_province":
|
||||||
|
contact.Province = value
|
||||||
|
case "registrant_postal_code":
|
||||||
|
contact.PostalCode = value
|
||||||
|
case "registrant_country":
|
||||||
|
contact.Country = value
|
||||||
|
case "registrant_phone":
|
||||||
|
contact.Phone = value
|
||||||
|
case "registrant_phone_ext":
|
||||||
|
contact.PhoneExt = value
|
||||||
|
case "registrant_fax":
|
||||||
|
contact.Fax = value
|
||||||
|
case "registrant_fax_ext":
|
||||||
|
contact.FaxExt = value
|
||||||
|
case "registrant_email":
|
||||||
|
contact.Email = strings.ToLower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contact
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchDomain find domain from whois info
|
||||||
|
func searchDomain(text string) (string, string) {
|
||||||
|
r := regexp.MustCompile(`(?i)\[?domain(\s*\_?name)?\]?\s*\:?\s*([a-z0-9\-\.]+)\.([a-z]{2,})`)
|
||||||
|
m := r.FindStringSubmatch(text)
|
||||||
|
if len(m) > 0 {
|
||||||
|
return strings.ToLower(strings.TrimSpace(m[2])), strings.ToLower(strings.TrimSpace(m[3]))
|
||||||
|
}
|
||||||
|
|
||||||
|
r = regexp.MustCompile(`(?i)\[?domain(\s*\_?name)?\]?\s*\:?\s*([a-z]{2,})\n`)
|
||||||
|
m = r.FindStringSubmatch(text)
|
||||||
|
if len(m) > 0 {
|
||||||
|
return strings.ToLower(strings.TrimSpace(m[2])), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ""
|
||||||
|
}
|
930
vendor/github.com/likexian/whois-parser-go/prepare.go
generated
vendored
Normal file
930
vendor/github.com/likexian/whois-parser-go/prepare.go
generated
vendored
Normal file
@ -0,0 +1,930 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain whois info parse
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whoisparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/likexian/gokit/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prepare do prepare the whois info for parsing
|
||||||
|
func Prepare(text, ext string) (string, bool) {
|
||||||
|
text = strings.Replace(text, "\r", "", -1)
|
||||||
|
text = strings.Replace(text, "\t", " ", -1)
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case "":
|
||||||
|
return prepareTLD(text), true
|
||||||
|
case "edu":
|
||||||
|
return prepareEDU(text), true
|
||||||
|
case "int":
|
||||||
|
return prepareINT(text), true
|
||||||
|
case "mo":
|
||||||
|
return prepareMO(text), true
|
||||||
|
case "hk":
|
||||||
|
return prepareHK(text), true
|
||||||
|
case "tw":
|
||||||
|
return prepareTW(text), true
|
||||||
|
case "ch":
|
||||||
|
return prepareCH(text), true
|
||||||
|
case "it":
|
||||||
|
return prepareIT(text), true
|
||||||
|
case "fr", "re", "tf", "yt", "pm", "wf":
|
||||||
|
return prepareFR(text), true
|
||||||
|
case "ru", "su":
|
||||||
|
return prepareRU(text), true
|
||||||
|
case "jp":
|
||||||
|
return prepareJP(text), true
|
||||||
|
case "uk":
|
||||||
|
return prepareUK(text), true
|
||||||
|
case "kr":
|
||||||
|
return prepareKR(text), true
|
||||||
|
case "nz":
|
||||||
|
return prepareNZ(text), true
|
||||||
|
case "tk":
|
||||||
|
return prepareTK(text), true
|
||||||
|
case "nl":
|
||||||
|
return prepareNL(text), true
|
||||||
|
case "eu":
|
||||||
|
return prepareEU(text), true
|
||||||
|
case "br":
|
||||||
|
return prepareBR(text), true
|
||||||
|
case "ir":
|
||||||
|
return prepareIR(text), true
|
||||||
|
default:
|
||||||
|
return text, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareTLD do prepare the tld domain
|
||||||
|
func prepareTLD(text string) string {
|
||||||
|
token := ""
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
if strings.TrimSpace(vs[0]) == "organisation" {
|
||||||
|
if token == "" {
|
||||||
|
token = "registrant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(vs[0]) == "contact" {
|
||||||
|
token = strings.TrimSpace(vs[1])
|
||||||
|
} else {
|
||||||
|
if token != "" {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareEDU do prepare the .edu domain
|
||||||
|
func prepareEDU(text string) string {
|
||||||
|
tokens := map[string][]string{
|
||||||
|
"Registrant:": {
|
||||||
|
"Organization",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
},
|
||||||
|
"Administrative Contact:": {
|
||||||
|
"Name",
|
||||||
|
"Organization",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Phone",
|
||||||
|
"Email",
|
||||||
|
},
|
||||||
|
"Technical Contact:": {
|
||||||
|
"Name",
|
||||||
|
"Organization",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Phone",
|
||||||
|
"Email",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(v, ":") {
|
||||||
|
token = ""
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = v
|
||||||
|
} else {
|
||||||
|
if token == "" {
|
||||||
|
result += "\n" + v
|
||||||
|
} else {
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v)
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareINT do prepare the .int domain
|
||||||
|
func prepareINT(text string) string {
|
||||||
|
token := ""
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
if strings.TrimSpace(vs[0]) == "organisation" {
|
||||||
|
if token == "" {
|
||||||
|
token = "registrant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(vs[0]) == "contact" {
|
||||||
|
token = strings.TrimSpace(vs[1])
|
||||||
|
} else {
|
||||||
|
if token != "" {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareMO do prepare the .mo domain
|
||||||
|
func prepareMO(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"Registrant:": "Registrant",
|
||||||
|
"Admin Contact(s):": "Admin",
|
||||||
|
"Billing Contact(s):": "Billing",
|
||||||
|
"Technical Contact(s):": "Technical",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v[0] == '-' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, s := range []string{"Record created on", "Record expires on"} {
|
||||||
|
if strings.HasPrefix(v, s) {
|
||||||
|
v = strings.Replace(v, s, s+":", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = tokens[v]
|
||||||
|
} else {
|
||||||
|
if token != "" {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareHK do prepare the .hk domain
|
||||||
|
func prepareHK(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"Registrant Contact Information:": "Registrant",
|
||||||
|
"Administrative Contact Information:": "Admin",
|
||||||
|
"Technical Contact Information:": "Technical",
|
||||||
|
"Name Servers Information:": "Name Servers:",
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTokens := []string{
|
||||||
|
"Domain Name Commencement Date",
|
||||||
|
"Expiry Date",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
addressToken := false
|
||||||
|
text = strings.Replace(text, "\n\n", "\n", -1)
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field := ""
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
field = strings.TrimSpace(vs[0])
|
||||||
|
if strings.Contains(field, "(") {
|
||||||
|
field = strings.Split(field, "(")[0]
|
||||||
|
v = fmt.Sprintf("%s: %s", field, vs[1])
|
||||||
|
}
|
||||||
|
addressToken = field == "Address"
|
||||||
|
if field == "Registrar Contact Information" {
|
||||||
|
re := regexp.MustCompile(`Email\:\s+([^\s]+)(\s+Hotline\:(.*))?`)
|
||||||
|
m := re.FindStringSubmatch(vs[1])
|
||||||
|
if len(m) == 4 {
|
||||||
|
v = ""
|
||||||
|
if m[1] != "" {
|
||||||
|
v += "Registrar Contact Email: " + m[1] + "\n"
|
||||||
|
}
|
||||||
|
if m[3] != "" {
|
||||||
|
v += "Registrar Contact Phone: " + m[3] + "\n"
|
||||||
|
}
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if field == "Family name" {
|
||||||
|
vv := strings.TrimSpace(vs[1])
|
||||||
|
if vv != "" && vv != "." {
|
||||||
|
result += " " + vv
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if addressToken {
|
||||||
|
result += ", " + v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = tokens[v]
|
||||||
|
} else {
|
||||||
|
if token != "" && !assert.IsContains(dateTokens, field) {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareTW do prepare the .tw domain
|
||||||
|
func prepareTW(text string) string {
|
||||||
|
tokens := map[string][]string{
|
||||||
|
"Registrant:": {
|
||||||
|
"Organization",
|
||||||
|
"Name,Email",
|
||||||
|
"Phone",
|
||||||
|
"Fax",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
},
|
||||||
|
"Administrative Contact:": {
|
||||||
|
"Name,Email",
|
||||||
|
"Phone",
|
||||||
|
"Fax",
|
||||||
|
},
|
||||||
|
"Technical Contact:": {
|
||||||
|
"Name,Email",
|
||||||
|
"Phone",
|
||||||
|
"Fax",
|
||||||
|
},
|
||||||
|
"Contact:": {
|
||||||
|
"Name",
|
||||||
|
"Email",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
index := -1
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, s := range []string{"Record created on", "Record expires on"} {
|
||||||
|
if strings.HasPrefix(v, s) {
|
||||||
|
v = strings.Replace(v, s, s+":", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
token = ""
|
||||||
|
index = -1
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = v
|
||||||
|
} else {
|
||||||
|
if token == "" {
|
||||||
|
result += "\n" + v
|
||||||
|
} else {
|
||||||
|
index += 1
|
||||||
|
tokenName := token[:len(token)-1]
|
||||||
|
indexName := tokens[token][index]
|
||||||
|
if tokenName == "Contact" {
|
||||||
|
tokenName = "Registrant Contact"
|
||||||
|
}
|
||||||
|
if strings.Contains(indexName, ",") {
|
||||||
|
ins := strings.Split(indexName, ",")
|
||||||
|
re := regexp.MustCompile(`(.*)\s+([^\s]+@[^\s]+)`)
|
||||||
|
m := re.FindStringSubmatch(v)
|
||||||
|
if len(m) == 3 {
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(m[1]))
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[1], strings.TrimSpace(m[2]))
|
||||||
|
} else {
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", tokenName, ins[0], strings.TrimSpace(v))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", tokenName, indexName, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareCH do prepare the .ch domain
|
||||||
|
func prepareCH(text string) string {
|
||||||
|
tokens := []string{
|
||||||
|
"Domain name",
|
||||||
|
"Holder",
|
||||||
|
"Technical contact",
|
||||||
|
"Registrar",
|
||||||
|
"DNSSEC",
|
||||||
|
"Name servers",
|
||||||
|
"First registration date",
|
||||||
|
}
|
||||||
|
|
||||||
|
splits := map[string]string{
|
||||||
|
"Holder": "Registrant organization, Registrant name, Registrant street",
|
||||||
|
"Technical contact": "Technical organization, Technical name, Technical street",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, t := range tokens {
|
||||||
|
if strings.HasPrefix(strings.ToLower(v)+" ", strings.ToLower(t+" ")) {
|
||||||
|
found = true
|
||||||
|
result += fmt.Sprintf("\n%s: %s", strings.TrimSpace(t), strings.TrimSpace(v[len(t):]))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
result += ", " + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results := []string{}
|
||||||
|
for _, v := range strings.Split(result, "\n") {
|
||||||
|
if !strings.Contains(v, ":") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
if sp, ok := splits[vs[0]]; ok {
|
||||||
|
vv := strings.Split(vs[1], ", ")
|
||||||
|
ss := strings.Split(sp, ", ")
|
||||||
|
if len(vv) > len(ss) {
|
||||||
|
vv[len(ss)-1] = strings.Join(vv[len(ss)-1:], ", ")
|
||||||
|
vv = vv[:len(ss)]
|
||||||
|
}
|
||||||
|
for k := range vv {
|
||||||
|
results = append(results, fmt.Sprintf("%s: %s", ss[k], vv[k]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results = append(results, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text = strings.Join(results, "\n")
|
||||||
|
text = strings.Replace(text, ": ,", ":", -1)
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIT do prepare the .it domain
|
||||||
|
func prepareIT(text string) string {
|
||||||
|
topTokens := []string{
|
||||||
|
"Registrant",
|
||||||
|
"Admin Contact",
|
||||||
|
"Technical Contacts",
|
||||||
|
"Registrar",
|
||||||
|
"Nameservers",
|
||||||
|
}
|
||||||
|
|
||||||
|
topToken := ""
|
||||||
|
subToken := ""
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if assert.IsContains(topTokens, v) {
|
||||||
|
topToken = v + " "
|
||||||
|
subToken = ""
|
||||||
|
} else {
|
||||||
|
if v[0] != '*' && strings.Contains(v, ":") {
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
subToken = vs[0]
|
||||||
|
} else {
|
||||||
|
if subToken != "" {
|
||||||
|
result += ", " + v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if topToken != "" && !strings.Contains(v, ":") {
|
||||||
|
result += fmt.Sprintf("\n%s: %s", topToken, v)
|
||||||
|
} else {
|
||||||
|
result += fmt.Sprintf("\n%s%s", topToken, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareFR do prepare the .fr domain
|
||||||
|
func prepareFR(text string) string {
|
||||||
|
dsToken := "dsl-id"
|
||||||
|
hdlToken := "nic-hdl"
|
||||||
|
regToken := "registrar"
|
||||||
|
|
||||||
|
tokens := map[string]string{
|
||||||
|
"holder-c": "holder",
|
||||||
|
"admin-c": "admin",
|
||||||
|
"tech-c": "tech",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
newBlock := false
|
||||||
|
hdls := map[string]string{}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
newBlock = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
if newBlock && strings.TrimSpace(vs[0]) == regToken {
|
||||||
|
token = regToken + " "
|
||||||
|
v = fmt.Sprintf("name: %s", strings.TrimSpace(vs[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
newBlock = false
|
||||||
|
if t, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
hdls[t] = strings.TrimSpace(vs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(vs[0]) == dsToken && strings.TrimSpace(vs[1]) != "" {
|
||||||
|
v += "\nDNSSEC: signed"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(vs[0]) == hdlToken {
|
||||||
|
for _, kk := range Keys(hdls) {
|
||||||
|
if strings.TrimSpace(vs[1]) == hdls[kk] {
|
||||||
|
token = kk + " "
|
||||||
|
delete(hdls, kk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += fmt.Sprintf("\n%s%s", token, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareRU do prepare the .ru domain
|
||||||
|
func prepareRU(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"person": "Registrant Name",
|
||||||
|
"e-mail": "Registrant Email",
|
||||||
|
"org": "Registrant Organization",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(v, ":") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
v = fmt.Sprintf("%s: %s", vv, vs[1])
|
||||||
|
}
|
||||||
|
result += v + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareJP do prepare the .jp domain
|
||||||
|
func prepareJP(text string) string {
|
||||||
|
replacer := regexp.MustCompile(`\n\[(.+?)\][\ ]*(.+?)?`)
|
||||||
|
text = replacer.ReplaceAllString(text, "\n$1: $2")
|
||||||
|
|
||||||
|
adminToken := "Contact Information"
|
||||||
|
addressToken := "Postal Address"
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
prefixToken := ""
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.Split(v, ":")
|
||||||
|
token = strings.TrimSpace(vs[0])
|
||||||
|
if token == adminToken {
|
||||||
|
prefixToken = "admin "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if token == addressToken {
|
||||||
|
result += ", " + v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + prefixToken + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareUK do prepare the .uk domain
|
||||||
|
func prepareUK(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"URL": "Registrar URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
v = fmt.Sprintf("%s: %s", vv, vs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareKR do prepare the .kr domain
|
||||||
|
func prepareKR(text string) string {
|
||||||
|
english := "# ENGLISH"
|
||||||
|
tokens := map[string]string{
|
||||||
|
"Administrative Contact(AC)": "Administrative Contact Name",
|
||||||
|
"AC E-Mail": "Administrative Contact E-Mail",
|
||||||
|
"AC Phone Number": "Administrative Contact Phone Number",
|
||||||
|
"Authorized Agency": "Registrar Name",
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := strings.Index(text, english)
|
||||||
|
if pos != -1 {
|
||||||
|
text = text[pos+len(english):]
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v[0] == '\'' || v[0] == '-' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
v = fmt.Sprintf("%s: %s", vv, vs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareNZ do prepare the .nz domain
|
||||||
|
func prepareNZ(text string) string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(vs[0]), "ns_name_") {
|
||||||
|
v = fmt.Sprintf("name server: %s", vs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareTK do prepare the .tk domain
|
||||||
|
func prepareTK(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"Domain name:": "Domain",
|
||||||
|
"Owner contact:": "Registrant",
|
||||||
|
"Admin contact:": "Admin",
|
||||||
|
"Billing contact:": "Billing",
|
||||||
|
"Tech contact:": "Technical",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = tokens[v]
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if token == "Domain" {
|
||||||
|
if strings.Contains(v, " is ") {
|
||||||
|
vs := strings.Split(v, " is ")
|
||||||
|
v = fmt.Sprintf("Name: %s\nStatus: %s", vs[0], vs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareNL do prepare the .nl domain
|
||||||
|
func prepareNL(text string) string {
|
||||||
|
tokens := map[string][]string{
|
||||||
|
"Reseller:": {
|
||||||
|
"Name",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
},
|
||||||
|
"Registrar:": {
|
||||||
|
"Name",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
"Address",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(v, ":") {
|
||||||
|
token = ""
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = v
|
||||||
|
} else {
|
||||||
|
if token == "" {
|
||||||
|
result += "\n" + v
|
||||||
|
} else {
|
||||||
|
result += fmt.Sprintf("\n%s %s: %s", token[:len(token)-1], tokens[token][index], v)
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareEU do prepare the .eu domain
|
||||||
|
func prepareEU(text string) string {
|
||||||
|
tokens := map[string]string{
|
||||||
|
"Registrant:": "Registrant",
|
||||||
|
"Technical:": "Technical",
|
||||||
|
"Registrar:": "Registrar",
|
||||||
|
"Onsite(s):": "Onsite",
|
||||||
|
"Name servers:": "Name servers",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := tokens[v]; ok {
|
||||||
|
token = tokens[v]
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if token != "" {
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
v = fmt.Sprintf("%s %s", token, v)
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(v, "Visit www.eurid.eu") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v = fmt.Sprintf("%s: %s", token, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareBR do prepare the .br domain
|
||||||
|
func prepareBR(text string) string {
|
||||||
|
hdlToken := "nic-hdl-br"
|
||||||
|
tokens := map[string]string{
|
||||||
|
"owner-c": "registrant",
|
||||||
|
"admin-c": "admin",
|
||||||
|
"tech-c": "tech",
|
||||||
|
"billing-c": "billing",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
hdlMap := map[string]string{}
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if strings.TrimSpace(vs[0]) == hdlToken {
|
||||||
|
token = strings.TrimSpace(vs[1])
|
||||||
|
hdlMap[token] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
hdlMap[token] += "\n" + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if strings.TrimSpace(vs[0]) == "owner" {
|
||||||
|
v = fmt.Sprintf("registrant organization: %s", vs[1])
|
||||||
|
}
|
||||||
|
if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") {
|
||||||
|
if strings.TrimSpace(tt) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("\n%s %s", vv, tt)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIR do prepare the .ir domain
|
||||||
|
func prepareIR(text string) string {
|
||||||
|
hdlToken := "nic-hdl"
|
||||||
|
tokens := map[string]string{
|
||||||
|
"holder-c": "registrant",
|
||||||
|
"admin-c": "admin",
|
||||||
|
"tech-c": "tech",
|
||||||
|
"bill-c": "billing",
|
||||||
|
}
|
||||||
|
|
||||||
|
token := ""
|
||||||
|
hdlMap := map[string]string{}
|
||||||
|
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if strings.TrimSpace(vs[0]) == hdlToken {
|
||||||
|
token = strings.TrimSpace(vs[1])
|
||||||
|
hdlMap[token] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
hdlMap[token] += "\n" + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for _, v := range strings.Split(text, "\n") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(v, ":") {
|
||||||
|
vs := strings.SplitN(v, ":", 2)
|
||||||
|
if vv, ok := tokens[strings.TrimSpace(vs[0])]; ok {
|
||||||
|
for _, tt := range strings.Split(hdlMap[strings.TrimSpace(vs[1])], "\n") {
|
||||||
|
if strings.TrimSpace(tt) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("\n%s %s", vv, tt)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += "\n" + v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
171
vendor/github.com/likexian/whois-parser-go/rule.go
generated
vendored
Normal file
171
vendor/github.com/likexian/whois-parser-go/rule.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain whois info parse
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whoisparser
|
||||||
|
|
||||||
|
var (
|
||||||
|
// keyRule is parser key rule mapper
|
||||||
|
keyRule = map[string]string{
|
||||||
|
"id": "domain_id",
|
||||||
|
"roid": "domain_id",
|
||||||
|
"domain id": "domain_id",
|
||||||
|
"domain": "domain_name",
|
||||||
|
"domain name": "domain_name",
|
||||||
|
"status": "domain_status",
|
||||||
|
"state": "domain_status",
|
||||||
|
"domain status": "domain_status",
|
||||||
|
"registration status": "domain_status",
|
||||||
|
"query status": "domain_status",
|
||||||
|
"dnssec": "domain_dnssec",
|
||||||
|
"domain dnssec": "domain_dnssec",
|
||||||
|
"registrar dnssec": "domain_dnssec",
|
||||||
|
"signing key": "domain_dnssec",
|
||||||
|
"domain signed": "domain_dnssec",
|
||||||
|
"whois": "whois_server",
|
||||||
|
"whois server": "whois_server",
|
||||||
|
"registrar whois server": "whois_server",
|
||||||
|
"nserver": "name_servers",
|
||||||
|
"name server": "name_servers",
|
||||||
|
"name servers": "name_servers",
|
||||||
|
"nameserver": "name_servers",
|
||||||
|
"nameservers": "name_servers",
|
||||||
|
"name servers information": "name_servers",
|
||||||
|
"host name": "name_servers",
|
||||||
|
"domain nameservers": "name_servers",
|
||||||
|
"domain name servers": "name_servers",
|
||||||
|
"domain servers in listed order": "name_servers",
|
||||||
|
"created": "created_date",
|
||||||
|
"registered": "created_date",
|
||||||
|
"created on": "created_date",
|
||||||
|
"create date": "created_date",
|
||||||
|
"created date": "created_date",
|
||||||
|
"creation date": "created_date",
|
||||||
|
"domain registration date": "created_date",
|
||||||
|
"registration date": "created_date",
|
||||||
|
"domain create date": "created_date",
|
||||||
|
"domain name commencement date": "created_date",
|
||||||
|
"registered date": "created_date",
|
||||||
|
"registered on": "created_date",
|
||||||
|
"registration time": "created_date",
|
||||||
|
"first registration date": "created_date",
|
||||||
|
"domain record activated": "created_date",
|
||||||
|
"record created on": "created_date",
|
||||||
|
"domain registered": "created_date",
|
||||||
|
"modified": "updated_date",
|
||||||
|
"changed": "updated_date",
|
||||||
|
"update date": "updated_date",
|
||||||
|
"updated date": "updated_date",
|
||||||
|
"updated on": "updated_date",
|
||||||
|
"last update": "updated_date",
|
||||||
|
"last updated": "updated_date",
|
||||||
|
"last updated on": "updated_date",
|
||||||
|
"last modified": "updated_date",
|
||||||
|
"last updated date": "updated_date",
|
||||||
|
"domain last updated date": "updated_date",
|
||||||
|
"domain record last updated": "updated_date",
|
||||||
|
"domain datelastmodified": "updated_date",
|
||||||
|
"expire": "expired_date",
|
||||||
|
"expires": "expired_date",
|
||||||
|
"expires on": "expired_date",
|
||||||
|
"paid till": "expired_date",
|
||||||
|
"expire date": "expired_date",
|
||||||
|
"expired date": "expired_date",
|
||||||
|
"expiration date": "expired_date",
|
||||||
|
"expiration on": "expired_date",
|
||||||
|
"registrar registration expiration date": "expired_date",
|
||||||
|
"domain expiration date": "expired_date",
|
||||||
|
"expiry date": "expired_date",
|
||||||
|
"expiration time": "expired_date",
|
||||||
|
"domain expires": "expired_date",
|
||||||
|
"record expires on": "expired_date",
|
||||||
|
"record will expire on": "expired_date",
|
||||||
|
"referral url": "referral_url",
|
||||||
|
"registrar url": "referral_url",
|
||||||
|
"registrar web": "referral_url",
|
||||||
|
"registrar website": "referral_url",
|
||||||
|
"registration service url": "referral_url",
|
||||||
|
"registrant c": "registrant_id",
|
||||||
|
"registrant id": "registrant_id",
|
||||||
|
"registrant iana id": "registrant_id",
|
||||||
|
"registrant contact id": "registrant_id",
|
||||||
|
"registrant name": "registrant_name",
|
||||||
|
"registrant person": "registrant_name",
|
||||||
|
"registrant contact": "registrant_name",
|
||||||
|
"registrant contact name": "registrant_name",
|
||||||
|
"registrant given name": "registrant_name",
|
||||||
|
"registrant holder name": "registrant_name",
|
||||||
|
"registrant holder english name": "registrant_name",
|
||||||
|
"registrant service provider": "registrant_name",
|
||||||
|
"registrant org": "registrant_organization",
|
||||||
|
"registrant organization": "registrant_organization",
|
||||||
|
"registrant contact organization": "registrant_organization",
|
||||||
|
"registrant organisation": "registrant_organization",
|
||||||
|
"registrant contact organisation": "registrant_organization",
|
||||||
|
"registrant company name": "registrant_organization",
|
||||||
|
"registrant company english name": "registrant_organization",
|
||||||
|
"registrant address": "registrant_street",
|
||||||
|
"registrant address1": "registrant_street",
|
||||||
|
"registrant street": "registrant_street",
|
||||||
|
"registrant street1": "registrant_street",
|
||||||
|
"registrant contact address": "registrant_street",
|
||||||
|
"registrant contact address1": "registrant_street",
|
||||||
|
"registrant contact street": "registrant_street",
|
||||||
|
"registrant contact street1": "registrant_street",
|
||||||
|
"registrant s address": "registrant_street",
|
||||||
|
"registrant s address1": "registrant_street",
|
||||||
|
"registrant postal address": "registrant_street",
|
||||||
|
"registrant postal address1": "registrant_street",
|
||||||
|
"registrant city": "registrant_city",
|
||||||
|
"registrant contact city": "registrant_city",
|
||||||
|
"registrant state province": "registrant_state_province",
|
||||||
|
"registrant contact state province": "registrant_state_province",
|
||||||
|
"registrant zipcode": "registrant_postal_code",
|
||||||
|
"registrant zip code": "registrant_postal_code",
|
||||||
|
"registrant postal code": "registrant_postal_code",
|
||||||
|
"registrant contact postal code": "registrant_postal_code",
|
||||||
|
"registrant country": "registrant_country",
|
||||||
|
"registrant country economy": "registrant_country",
|
||||||
|
"registrant contact country": "registrant_country",
|
||||||
|
"registrant phone": "registrant_phone",
|
||||||
|
"registrant phone number": "registrant_phone",
|
||||||
|
"registrant contact phone": "registrant_phone",
|
||||||
|
"registrant contact phone number": "registrant_phone",
|
||||||
|
"registrant abuse contact phone": "registrant_phone",
|
||||||
|
"registrant phone ext": "registrant_phone_ext",
|
||||||
|
"registrant contact phone ext": "registrant_phone_ext",
|
||||||
|
"registrant fax": "registrant_fax",
|
||||||
|
"registrant fax no": "registrant_fax",
|
||||||
|
"registrant fax number": "registrant_fax",
|
||||||
|
"registrant facsimile": "registrant_fax",
|
||||||
|
"registrant facsimile number": "registrant_fax",
|
||||||
|
"registrant contact fax": "registrant_fax",
|
||||||
|
"registrant contact fax number": "registrant_fax",
|
||||||
|
"registrant contact facsimile": "registrant_fax",
|
||||||
|
"registrant contact facsimile number": "registrant_fax",
|
||||||
|
"registrant fax ext": "registrant_fax_ext",
|
||||||
|
"registrant contact fax ext": "registrant_fax_ext",
|
||||||
|
"registrant mail": "registrant_email",
|
||||||
|
"registrant email": "registrant_email",
|
||||||
|
"registrant e mail": "registrant_email",
|
||||||
|
"registrant contact mail": "registrant_email",
|
||||||
|
"registrant contact email": "registrant_email",
|
||||||
|
"registrant contact e mail": "registrant_email",
|
||||||
|
"registrant abuse contact email": "registrant_email",
|
||||||
|
}
|
||||||
|
)
|
63
vendor/github.com/likexian/whois-parser-go/struct.go
generated
vendored
Normal file
63
vendor/github.com/likexian/whois-parser-go/struct.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain whois info parse
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whoisparser
|
||||||
|
|
||||||
|
// WhoisInfo storing domain whois info
|
||||||
|
type WhoisInfo struct {
|
||||||
|
Domain Domain `json:"domain"`
|
||||||
|
Registrar Contact `json:"registrar"`
|
||||||
|
Registrant Contact `json:"registrant"`
|
||||||
|
Administrative Contact `json:"administrative"`
|
||||||
|
Technical Contact `json:"technical"`
|
||||||
|
Billing Contact `json:"billing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain storing domain name info
|
||||||
|
type Domain struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Extension string `json:"extension"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
DNSSEC string `json:"dnssec"`
|
||||||
|
WhoisServer string `json:"whois_server"`
|
||||||
|
NameServers string `json:"name_servers"`
|
||||||
|
CreatedDate string `json:"created_date"`
|
||||||
|
UpdatedDate string `json:"updated_date"`
|
||||||
|
ExpirationDate string `json:"expiration_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact storing domain contact info
|
||||||
|
type Contact struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Street string `json:"street"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
PostalCode string `json:"postal_code"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
PhoneExt string `json:"phone_ext"`
|
||||||
|
Fax string `json:"fax"`
|
||||||
|
FaxExt string `json:"fax_ext"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ReferralURL string `json:"referral_url"`
|
||||||
|
}
|
142
vendor/github.com/likexian/whois-parser-go/utils.go
generated
vendored
Normal file
142
vendor/github.com/likexian/whois-parser-go/utils.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 Li Kexian
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Go module for domain whois info parse
|
||||||
|
* https://www.likexian.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package whoisparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/likexian/gokit/xslice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNotFound returns domain is not found
|
||||||
|
func IsNotFound(data string) bool {
|
||||||
|
notExistsKeys := []string{
|
||||||
|
"no found",
|
||||||
|
"no match",
|
||||||
|
"not found",
|
||||||
|
"not match",
|
||||||
|
"no entries found",
|
||||||
|
"no data found",
|
||||||
|
"not registered",
|
||||||
|
"not been registered",
|
||||||
|
"is free",
|
||||||
|
"not available for registration",
|
||||||
|
"object does not exist",
|
||||||
|
}
|
||||||
|
|
||||||
|
data = strings.ToLower(data)
|
||||||
|
for _, v := range notExistsKeys {
|
||||||
|
if strings.Contains(data, v) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLimitExceeded returns is query limit
|
||||||
|
func IsLimitExceeded(data string) bool {
|
||||||
|
data = strings.ToLower(data)
|
||||||
|
return strings.Contains(data, "limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearName returns cleared key name
|
||||||
|
func ClearName(key string) string {
|
||||||
|
if strings.Contains(key, "(") {
|
||||||
|
key = strings.Split(key, "(")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
key = strings.Replace(key, "-", " ", -1)
|
||||||
|
key = strings.Replace(key, "_", " ", -1)
|
||||||
|
key = strings.Replace(key, "/", " ", -1)
|
||||||
|
key = strings.Replace(key, "\\", " ", -1)
|
||||||
|
key = strings.Replace(key, "'", " ", -1)
|
||||||
|
key = strings.Replace(key, ".", " ", -1)
|
||||||
|
|
||||||
|
key = strings.TrimPrefix(key, "Registry ")
|
||||||
|
key = strings.TrimPrefix(key, "Sponsoring ")
|
||||||
|
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindKeyName returns the mapper value by key
|
||||||
|
func FindKeyName(key string) string {
|
||||||
|
key = ClearName(key)
|
||||||
|
if v, ok := keyRule[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDuplicateField remove the duplicate field
|
||||||
|
func RemoveDuplicateField(data string) string {
|
||||||
|
fs := []string{}
|
||||||
|
|
||||||
|
for _, v := range strings.Split(data, ",") {
|
||||||
|
if strings.TrimSpace(v) != "" {
|
||||||
|
fs = append(fs, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := xslice.Unique(fs)
|
||||||
|
result := strings.Join(fields.([]string), ",")
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixDomainStatus returns fixed domain status
|
||||||
|
func FixDomainStatus(state string) string {
|
||||||
|
states := strings.Split(state, ",")
|
||||||
|
for k, v := range states {
|
||||||
|
names := strings.Split(strings.TrimSpace(v), " ")
|
||||||
|
states[k] = names[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(states, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixNameServers returns fixed name servers
|
||||||
|
func FixNameServers(nservers string) string {
|
||||||
|
servers := strings.Split(nservers, ",")
|
||||||
|
for k, v := range servers {
|
||||||
|
names := strings.Split(strings.TrimSpace(v), " ")
|
||||||
|
servers[k] = strings.Trim(names[0], ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(servers, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns all keys of map by sort
|
||||||
|
func Keys(m map[string]string) []string {
|
||||||
|
r := []string{}
|
||||||
|
|
||||||
|
for k := range m {
|
||||||
|
r = append(r, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(r)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
9
vendor/github.com/mattn/go-colorable/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/mattn/go-colorable/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw
|
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||||
|
|
||||||
|
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.
|
48
vendor/github.com/mattn/go-colorable/README.md
generated
vendored
Normal file
48
vendor/github.com/mattn/go-colorable/README.md
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# go-colorable
|
||||||
|
|
||||||
|
[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
|
||||||
|
[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
|
||||||
|
|
||||||
|
Colorable writer for windows.
|
||||||
|
|
||||||
|
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||||
|
This package is possible to handle escape sequence for ansi color on windows.
|
||||||
|
|
||||||
|
## Too Bad!
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
|
||||||
|
|
||||||
|
|
||||||
|
## So Good!
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
||||||
|
logrus.SetOutput(colorable.NewColorableStdout())
|
||||||
|
|
||||||
|
logrus.Info("succeeded")
|
||||||
|
logrus.Warn("not correct")
|
||||||
|
logrus.Error("something error")
|
||||||
|
logrus.Fatal("panic")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can compile above code on non-windows OSs.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/mattn/go-colorable
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
# Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
Normal file
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package colorable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewColorable return new instance of Writer which handle escape sequence.
|
||||||
|
func NewColorable(file *os.File) io.Writer {
|
||||||
|
if file == nil {
|
||||||
|
panic("nil passed instead of *os.File to NewColorable()")
|
||||||
|
}
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||||
|
func NewColorableStdout() io.Writer {
|
||||||
|
return os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||||
|
func NewColorableStderr() io.Writer {
|
||||||
|
return os.Stderr
|
||||||
|
}
|
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
Normal file
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// +build !windows
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package colorable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewColorable return new instance of Writer which handle escape sequence.
|
||||||
|
func NewColorable(file *os.File) io.Writer {
|
||||||
|
if file == nil {
|
||||||
|
panic("nil passed instead of *os.File to NewColorable()")
|
||||||
|
}
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||||
|
func NewColorableStdout() io.Writer {
|
||||||
|
return os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||||
|
func NewColorableStderr() io.Writer {
|
||||||
|
return os.Stderr
|
||||||
|
}
|
980
vendor/github.com/mattn/go-colorable/colorable_windows.go
generated
vendored
Normal file
980
vendor/github.com/mattn/go-colorable/colorable_windows.go
generated
vendored
Normal file
@ -0,0 +1,980 @@
|
|||||||
|
// +build windows
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package colorable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
foregroundBlue = 0x1
|
||||||
|
foregroundGreen = 0x2
|
||||||
|
foregroundRed = 0x4
|
||||||
|
foregroundIntensity = 0x8
|
||||||
|
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||||
|
backgroundBlue = 0x10
|
||||||
|
backgroundGreen = 0x20
|
||||||
|
backgroundRed = 0x40
|
||||||
|
backgroundIntensity = 0x80
|
||||||
|
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
genericRead = 0x80000000
|
||||||
|
genericWrite = 0x40000000
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
consoleTextmodeBuffer = 0x1
|
||||||
|
)
|
||||||
|
|
||||||
|
type wchar uint16
|
||||||
|
type short int16
|
||||||
|
type dword uint32
|
||||||
|
type word uint16
|
||||||
|
|
||||||
|
type coord struct {
|
||||||
|
x short
|
||||||
|
y short
|
||||||
|
}
|
||||||
|
|
||||||
|
type smallRect struct {
|
||||||
|
left short
|
||||||
|
top short
|
||||||
|
right short
|
||||||
|
bottom short
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleScreenBufferInfo struct {
|
||||||
|
size coord
|
||||||
|
cursorPosition coord
|
||||||
|
attributes word
|
||||||
|
window smallRect
|
||||||
|
maximumWindowSize coord
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleCursorInfo struct {
|
||||||
|
size dword
|
||||||
|
visible int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||||
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||||
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||||
|
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
||||||
|
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||||
|
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||||
|
procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
|
||||||
|
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Writer provide colorable Writer to the console
|
||||||
|
type Writer struct {
|
||||||
|
out io.Writer
|
||||||
|
handle syscall.Handle
|
||||||
|
althandle syscall.Handle
|
||||||
|
oldattr word
|
||||||
|
oldpos coord
|
||||||
|
rest bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorable return new instance of Writer which handle escape sequence from File.
|
||||||
|
func NewColorable(file *os.File) io.Writer {
|
||||||
|
if file == nil {
|
||||||
|
panic("nil passed instead of *os.File to NewColorable()")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isatty.IsTerminal(file.Fd()) {
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
handle := syscall.Handle(file.Fd())
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||||
|
func NewColorableStdout() io.Writer {
|
||||||
|
return NewColorable(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||||
|
func NewColorableStderr() io.Writer {
|
||||||
|
return NewColorable(os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var color256 = map[int]int{
|
||||||
|
0: 0x000000,
|
||||||
|
1: 0x800000,
|
||||||
|
2: 0x008000,
|
||||||
|
3: 0x808000,
|
||||||
|
4: 0x000080,
|
||||||
|
5: 0x800080,
|
||||||
|
6: 0x008080,
|
||||||
|
7: 0xc0c0c0,
|
||||||
|
8: 0x808080,
|
||||||
|
9: 0xff0000,
|
||||||
|
10: 0x00ff00,
|
||||||
|
11: 0xffff00,
|
||||||
|
12: 0x0000ff,
|
||||||
|
13: 0xff00ff,
|
||||||
|
14: 0x00ffff,
|
||||||
|
15: 0xffffff,
|
||||||
|
16: 0x000000,
|
||||||
|
17: 0x00005f,
|
||||||
|
18: 0x000087,
|
||||||
|
19: 0x0000af,
|
||||||
|
20: 0x0000d7,
|
||||||
|
21: 0x0000ff,
|
||||||
|
22: 0x005f00,
|
||||||
|
23: 0x005f5f,
|
||||||
|
24: 0x005f87,
|
||||||
|
25: 0x005faf,
|
||||||
|
26: 0x005fd7,
|
||||||
|
27: 0x005fff,
|
||||||
|
28: 0x008700,
|
||||||
|
29: 0x00875f,
|
||||||
|
30: 0x008787,
|
||||||
|
31: 0x0087af,
|
||||||
|
32: 0x0087d7,
|
||||||
|
33: 0x0087ff,
|
||||||
|
34: 0x00af00,
|
||||||
|
35: 0x00af5f,
|
||||||
|
36: 0x00af87,
|
||||||
|
37: 0x00afaf,
|
||||||
|
38: 0x00afd7,
|
||||||
|
39: 0x00afff,
|
||||||
|
40: 0x00d700,
|
||||||
|
41: 0x00d75f,
|
||||||
|
42: 0x00d787,
|
||||||
|
43: 0x00d7af,
|
||||||
|
44: 0x00d7d7,
|
||||||
|
45: 0x00d7ff,
|
||||||
|
46: 0x00ff00,
|
||||||
|
47: 0x00ff5f,
|
||||||
|
48: 0x00ff87,
|
||||||
|
49: 0x00ffaf,
|
||||||
|
50: 0x00ffd7,
|
||||||
|
51: 0x00ffff,
|
||||||
|
52: 0x5f0000,
|
||||||
|
53: 0x5f005f,
|
||||||
|
54: 0x5f0087,
|
||||||
|
55: 0x5f00af,
|
||||||
|
56: 0x5f00d7,
|
||||||
|
57: 0x5f00ff,
|
||||||
|
58: 0x5f5f00,
|
||||||
|
59: 0x5f5f5f,
|
||||||
|
60: 0x5f5f87,
|
||||||
|
61: 0x5f5faf,
|
||||||
|
62: 0x5f5fd7,
|
||||||
|
63: 0x5f5fff,
|
||||||
|
64: 0x5f8700,
|
||||||
|
65: 0x5f875f,
|
||||||
|
66: 0x5f8787,
|
||||||
|
67: 0x5f87af,
|
||||||
|
68: 0x5f87d7,
|
||||||
|
69: 0x5f87ff,
|
||||||
|
70: 0x5faf00,
|
||||||
|
71: 0x5faf5f,
|
||||||
|
72: 0x5faf87,
|
||||||
|
73: 0x5fafaf,
|
||||||
|
74: 0x5fafd7,
|
||||||
|
75: 0x5fafff,
|
||||||
|
76: 0x5fd700,
|
||||||
|
77: 0x5fd75f,
|
||||||
|
78: 0x5fd787,
|
||||||
|
79: 0x5fd7af,
|
||||||
|
80: 0x5fd7d7,
|
||||||
|
81: 0x5fd7ff,
|
||||||
|
82: 0x5fff00,
|
||||||
|
83: 0x5fff5f,
|
||||||
|
84: 0x5fff87,
|
||||||
|
85: 0x5fffaf,
|
||||||
|
86: 0x5fffd7,
|
||||||
|
87: 0x5fffff,
|
||||||
|
88: 0x870000,
|
||||||
|
89: 0x87005f,
|
||||||
|
90: 0x870087,
|
||||||
|
91: 0x8700af,
|
||||||
|
92: 0x8700d7,
|
||||||
|
93: 0x8700ff,
|
||||||
|
94: 0x875f00,
|
||||||
|
95: 0x875f5f,
|
||||||
|
96: 0x875f87,
|
||||||
|
97: 0x875faf,
|
||||||
|
98: 0x875fd7,
|
||||||
|
99: 0x875fff,
|
||||||
|
100: 0x878700,
|
||||||
|
101: 0x87875f,
|
||||||
|
102: 0x878787,
|
||||||
|
103: 0x8787af,
|
||||||
|
104: 0x8787d7,
|
||||||
|
105: 0x8787ff,
|
||||||
|
106: 0x87af00,
|
||||||
|
107: 0x87af5f,
|
||||||
|
108: 0x87af87,
|
||||||
|
109: 0x87afaf,
|
||||||
|
110: 0x87afd7,
|
||||||
|
111: 0x87afff,
|
||||||
|
112: 0x87d700,
|
||||||
|
113: 0x87d75f,
|
||||||
|
114: 0x87d787,
|
||||||
|
115: 0x87d7af,
|
||||||
|
116: 0x87d7d7,
|
||||||
|
117: 0x87d7ff,
|
||||||
|
118: 0x87ff00,
|
||||||
|
119: 0x87ff5f,
|
||||||
|
120: 0x87ff87,
|
||||||
|
121: 0x87ffaf,
|
||||||
|
122: 0x87ffd7,
|
||||||
|
123: 0x87ffff,
|
||||||
|
124: 0xaf0000,
|
||||||
|
125: 0xaf005f,
|
||||||
|
126: 0xaf0087,
|
||||||
|
127: 0xaf00af,
|
||||||
|
128: 0xaf00d7,
|
||||||
|
129: 0xaf00ff,
|
||||||
|
130: 0xaf5f00,
|
||||||
|
131: 0xaf5f5f,
|
||||||
|
132: 0xaf5f87,
|
||||||
|
133: 0xaf5faf,
|
||||||
|
134: 0xaf5fd7,
|
||||||
|
135: 0xaf5fff,
|
||||||
|
136: 0xaf8700,
|
||||||
|
137: 0xaf875f,
|
||||||
|
138: 0xaf8787,
|
||||||
|
139: 0xaf87af,
|
||||||
|
140: 0xaf87d7,
|
||||||
|
141: 0xaf87ff,
|
||||||
|
142: 0xafaf00,
|
||||||
|
143: 0xafaf5f,
|
||||||
|
144: 0xafaf87,
|
||||||
|
145: 0xafafaf,
|
||||||
|
146: 0xafafd7,
|
||||||
|
147: 0xafafff,
|
||||||
|
148: 0xafd700,
|
||||||
|
149: 0xafd75f,
|
||||||
|
150: 0xafd787,
|
||||||
|
151: 0xafd7af,
|
||||||
|
152: 0xafd7d7,
|
||||||
|
153: 0xafd7ff,
|
||||||
|
154: 0xafff00,
|
||||||
|
155: 0xafff5f,
|
||||||
|
156: 0xafff87,
|
||||||
|
157: 0xafffaf,
|
||||||
|
158: 0xafffd7,
|
||||||
|
159: 0xafffff,
|
||||||
|
160: 0xd70000,
|
||||||
|
161: 0xd7005f,
|
||||||
|
162: 0xd70087,
|
||||||
|
163: 0xd700af,
|
||||||
|
164: 0xd700d7,
|
||||||
|
165: 0xd700ff,
|
||||||
|
166: 0xd75f00,
|
||||||
|
167: 0xd75f5f,
|
||||||
|
168: 0xd75f87,
|
||||||
|
169: 0xd75faf,
|
||||||
|
170: 0xd75fd7,
|
||||||
|
171: 0xd75fff,
|
||||||
|
172: 0xd78700,
|
||||||
|
173: 0xd7875f,
|
||||||
|
174: 0xd78787,
|
||||||
|
175: 0xd787af,
|
||||||
|
176: 0xd787d7,
|
||||||
|
177: 0xd787ff,
|
||||||
|
178: 0xd7af00,
|
||||||
|
179: 0xd7af5f,
|
||||||
|
180: 0xd7af87,
|
||||||
|
181: 0xd7afaf,
|
||||||
|
182: 0xd7afd7,
|
||||||
|
183: 0xd7afff,
|
||||||
|
184: 0xd7d700,
|
||||||
|
185: 0xd7d75f,
|
||||||
|
186: 0xd7d787,
|
||||||
|
187: 0xd7d7af,
|
||||||
|
188: 0xd7d7d7,
|
||||||
|
189: 0xd7d7ff,
|
||||||
|
190: 0xd7ff00,
|
||||||
|
191: 0xd7ff5f,
|
||||||
|
192: 0xd7ff87,
|
||||||
|
193: 0xd7ffaf,
|
||||||
|
194: 0xd7ffd7,
|
||||||
|
195: 0xd7ffff,
|
||||||
|
196: 0xff0000,
|
||||||
|
197: 0xff005f,
|
||||||
|
198: 0xff0087,
|
||||||
|
199: 0xff00af,
|
||||||
|
200: 0xff00d7,
|
||||||
|
201: 0xff00ff,
|
||||||
|
202: 0xff5f00,
|
||||||
|
203: 0xff5f5f,
|
||||||
|
204: 0xff5f87,
|
||||||
|
205: 0xff5faf,
|
||||||
|
206: 0xff5fd7,
|
||||||
|
207: 0xff5fff,
|
||||||
|
208: 0xff8700,
|
||||||
|
209: 0xff875f,
|
||||||
|
210: 0xff8787,
|
||||||
|
211: 0xff87af,
|
||||||
|
212: 0xff87d7,
|
||||||
|
213: 0xff87ff,
|
||||||
|
214: 0xffaf00,
|
||||||
|
215: 0xffaf5f,
|
||||||
|
216: 0xffaf87,
|
||||||
|
217: 0xffafaf,
|
||||||
|
218: 0xffafd7,
|
||||||
|
219: 0xffafff,
|
||||||
|
220: 0xffd700,
|
||||||
|
221: 0xffd75f,
|
||||||
|
222: 0xffd787,
|
||||||
|
223: 0xffd7af,
|
||||||
|
224: 0xffd7d7,
|
||||||
|
225: 0xffd7ff,
|
||||||
|
226: 0xffff00,
|
||||||
|
227: 0xffff5f,
|
||||||
|
228: 0xffff87,
|
||||||
|
229: 0xffffaf,
|
||||||
|
230: 0xffffd7,
|
||||||
|
231: 0xffffff,
|
||||||
|
232: 0x080808,
|
||||||
|
233: 0x121212,
|
||||||
|
234: 0x1c1c1c,
|
||||||
|
235: 0x262626,
|
||||||
|
236: 0x303030,
|
||||||
|
237: 0x3a3a3a,
|
||||||
|
238: 0x444444,
|
||||||
|
239: 0x4e4e4e,
|
||||||
|
240: 0x585858,
|
||||||
|
241: 0x626262,
|
||||||
|
242: 0x6c6c6c,
|
||||||
|
243: 0x767676,
|
||||||
|
244: 0x808080,
|
||||||
|
245: 0x8a8a8a,
|
||||||
|
246: 0x949494,
|
||||||
|
247: 0x9e9e9e,
|
||||||
|
248: 0xa8a8a8,
|
||||||
|
249: 0xb2b2b2,
|
||||||
|
250: 0xbcbcbc,
|
||||||
|
251: 0xc6c6c6,
|
||||||
|
252: 0xd0d0d0,
|
||||||
|
253: 0xdadada,
|
||||||
|
254: 0xe4e4e4,
|
||||||
|
255: 0xeeeeee,
|
||||||
|
}
|
||||||
|
|
||||||
|
// `\033]0;TITLESTR\007`
|
||||||
|
func doTitleSequence(er *bytes.Reader) error {
|
||||||
|
var c byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c, err = er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != '0' && c != '2' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c, err = er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ';' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
title := make([]byte, 0, 80)
|
||||||
|
for {
|
||||||
|
c, err = er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c == 0x07 || c == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
title = append(title, c)
|
||||||
|
}
|
||||||
|
if len(title) > 0 {
|
||||||
|
title8, err := syscall.UTF16PtrFromString(string(title))
|
||||||
|
if err == nil {
|
||||||
|
procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write write data on console
|
||||||
|
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
|
||||||
|
handle := w.handle
|
||||||
|
|
||||||
|
var er *bytes.Reader
|
||||||
|
if w.rest.Len() > 0 {
|
||||||
|
var rest bytes.Buffer
|
||||||
|
w.rest.WriteTo(&rest)
|
||||||
|
w.rest.Reset()
|
||||||
|
rest.Write(data)
|
||||||
|
er = bytes.NewReader(rest.Bytes())
|
||||||
|
} else {
|
||||||
|
er = bytes.NewReader(data)
|
||||||
|
}
|
||||||
|
var bw [1]byte
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
c1, err := er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if c1 != 0x1b {
|
||||||
|
bw[0] = c1
|
||||||
|
w.out.Write(bw[:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c2, err := er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c2 {
|
||||||
|
case '>':
|
||||||
|
continue
|
||||||
|
case ']':
|
||||||
|
w.rest.WriteByte(c1)
|
||||||
|
w.rest.WriteByte(c2)
|
||||||
|
er.WriteTo(&w.rest)
|
||||||
|
if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
er = bytes.NewReader(w.rest.Bytes()[2:])
|
||||||
|
err := doTitleSequence(er)
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
w.rest.Reset()
|
||||||
|
continue
|
||||||
|
// https://github.com/mattn/go-colorable/issues/27
|
||||||
|
case '7':
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
w.oldpos = csbi.cursorPosition
|
||||||
|
continue
|
||||||
|
case '8':
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
|
||||||
|
continue
|
||||||
|
case 0x5b:
|
||||||
|
// execute part after switch
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w.rest.WriteByte(c1)
|
||||||
|
w.rest.WriteByte(c2)
|
||||||
|
er.WriteTo(&w.rest)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var m byte
|
||||||
|
for i, c := range w.rest.Bytes()[2:] {
|
||||||
|
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||||
|
m = c
|
||||||
|
er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
|
||||||
|
w.rest.Reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf.Write([]byte(string(c)))
|
||||||
|
}
|
||||||
|
if m == 0 {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m {
|
||||||
|
case 'A':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.y -= short(n)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'B':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.y += short(n)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'C':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.x += short(n)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'D':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.x -= short(n)
|
||||||
|
if csbi.cursorPosition.x < 0 {
|
||||||
|
csbi.cursorPosition.x = 0
|
||||||
|
}
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'E':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.x = 0
|
||||||
|
csbi.cursorPosition.y += short(n)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'F':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.x = 0
|
||||||
|
csbi.cursorPosition.y -= short(n)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'G':
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
csbi.cursorPosition.x = short(n - 1)
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'H', 'f':
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
token := strings.Split(buf.String(), ";")
|
||||||
|
switch len(token) {
|
||||||
|
case 1:
|
||||||
|
n1, err := strconv.Atoi(token[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
csbi.cursorPosition.y = short(n1 - 1)
|
||||||
|
case 2:
|
||||||
|
n1, err := strconv.Atoi(token[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n2, err := strconv.Atoi(token[1])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
csbi.cursorPosition.x = short(n2 - 1)
|
||||||
|
csbi.cursorPosition.y = short(n1 - 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
csbi.cursorPosition.y = 0
|
||||||
|
}
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||||
|
case 'J':
|
||||||
|
n := 0
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var count, written dword
|
||||||
|
var cursor coord
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
|
||||||
|
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||||
|
case 1:
|
||||||
|
cursor = coord{x: csbi.window.left, y: csbi.window.top}
|
||||||
|
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||||
|
case 2:
|
||||||
|
cursor = coord{x: csbi.window.left, y: csbi.window.top}
|
||||||
|
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||||
|
}
|
||||||
|
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||||
|
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||||
|
case 'K':
|
||||||
|
n := 0
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
n, err = strconv.Atoi(buf.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
var cursor coord
|
||||||
|
var count, written dword
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
|
||||||
|
count = dword(csbi.size.x - csbi.cursorPosition.x)
|
||||||
|
case 1:
|
||||||
|
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
|
||||||
|
count = dword(csbi.size.x - csbi.cursorPosition.x)
|
||||||
|
case 2:
|
||||||
|
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
|
||||||
|
count = dword(csbi.size.x)
|
||||||
|
}
|
||||||
|
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||||
|
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||||
|
case 'm':
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
attr := csbi.attributes
|
||||||
|
cs := buf.String()
|
||||||
|
if cs == "" {
|
||||||
|
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := strings.Split(cs, ";")
|
||||||
|
for i := 0; i < len(token); i++ {
|
||||||
|
ns := token[i]
|
||||||
|
if n, err = strconv.Atoi(ns); err == nil {
|
||||||
|
switch {
|
||||||
|
case n == 0 || n == 100:
|
||||||
|
attr = w.oldattr
|
||||||
|
case 1 <= n && n <= 5:
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
case n == 7:
|
||||||
|
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||||
|
case n == 22 || n == 25:
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
case n == 27:
|
||||||
|
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||||
|
case 30 <= n && n <= 37:
|
||||||
|
attr &= backgroundMask
|
||||||
|
if (n-30)&1 != 0 {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if (n-30)&2 != 0 {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if (n-30)&4 != 0 {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
case n == 38: // set foreground color.
|
||||||
|
if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
|
||||||
|
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||||
|
if n256foreAttr == nil {
|
||||||
|
n256setup()
|
||||||
|
}
|
||||||
|
attr &= backgroundMask
|
||||||
|
attr |= n256foreAttr[n256]
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
} else if len(token) == 5 && token[i+1] == "2" {
|
||||||
|
var r, g, b int
|
||||||
|
r, _ = strconv.Atoi(token[i+2])
|
||||||
|
g, _ = strconv.Atoi(token[i+3])
|
||||||
|
b, _ = strconv.Atoi(token[i+4])
|
||||||
|
i += 4
|
||||||
|
if r > 127 {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if g > 127 {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if b > 127 {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attr = attr & (w.oldattr & backgroundMask)
|
||||||
|
}
|
||||||
|
case n == 39: // reset foreground color.
|
||||||
|
attr &= backgroundMask
|
||||||
|
attr |= w.oldattr & foregroundMask
|
||||||
|
case 40 <= n && n <= 47:
|
||||||
|
attr &= foregroundMask
|
||||||
|
if (n-40)&1 != 0 {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if (n-40)&2 != 0 {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if (n-40)&4 != 0 {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
case n == 48: // set background color.
|
||||||
|
if i < len(token)-2 && token[i+1] == "5" {
|
||||||
|
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||||
|
if n256backAttr == nil {
|
||||||
|
n256setup()
|
||||||
|
}
|
||||||
|
attr &= foregroundMask
|
||||||
|
attr |= n256backAttr[n256]
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
} else if len(token) == 5 && token[i+1] == "2" {
|
||||||
|
var r, g, b int
|
||||||
|
r, _ = strconv.Atoi(token[i+2])
|
||||||
|
g, _ = strconv.Atoi(token[i+3])
|
||||||
|
b, _ = strconv.Atoi(token[i+4])
|
||||||
|
i += 4
|
||||||
|
if r > 127 {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if g > 127 {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if b > 127 {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attr = attr & (w.oldattr & foregroundMask)
|
||||||
|
}
|
||||||
|
case n == 49: // reset foreground color.
|
||||||
|
attr &= foregroundMask
|
||||||
|
attr |= w.oldattr & backgroundMask
|
||||||
|
case 90 <= n && n <= 97:
|
||||||
|
attr = (attr & backgroundMask)
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
if (n-90)&1 != 0 {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if (n-90)&2 != 0 {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if (n-90)&4 != 0 {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
case 100 <= n && n <= 107:
|
||||||
|
attr = (attr & foregroundMask)
|
||||||
|
attr |= backgroundIntensity
|
||||||
|
if (n-100)&1 != 0 {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if (n-100)&2 != 0 {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if (n-100)&4 != 0 {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'h':
|
||||||
|
var ci consoleCursorInfo
|
||||||
|
cs := buf.String()
|
||||||
|
if cs == "5>" {
|
||||||
|
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
ci.visible = 0
|
||||||
|
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
} else if cs == "?25" {
|
||||||
|
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
ci.visible = 1
|
||||||
|
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
} else if cs == "?1049" {
|
||||||
|
if w.althandle == 0 {
|
||||||
|
h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
|
||||||
|
w.althandle = syscall.Handle(h)
|
||||||
|
if w.althandle != 0 {
|
||||||
|
handle = w.althandle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'l':
|
||||||
|
var ci consoleCursorInfo
|
||||||
|
cs := buf.String()
|
||||||
|
if cs == "5>" {
|
||||||
|
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
ci.visible = 1
|
||||||
|
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
} else if cs == "?25" {
|
||||||
|
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
ci.visible = 0
|
||||||
|
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||||
|
} else if cs == "?1049" {
|
||||||
|
if w.althandle != 0 {
|
||||||
|
syscall.CloseHandle(w.althandle)
|
||||||
|
w.althandle = 0
|
||||||
|
handle = w.handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
w.oldpos = csbi.cursorPosition
|
||||||
|
case 'u':
|
||||||
|
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleColor struct {
|
||||||
|
rgb int
|
||||||
|
red bool
|
||||||
|
green bool
|
||||||
|
blue bool
|
||||||
|
intensity bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c consoleColor) foregroundAttr() (attr word) {
|
||||||
|
if c.red {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if c.green {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if c.blue {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
if c.intensity {
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c consoleColor) backgroundAttr() (attr word) {
|
||||||
|
if c.red {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if c.green {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if c.blue {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
if c.intensity {
|
||||||
|
attr |= backgroundIntensity
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var color16 = []consoleColor{
|
||||||
|
{0x000000, false, false, false, false},
|
||||||
|
{0x000080, false, false, true, false},
|
||||||
|
{0x008000, false, true, false, false},
|
||||||
|
{0x008080, false, true, true, false},
|
||||||
|
{0x800000, true, false, false, false},
|
||||||
|
{0x800080, true, false, true, false},
|
||||||
|
{0x808000, true, true, false, false},
|
||||||
|
{0xc0c0c0, true, true, true, false},
|
||||||
|
{0x808080, false, false, false, true},
|
||||||
|
{0x0000ff, false, false, true, true},
|
||||||
|
{0x00ff00, false, true, false, true},
|
||||||
|
{0x00ffff, false, true, true, true},
|
||||||
|
{0xff0000, true, false, false, true},
|
||||||
|
{0xff00ff, true, false, true, true},
|
||||||
|
{0xffff00, true, true, false, true},
|
||||||
|
{0xffffff, true, true, true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
type hsv struct {
|
||||||
|
h, s, v float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a hsv) dist(b hsv) float32 {
|
||||||
|
dh := a.h - b.h
|
||||||
|
switch {
|
||||||
|
case dh > 0.5:
|
||||||
|
dh = 1 - dh
|
||||||
|
case dh < -0.5:
|
||||||
|
dh = -1 - dh
|
||||||
|
}
|
||||||
|
ds := a.s - b.s
|
||||||
|
dv := a.v - b.v
|
||||||
|
return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHSV(rgb int) hsv {
|
||||||
|
r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
|
||||||
|
float32((rgb&0x00FF00)>>8)/256.0,
|
||||||
|
float32(rgb&0x0000FF)/256.0
|
||||||
|
min, max := minmax3f(r, g, b)
|
||||||
|
h := max - min
|
||||||
|
if h > 0 {
|
||||||
|
if max == r {
|
||||||
|
h = (g - b) / h
|
||||||
|
if h < 0 {
|
||||||
|
h += 6
|
||||||
|
}
|
||||||
|
} else if max == g {
|
||||||
|
h = 2 + (b-r)/h
|
||||||
|
} else {
|
||||||
|
h = 4 + (r-g)/h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h /= 6.0
|
||||||
|
s := max - min
|
||||||
|
if max != 0 {
|
||||||
|
s /= max
|
||||||
|
}
|
||||||
|
v := max
|
||||||
|
return hsv{h: h, s: s, v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
type hsvTable []hsv
|
||||||
|
|
||||||
|
func toHSVTable(rgbTable []consoleColor) hsvTable {
|
||||||
|
t := make(hsvTable, len(rgbTable))
|
||||||
|
for i, c := range rgbTable {
|
||||||
|
t[i] = toHSV(c.rgb)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t hsvTable) find(rgb int) consoleColor {
|
||||||
|
hsv := toHSV(rgb)
|
||||||
|
n := 7
|
||||||
|
l := float32(5.0)
|
||||||
|
for i, p := range t {
|
||||||
|
d := hsv.dist(p)
|
||||||
|
if d < l {
|
||||||
|
l, n = d, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color16[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func minmax3f(a, b, c float32) (min, max float32) {
|
||||||
|
if a < b {
|
||||||
|
if b < c {
|
||||||
|
return a, c
|
||||||
|
} else if a < c {
|
||||||
|
return a, b
|
||||||
|
} else {
|
||||||
|
return c, b
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if a < c {
|
||||||
|
return b, c
|
||||||
|
} else if b < c {
|
||||||
|
return b, a
|
||||||
|
} else {
|
||||||
|
return c, a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var n256foreAttr []word
|
||||||
|
var n256backAttr []word
|
||||||
|
|
||||||
|
func n256setup() {
|
||||||
|
n256foreAttr = make([]word, 256)
|
||||||
|
n256backAttr = make([]word, 256)
|
||||||
|
t := toHSVTable(color16)
|
||||||
|
for i, rgb := range color256 {
|
||||||
|
c := t.find(rgb)
|
||||||
|
n256foreAttr[i] = c.foregroundAttr()
|
||||||
|
n256backAttr[i] = c.backgroundAttr()
|
||||||
|
}
|
||||||
|
}
|
3
vendor/github.com/mattn/go-colorable/go.mod
generated
vendored
Normal file
3
vendor/github.com/mattn/go-colorable/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/mattn/go-colorable
|
||||||
|
|
||||||
|
require github.com/mattn/go-isatty v0.0.8
|
4
vendor/github.com/mattn/go-colorable/go.sum
generated
vendored
Normal file
4
vendor/github.com/mattn/go-colorable/go.sum
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
55
vendor/github.com/mattn/go-colorable/noncolorable.go
generated
vendored
Normal file
55
vendor/github.com/mattn/go-colorable/noncolorable.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package colorable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NonColorable hold writer but remove escape sequence.
|
||||||
|
type NonColorable struct {
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNonColorable return new instance of Writer which remove escape sequence from Writer.
|
||||||
|
func NewNonColorable(w io.Writer) io.Writer {
|
||||||
|
return &NonColorable{out: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write write data on console
|
||||||
|
func (w *NonColorable) Write(data []byte) (n int, err error) {
|
||||||
|
er := bytes.NewReader(data)
|
||||||
|
var bw [1]byte
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
c1, err := er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if c1 != 0x1b {
|
||||||
|
bw[0] = c1
|
||||||
|
w.out.Write(bw[:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c2, err := er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if c2 != 0x5b {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for {
|
||||||
|
c, err := er.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf.Write([]byte(string(c)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(data), nil
|
||||||
|
}
|
13
vendor/github.com/mattn/go-isatty/.travis.yml
generated
vendored
Normal file
13
vendor/github.com/mattn/go-isatty/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5
|
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||||
|
|
||||||
|
MIT License (Expat)
|
||||||
|
|
||||||
|
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.
|
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# go-isatty
|
||||||
|
|
||||||
|
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
|
||||||
|
[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
|
||||||
|
|
||||||
|
isatty for golang
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
|
fmt.Println("Is Terminal")
|
||||||
|
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||||
|
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Is Not Terminal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/mattn/go-isatty
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
* k-takata: base idea for IsCygwinTerminal
|
||||||
|
|
||||||
|
https://github.com/k-takata/go-iscygpty
|
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Package isatty implements interface to isatty
|
||||||
|
package isatty
|
3
vendor/github.com/mattn/go-isatty/go.mod
generated
vendored
Normal file
3
vendor/github.com/mattn/go-isatty/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/mattn/go-isatty
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
2
vendor/github.com/mattn/go-isatty/go.sum
generated
vendored
Normal file
2
vendor/github.com/mattn/go-isatty/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
23
vendor/github.com/mattn/go-isatty/isatty_android.go
generated
vendored
Normal file
23
vendor/github.com/mattn/go-isatty/isatty_android.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// +build android
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
|
// IsTerminal return true if the file descriptor is terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termios syscall.Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal. This is also always false on this environment.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
24
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
24
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// +build darwin freebsd openbsd netbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
// IsTerminal return true if the file descriptor is terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termios syscall.Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal. This is also always false on this environment.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
15
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
15
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// +build appengine js nacl
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
// IsTerminal returns true if the file descriptor is terminal which
|
||||||
|
// is always false on js and appengine classic which is a sandboxed PaaS.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal. This is also always false on this environment.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
22
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
22
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// +build solaris
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termio unix.Termio
|
||||||
|
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal. This is also always false on this environment.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
19
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
19
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// +build linux aix
|
||||||
|
// +build !appengine
|
||||||
|
// +build !android
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// IsTerminal return true if the file descriptor is terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal. This is also always false on this environment.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
return false
|
||||||
|
}
|
94
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
94
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// +build windows
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package isatty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileNameInfo uintptr = 2
|
||||||
|
fileTypePipe = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||||
|
procGetFileType = kernel32.NewProc("GetFileType")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Check if GetFileInformationByHandleEx is available.
|
||||||
|
if procGetFileInformationByHandleEx.Find() != nil {
|
||||||
|
procGetFileInformationByHandleEx = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal return true if the file descriptor is terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pipe name is used for cygwin/msys2 pty.
|
||||||
|
// Cygwin/MSYS2 PTY has a name like:
|
||||||
|
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||||
|
func isCygwinPipeName(name string) bool {
|
||||||
|
token := strings.Split(name, "-")
|
||||||
|
if len(token) < 5 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if token[0] != `\msys` && token[0] != `\cygwin` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if token[1] == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(token[2], "pty") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if token[3] != `from` && token[3] != `to` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if token[4] != "master" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||||
|
// terminal.
|
||||||
|
func IsCygwinTerminal(fd uintptr) bool {
|
||||||
|
if procGetFileInformationByHandleEx == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cygwin/msys's pty is a pipe.
|
||||||
|
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
||||||
|
if ft != fileTypePipe || e != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [2 + syscall.MAX_PATH]uint16
|
||||||
|
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
||||||
|
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
||||||
|
uintptr(len(buf)*2), 0, 0)
|
||||||
|
if r == 0 || e != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
l := *(*uint32)(unsafe.Pointer(&buf))
|
||||||
|
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
||||||
|
}
|
15
vendor/github.com/valyala/bytebufferpool/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/valyala/bytebufferpool/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
|
||||||
|
script:
|
||||||
|
# build test for supported platforms
|
||||||
|
- GOOS=linux go build
|
||||||
|
- GOOS=darwin go build
|
||||||
|
- GOOS=freebsd go build
|
||||||
|
- GOOS=windows go build
|
||||||
|
- GOARCH=386 go build
|
||||||
|
|
||||||
|
# run tests on a standard platform
|
||||||
|
- go test -v ./...
|
22
vendor/github.com/valyala/bytebufferpool/LICENSE
generated
vendored
Normal file
22
vendor/github.com/valyala/bytebufferpool/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
21
vendor/github.com/valyala/bytebufferpool/README.md
generated
vendored
Normal file
21
vendor/github.com/valyala/bytebufferpool/README.md
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[![Build Status](https://travis-ci.org/valyala/bytebufferpool.svg)](https://travis-ci.org/valyala/bytebufferpool)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/valyala/bytebufferpool?status.svg)](http://godoc.org/github.com/valyala/bytebufferpool)
|
||||||
|
[![Go Report](http://goreportcard.com/badge/valyala/bytebufferpool)](http://goreportcard.com/report/valyala/bytebufferpool)
|
||||||
|
|
||||||
|
# bytebufferpool
|
||||||
|
|
||||||
|
An implementation of a pool of byte buffers with anti-memory-waste protection.
|
||||||
|
|
||||||
|
The pool may waste limited amount of memory due to fragmentation.
|
||||||
|
This amount equals to the maximum total size of the byte buffers
|
||||||
|
in concurrent use.
|
||||||
|
|
||||||
|
# Benchmark results
|
||||||
|
Currently bytebufferpool is fastest and most effective buffer pool written in Go.
|
||||||
|
|
||||||
|
You can find results [here](https://omgnull.github.io/go-benchmark/buffer/).
|
||||||
|
|
||||||
|
# bytebufferpool users
|
||||||
|
|
||||||
|
* [fasthttp](https://github.com/valyala/fasthttp)
|
||||||
|
* [quicktemplate](https://github.com/valyala/quicktemplate)
|
111
vendor/github.com/valyala/bytebufferpool/bytebuffer.go
generated
vendored
Normal file
111
vendor/github.com/valyala/bytebufferpool/bytebuffer.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// ByteBuffer provides byte buffer, which can be used for minimizing
|
||||||
|
// memory allocations.
|
||||||
|
//
|
||||||
|
// ByteBuffer may be used with functions appending data to the given []byte
|
||||||
|
// slice. See example code for details.
|
||||||
|
//
|
||||||
|
// Use Get for obtaining an empty byte buffer.
|
||||||
|
type ByteBuffer struct {
|
||||||
|
|
||||||
|
// B is a byte buffer to use in append-like workloads.
|
||||||
|
// See example code for details.
|
||||||
|
B []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the size of the byte buffer.
|
||||||
|
func (b *ByteBuffer) Len() int {
|
||||||
|
return len(b.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom implements io.ReaderFrom.
|
||||||
|
//
|
||||||
|
// The function appends all the data read from r to b.
|
||||||
|
func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
p := b.B
|
||||||
|
nStart := int64(len(p))
|
||||||
|
nMax := int64(cap(p))
|
||||||
|
n := nStart
|
||||||
|
if nMax == 0 {
|
||||||
|
nMax = 64
|
||||||
|
p = make([]byte, nMax)
|
||||||
|
} else {
|
||||||
|
p = p[:nMax]
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if n == nMax {
|
||||||
|
nMax *= 2
|
||||||
|
bNew := make([]byte, nMax)
|
||||||
|
copy(bNew, p)
|
||||||
|
p = bNew
|
||||||
|
}
|
||||||
|
nn, err := r.Read(p[n:])
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
b.B = p[:n]
|
||||||
|
n -= nStart
|
||||||
|
if err == io.EOF {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements io.WriterTo.
|
||||||
|
func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(b.B)
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns b.B, i.e. all the bytes accumulated in the buffer.
|
||||||
|
//
|
||||||
|
// The purpose of this function is bytes.Buffer compatibility.
|
||||||
|
func (b *ByteBuffer) Bytes() []byte {
|
||||||
|
return b.B
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer - it appends p to ByteBuffer.B
|
||||||
|
func (b *ByteBuffer) Write(p []byte) (int, error) {
|
||||||
|
b.B = append(b.B, p...)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte appends the byte c to the buffer.
|
||||||
|
//
|
||||||
|
// The purpose of this function is bytes.Buffer compatibility.
|
||||||
|
//
|
||||||
|
// The function always returns nil.
|
||||||
|
func (b *ByteBuffer) WriteByte(c byte) error {
|
||||||
|
b.B = append(b.B, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString appends s to ByteBuffer.B.
|
||||||
|
func (b *ByteBuffer) WriteString(s string) (int, error) {
|
||||||
|
b.B = append(b.B, s...)
|
||||||
|
return len(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets ByteBuffer.B to p.
|
||||||
|
func (b *ByteBuffer) Set(p []byte) {
|
||||||
|
b.B = append(b.B[:0], p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetString sets ByteBuffer.B to s.
|
||||||
|
func (b *ByteBuffer) SetString(s string) {
|
||||||
|
b.B = append(b.B[:0], s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns string representation of ByteBuffer.B.
|
||||||
|
func (b *ByteBuffer) String() string {
|
||||||
|
return string(b.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset makes ByteBuffer.B empty.
|
||||||
|
func (b *ByteBuffer) Reset() {
|
||||||
|
b.B = b.B[:0]
|
||||||
|
}
|
7
vendor/github.com/valyala/bytebufferpool/doc.go
generated
vendored
Normal file
7
vendor/github.com/valyala/bytebufferpool/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Package bytebufferpool implements a pool of byte buffers
|
||||||
|
// with anti-fragmentation protection.
|
||||||
|
//
|
||||||
|
// The pool may waste limited amount of memory due to fragmentation.
|
||||||
|
// This amount equals to the maximum total size of the byte buffers
|
||||||
|
// in concurrent use.
|
||||||
|
package bytebufferpool
|
151
vendor/github.com/valyala/bytebufferpool/pool.go
generated
vendored
Normal file
151
vendor/github.com/valyala/bytebufferpool/pool.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package bytebufferpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minBitSize = 6 // 2**6=64 is a CPU cache line size
|
||||||
|
steps = 20
|
||||||
|
|
||||||
|
minSize = 1 << minBitSize
|
||||||
|
maxSize = 1 << (minBitSize + steps - 1)
|
||||||
|
|
||||||
|
calibrateCallsThreshold = 42000
|
||||||
|
maxPercentile = 0.95
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pool represents byte buffer pool.
|
||||||
|
//
|
||||||
|
// Distinct pools may be used for distinct types of byte buffers.
|
||||||
|
// Properly determined byte buffer types with their own pools may help reducing
|
||||||
|
// memory waste.
|
||||||
|
type Pool struct {
|
||||||
|
calls [steps]uint64
|
||||||
|
calibrating uint64
|
||||||
|
|
||||||
|
defaultSize uint64
|
||||||
|
maxSize uint64
|
||||||
|
|
||||||
|
pool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultPool Pool
|
||||||
|
|
||||||
|
// Get returns an empty byte buffer from the pool.
|
||||||
|
//
|
||||||
|
// Got byte buffer may be returned to the pool via Put call.
|
||||||
|
// This reduces the number of memory allocations required for byte buffer
|
||||||
|
// management.
|
||||||
|
func Get() *ByteBuffer { return defaultPool.Get() }
|
||||||
|
|
||||||
|
// Get returns new byte buffer with zero length.
|
||||||
|
//
|
||||||
|
// The byte buffer may be returned to the pool via Put after the use
|
||||||
|
// in order to minimize GC overhead.
|
||||||
|
func (p *Pool) Get() *ByteBuffer {
|
||||||
|
v := p.pool.Get()
|
||||||
|
if v != nil {
|
||||||
|
return v.(*ByteBuffer)
|
||||||
|
}
|
||||||
|
return &ByteBuffer{
|
||||||
|
B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put returns byte buffer to the pool.
|
||||||
|
//
|
||||||
|
// ByteBuffer.B mustn't be touched after returning it to the pool.
|
||||||
|
// Otherwise data races will occur.
|
||||||
|
func Put(b *ByteBuffer) { defaultPool.Put(b) }
|
||||||
|
|
||||||
|
// Put releases byte buffer obtained via Get to the pool.
|
||||||
|
//
|
||||||
|
// The buffer mustn't be accessed after returning to the pool.
|
||||||
|
func (p *Pool) Put(b *ByteBuffer) {
|
||||||
|
idx := index(len(b.B))
|
||||||
|
|
||||||
|
if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
|
||||||
|
p.calibrate()
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSize := int(atomic.LoadUint64(&p.maxSize))
|
||||||
|
if maxSize == 0 || cap(b.B) <= maxSize {
|
||||||
|
b.Reset()
|
||||||
|
p.pool.Put(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) calibrate() {
|
||||||
|
if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a := make(callSizes, 0, steps)
|
||||||
|
var callsSum uint64
|
||||||
|
for i := uint64(0); i < steps; i++ {
|
||||||
|
calls := atomic.SwapUint64(&p.calls[i], 0)
|
||||||
|
callsSum += calls
|
||||||
|
a = append(a, callSize{
|
||||||
|
calls: calls,
|
||||||
|
size: minSize << i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Sort(a)
|
||||||
|
|
||||||
|
defaultSize := a[0].size
|
||||||
|
maxSize := defaultSize
|
||||||
|
|
||||||
|
maxSum := uint64(float64(callsSum) * maxPercentile)
|
||||||
|
callsSum = 0
|
||||||
|
for i := 0; i < steps; i++ {
|
||||||
|
if callsSum > maxSum {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
callsSum += a[i].calls
|
||||||
|
size := a[i].size
|
||||||
|
if size > maxSize {
|
||||||
|
maxSize = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreUint64(&p.defaultSize, defaultSize)
|
||||||
|
atomic.StoreUint64(&p.maxSize, maxSize)
|
||||||
|
|
||||||
|
atomic.StoreUint64(&p.calibrating, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type callSize struct {
|
||||||
|
calls uint64
|
||||||
|
size uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type callSizes []callSize
|
||||||
|
|
||||||
|
func (ci callSizes) Len() int {
|
||||||
|
return len(ci)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci callSizes) Less(i, j int) bool {
|
||||||
|
return ci[i].calls > ci[j].calls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci callSizes) Swap(i, j int) {
|
||||||
|
ci[i], ci[j] = ci[j], ci[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(n int) int {
|
||||||
|
n--
|
||||||
|
n >>= minBitSize
|
||||||
|
idx := 0
|
||||||
|
for n > 0 {
|
||||||
|
n >>= 1
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
if idx >= steps {
|
||||||
|
idx = steps - 1
|
||||||
|
}
|
||||||
|
return idx
|
||||||
|
}
|
22
vendor/github.com/valyala/fasttemplate/LICENSE
generated
vendored
Normal file
22
vendor/github.com/valyala/fasttemplate/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Aliaksandr Valialkin
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
85
vendor/github.com/valyala/fasttemplate/README.md
generated
vendored
Normal file
85
vendor/github.com/valyala/fasttemplate/README.md
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
fasttemplate
|
||||||
|
============
|
||||||
|
|
||||||
|
Simple and fast template engine for Go.
|
||||||
|
|
||||||
|
Fasttemplate peforms only a single task - it substitutes template placeholders
|
||||||
|
with user-defined values. At high speed :)
|
||||||
|
|
||||||
|
Take a look at [quicktemplate](https://github.com/valyala/quicktemplate) if you need fast yet powerful html template engine.
|
||||||
|
|
||||||
|
*Please note that fasttemplate doesn't do any escaping on template values
|
||||||
|
unlike [html/template](http://golang.org/pkg/html/template/) do. So values
|
||||||
|
must be properly escaped before passing them to fasttemplate.*
|
||||||
|
|
||||||
|
Fasttemplate is faster than [text/template](http://golang.org/pkg/text/template/),
|
||||||
|
[strings.Replace](http://golang.org/pkg/strings/#Replace),
|
||||||
|
[strings.Replacer](http://golang.org/pkg/strings/#Replacer)
|
||||||
|
and [fmt.Fprintf](https://golang.org/pkg/fmt/#Fprintf) on placeholders' substitution.
|
||||||
|
|
||||||
|
Below are benchmark results comparing fasttemplate performance to text/template,
|
||||||
|
strings.Replace, strings.Replacer and fmt.Fprintf:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go test -bench=. -benchmem
|
||||||
|
PASS
|
||||||
|
BenchmarkFmtFprintf-4 2000000 790 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkStringsReplace-4 500000 3474 ns/op 2112 B/op 14 allocs/op
|
||||||
|
BenchmarkStringsReplacer-4 500000 2657 ns/op 2256 B/op 23 allocs/op
|
||||||
|
BenchmarkTextTemplate-4 500000 3333 ns/op 336 B/op 19 allocs/op
|
||||||
|
BenchmarkFastTemplateExecuteFunc-4 5000000 349 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkFastTemplateExecute-4 3000000 383 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkFastTemplateExecuteFuncString-4 3000000 549 ns/op 144 B/op 1 allocs/op
|
||||||
|
BenchmarkFastTemplateExecuteString-4 3000000 572 ns/op 144 B/op 1 allocs/op
|
||||||
|
BenchmarkFastTemplateExecuteTagFunc-4 2000000 743 ns/op 144 B/op 3 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Docs
|
||||||
|
====
|
||||||
|
|
||||||
|
See http://godoc.org/github.com/valyala/fasttemplate .
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
```go
|
||||||
|
template := "http://{{host}}/?q={{query}}&foo={{bar}}{{bar}}"
|
||||||
|
t := fasttemplate.New(template, "{{", "}}")
|
||||||
|
s := t.ExecuteString(map[string]interface{}{
|
||||||
|
"host": "google.com",
|
||||||
|
"query": url.QueryEscape("hello=world"),
|
||||||
|
"bar": "foobar",
|
||||||
|
})
|
||||||
|
fmt.Printf("%s", s)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// http://google.com/?q=hello%3Dworld&foo=foobarfoobar
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Advanced usage
|
||||||
|
==============
|
||||||
|
|
||||||
|
```go
|
||||||
|
template := "Hello, [user]! You won [prize]!!! [foobar]"
|
||||||
|
t, err := fasttemplate.NewTemplate(template, "[", "]")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unexpected error when parsing template: %s", err)
|
||||||
|
}
|
||||||
|
s := t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
|
||||||
|
switch tag {
|
||||||
|
case "user":
|
||||||
|
return w.Write([]byte("John"))
|
||||||
|
case "prize":
|
||||||
|
return w.Write([]byte("$100500"))
|
||||||
|
default:
|
||||||
|
return w.Write([]byte(fmt.Sprintf("[unknown tag %q]", tag)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
fmt.Printf("%s", s)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Hello, John! You won $100500!!! [unknown tag "foobar"]
|
||||||
|
```
|
3
vendor/github.com/valyala/fasttemplate/go.mod
generated
vendored
Normal file
3
vendor/github.com/valyala/fasttemplate/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/valyala/fasttemplate
|
||||||
|
|
||||||
|
require github.com/valyala/bytebufferpool v1.0.0
|
2
vendor/github.com/valyala/fasttemplate/go.sum
generated
vendored
Normal file
2
vendor/github.com/valyala/fasttemplate/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
317
vendor/github.com/valyala/fasttemplate/template.go
generated
vendored
Normal file
317
vendor/github.com/valyala/fasttemplate/template.go
generated
vendored
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
// Package fasttemplate implements simple and fast template library.
|
||||||
|
//
|
||||||
|
// Fasttemplate is faster than text/template, strings.Replace
|
||||||
|
// and strings.Replacer.
|
||||||
|
//
|
||||||
|
// Fasttemplate ideally fits for fast and simple placeholders' substitutions.
|
||||||
|
package fasttemplate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/valyala/bytebufferpool"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
||||||
|
//
|
||||||
|
// Returns the number of bytes written to w.
|
||||||
|
//
|
||||||
|
// This function is optimized for constantly changing templates.
|
||||||
|
// Use Template.ExecuteFunc for frozen templates.
|
||||||
|
func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error) {
|
||||||
|
s := unsafeString2Bytes(template)
|
||||||
|
a := unsafeString2Bytes(startTag)
|
||||||
|
b := unsafeString2Bytes(endTag)
|
||||||
|
|
||||||
|
var nn int64
|
||||||
|
var ni int
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
n := bytes.Index(s, a)
|
||||||
|
if n < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ni, err = w.Write(s[:n])
|
||||||
|
nn += int64(ni)
|
||||||
|
if err != nil {
|
||||||
|
return nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[n+len(a):]
|
||||||
|
n = bytes.Index(s, b)
|
||||||
|
if n < 0 {
|
||||||
|
// cannot find end tag - just write it to the output.
|
||||||
|
ni, _ = w.Write(a)
|
||||||
|
nn += int64(ni)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ni, err = f(w, unsafeBytes2String(s[:n]))
|
||||||
|
nn += int64(ni)
|
||||||
|
s = s[n+len(b):]
|
||||||
|
}
|
||||||
|
ni, err = w.Write(s)
|
||||||
|
nn += int64(ni)
|
||||||
|
|
||||||
|
return nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute substitutes template tags (placeholders) with the corresponding
|
||||||
|
// values from the map m and writes the result to the given writer w.
|
||||||
|
//
|
||||||
|
// Substitution map m may contain values with the following types:
|
||||||
|
// * []byte - the fastest value type
|
||||||
|
// * string - convenient value type
|
||||||
|
// * TagFunc - flexible value type
|
||||||
|
//
|
||||||
|
// Returns the number of bytes written to w.
|
||||||
|
//
|
||||||
|
// This function is optimized for constantly changing templates.
|
||||||
|
// Use Template.Execute for frozen templates.
|
||||||
|
func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
|
||||||
|
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
||||||
|
// and substitutes it with the data written to TagFunc's w.
|
||||||
|
//
|
||||||
|
// Returns the resulting string.
|
||||||
|
//
|
||||||
|
// This function is optimized for constantly changing templates.
|
||||||
|
// Use Template.ExecuteFuncString for frozen templates.
|
||||||
|
func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
|
||||||
|
tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag))
|
||||||
|
if tagsCount == 0 {
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
bb := byteBufferPool.Get()
|
||||||
|
if _, err := ExecuteFunc(template, startTag, endTag, bb, f); err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
||||||
|
}
|
||||||
|
s := string(bb.B)
|
||||||
|
bb.Reset()
|
||||||
|
byteBufferPool.Put(bb)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var byteBufferPool bytebufferpool.Pool
|
||||||
|
|
||||||
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
||||||
|
// values from the map m and returns the result.
|
||||||
|
//
|
||||||
|
// Substitution map m may contain values with the following types:
|
||||||
|
// * []byte - the fastest value type
|
||||||
|
// * string - convenient value type
|
||||||
|
// * TagFunc - flexible value type
|
||||||
|
//
|
||||||
|
// This function is optimized for constantly changing templates.
|
||||||
|
// Use Template.ExecuteString for frozen templates.
|
||||||
|
func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {
|
||||||
|
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template implements simple template engine, which can be used for fast
|
||||||
|
// tags' (aka placeholders) substitution.
|
||||||
|
type Template struct {
|
||||||
|
template string
|
||||||
|
startTag string
|
||||||
|
endTag string
|
||||||
|
|
||||||
|
texts [][]byte
|
||||||
|
tags []string
|
||||||
|
byteBufferPool bytebufferpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New parses the given template using the given startTag and endTag
|
||||||
|
// as tag start and tag end.
|
||||||
|
//
|
||||||
|
// The returned template can be executed by concurrently running goroutines
|
||||||
|
// using Execute* methods.
|
||||||
|
//
|
||||||
|
// New panics if the given template cannot be parsed. Use NewTemplate instead
|
||||||
|
// if template may contain errors.
|
||||||
|
func New(template, startTag, endTag string) *Template {
|
||||||
|
t, err := NewTemplate(template, startTag, endTag)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTemplate parses the given template using the given startTag and endTag
|
||||||
|
// as tag start and tag end.
|
||||||
|
//
|
||||||
|
// The returned template can be executed by concurrently running goroutines
|
||||||
|
// using Execute* methods.
|
||||||
|
func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
||||||
|
var t Template
|
||||||
|
err := t.Reset(template, startTag, endTag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagFunc can be used as a substitution value in the map passed to Execute*.
|
||||||
|
// Execute* functions pass tag (placeholder) name in 'tag' argument.
|
||||||
|
//
|
||||||
|
// TagFunc must be safe to call from concurrently running goroutines.
|
||||||
|
//
|
||||||
|
// TagFunc must write contents to w and return the number of bytes written.
|
||||||
|
type TagFunc func(w io.Writer, tag string) (int, error)
|
||||||
|
|
||||||
|
// Reset resets the template t to new one defined by
|
||||||
|
// template, startTag and endTag.
|
||||||
|
//
|
||||||
|
// Reset allows Template object re-use.
|
||||||
|
//
|
||||||
|
// Reset may be called only if no other goroutines call t methods at the moment.
|
||||||
|
func (t *Template) Reset(template, startTag, endTag string) error {
|
||||||
|
// Keep these vars in t, so GC won't collect them and won't break
|
||||||
|
// vars derived via unsafe*
|
||||||
|
t.template = template
|
||||||
|
t.startTag = startTag
|
||||||
|
t.endTag = endTag
|
||||||
|
t.texts = t.texts[:0]
|
||||||
|
t.tags = t.tags[:0]
|
||||||
|
|
||||||
|
if len(startTag) == 0 {
|
||||||
|
panic("startTag cannot be empty")
|
||||||
|
}
|
||||||
|
if len(endTag) == 0 {
|
||||||
|
panic("endTag cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := unsafeString2Bytes(template)
|
||||||
|
a := unsafeString2Bytes(startTag)
|
||||||
|
b := unsafeString2Bytes(endTag)
|
||||||
|
|
||||||
|
tagsCount := bytes.Count(s, a)
|
||||||
|
if tagsCount == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagsCount+1 > cap(t.texts) {
|
||||||
|
t.texts = make([][]byte, 0, tagsCount+1)
|
||||||
|
}
|
||||||
|
if tagsCount > cap(t.tags) {
|
||||||
|
t.tags = make([]string, 0, tagsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
n := bytes.Index(s, a)
|
||||||
|
if n < 0 {
|
||||||
|
t.texts = append(t.texts, s)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.texts = append(t.texts, s[:n])
|
||||||
|
|
||||||
|
s = s[n+len(a):]
|
||||||
|
n = bytes.Index(s, b)
|
||||||
|
if n < 0 {
|
||||||
|
return fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.tags = append(t.tags, unsafeBytes2String(s[:n]))
|
||||||
|
s = s[n+len(b):]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
||||||
|
//
|
||||||
|
// Returns the number of bytes written to w.
|
||||||
|
//
|
||||||
|
// This function is optimized for frozen templates.
|
||||||
|
// Use ExecuteFunc for constantly changing templates.
|
||||||
|
func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
|
||||||
|
var nn int64
|
||||||
|
|
||||||
|
n := len(t.texts) - 1
|
||||||
|
if n == -1 {
|
||||||
|
ni, err := w.Write(unsafeString2Bytes(t.template))
|
||||||
|
return int64(ni), err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
ni, err := w.Write(t.texts[i])
|
||||||
|
nn += int64(ni)
|
||||||
|
if err != nil {
|
||||||
|
return nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ni, err = f(w, t.tags[i])
|
||||||
|
nn += int64(ni)
|
||||||
|
if err != nil {
|
||||||
|
return nn, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ni, err := w.Write(t.texts[n])
|
||||||
|
nn += int64(ni)
|
||||||
|
return nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute substitutes template tags (placeholders) with the corresponding
|
||||||
|
// values from the map m and writes the result to the given writer w.
|
||||||
|
//
|
||||||
|
// Substitution map m may contain values with the following types:
|
||||||
|
// * []byte - the fastest value type
|
||||||
|
// * string - convenient value type
|
||||||
|
// * TagFunc - flexible value type
|
||||||
|
//
|
||||||
|
// Returns the number of bytes written to w.
|
||||||
|
func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
|
||||||
|
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
||||||
|
// and substitutes it with the data written to TagFunc's w.
|
||||||
|
//
|
||||||
|
// Returns the resulting string.
|
||||||
|
//
|
||||||
|
// This function is optimized for frozen templates.
|
||||||
|
// Use ExecuteFuncString for constantly changing templates.
|
||||||
|
func (t *Template) ExecuteFuncString(f TagFunc) string {
|
||||||
|
bb := t.byteBufferPool.Get()
|
||||||
|
if _, err := t.ExecuteFunc(bb, f); err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
||||||
|
}
|
||||||
|
s := string(bb.Bytes())
|
||||||
|
bb.Reset()
|
||||||
|
t.byteBufferPool.Put(bb)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
||||||
|
// values from the map m and returns the result.
|
||||||
|
//
|
||||||
|
// Substitution map m may contain values with the following types:
|
||||||
|
// * []byte - the fastest value type
|
||||||
|
// * string - convenient value type
|
||||||
|
// * TagFunc - flexible value type
|
||||||
|
//
|
||||||
|
// This function is optimized for frozen templates.
|
||||||
|
// Use ExecuteString for constantly changing templates.
|
||||||
|
func (t *Template) ExecuteString(m map[string]interface{}) string {
|
||||||
|
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
|
||||||
|
v := m[tag]
|
||||||
|
if v == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
switch value := v.(type) {
|
||||||
|
case []byte:
|
||||||
|
return w.Write(value)
|
||||||
|
case string:
|
||||||
|
return w.Write([]byte(value))
|
||||||
|
case TagFunc:
|
||||||
|
return value(w, tag)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
|
||||||
|
}
|
||||||
|
}
|
22
vendor/github.com/valyala/fasttemplate/unsafe.go
generated
vendored
Normal file
22
vendor/github.com/valyala/fasttemplate/unsafe.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package fasttemplate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unsafeBytes2String(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsafeString2Bytes(s string) []byte {
|
||||||
|
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||||
|
bh := reflect.SliceHeader{
|
||||||
|
Data: sh.Data,
|
||||||
|
Len: sh.Len,
|
||||||
|
Cap: sh.Len,
|
||||||
|
}
|
||||||
|
return *(*[]byte)(unsafe.Pointer(&bh))
|
||||||
|
}
|
11
vendor/github.com/valyala/fasttemplate/unsafe_gae.go
generated
vendored
Normal file
11
vendor/github.com/valyala/fasttemplate/unsafe_gae.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package fasttemplate
|
||||||
|
|
||||||
|
func unsafeBytes2String(b []byte) string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsafeString2Bytes(s string) []byte {
|
||||||
|
return []byte(s)
|
||||||
|
}
|
3
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at https://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at https://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
956
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
Normal file
956
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
Normal file
@ -0,0 +1,956 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package acme provides an implementation of the
|
||||||
|
// Automatic Certificate Management Environment (ACME) spec.
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02 for details.
|
||||||
|
//
|
||||||
|
// Most common scenarios will want to use autocert subdirectory instead,
|
||||||
|
// which provides automatic access to certificates from Let's Encrypt
|
||||||
|
// and any other ACME-based CA.
|
||||||
|
//
|
||||||
|
// This package is a work in progress and makes no API stability promises.
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||||
|
LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
// ALPNProto is the ALPN protocol name used by a CA server when validating
|
||||||
|
// tls-alpn-01 challenges.
|
||||||
|
//
|
||||||
|
// Package users must ensure their servers can negotiate the ACME ALPN in
|
||||||
|
// order for tls-alpn-01 challenge verifications to succeed.
|
||||||
|
// See the crypto/tls package's Config.NextProtos field.
|
||||||
|
ALPNProto = "acme-tls/1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||||
|
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||||
|
maxCertSize = 1 << 20 // max size of a certificate, in bytes
|
||||||
|
|
||||||
|
// Max number of collected nonces kept in memory.
|
||||||
|
// Expect usual peak of 1 or 2.
|
||||||
|
maxNonces = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is an ACME client.
|
||||||
|
// The only required field is Key. An example of creating a client with a new key
|
||||||
|
// is as follows:
|
||||||
|
//
|
||||||
|
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// client := &Client{Key: key}
|
||||||
|
//
|
||||||
|
type Client struct {
|
||||||
|
// Key is the account key used to register with a CA and sign requests.
|
||||||
|
// Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
|
||||||
|
//
|
||||||
|
// The following algorithms are supported:
|
||||||
|
// RS256, ES256, ES384 and ES512.
|
||||||
|
// See RFC7518 for more details about the algorithms.
|
||||||
|
Key crypto.Signer
|
||||||
|
|
||||||
|
// HTTPClient optionally specifies an HTTP client to use
|
||||||
|
// instead of http.DefaultClient.
|
||||||
|
HTTPClient *http.Client
|
||||||
|
|
||||||
|
// DirectoryURL points to the CA directory endpoint.
|
||||||
|
// If empty, LetsEncryptURL is used.
|
||||||
|
// Mutating this value after a successful call of Client's Discover method
|
||||||
|
// will have no effect.
|
||||||
|
DirectoryURL string
|
||||||
|
|
||||||
|
// RetryBackoff computes the duration after which the nth retry of a failed request
|
||||||
|
// should occur. The value of n for the first call on failure is 1.
|
||||||
|
// The values of r and resp are the request and response of the last failed attempt.
|
||||||
|
// If the returned value is negative or zero, no more retries are done and an error
|
||||||
|
// is returned to the caller of the original method.
|
||||||
|
//
|
||||||
|
// Requests which result in a 4xx client error are not retried,
|
||||||
|
// except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests.
|
||||||
|
//
|
||||||
|
// If RetryBackoff is nil, a truncated exponential backoff algorithm
|
||||||
|
// with the ceiling of 10 seconds is used, where each subsequent retry n
|
||||||
|
// is done after either ("Retry-After" + jitter) or (2^n seconds + jitter),
|
||||||
|
// preferring the former if "Retry-After" header is found in the resp.
|
||||||
|
// The jitter is a random value up to 1 second.
|
||||||
|
RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
|
||||||
|
|
||||||
|
// UserAgent is prepended to the User-Agent header sent to the ACME server,
|
||||||
|
// which by default is this package's name and version.
|
||||||
|
//
|
||||||
|
// Reusable libraries and tools in particular should set this value to be
|
||||||
|
// identifiable by the server, in case they are causing issues.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
dirMu sync.Mutex // guards writes to dir
|
||||||
|
dir *Directory // cached result of Client's Discover method
|
||||||
|
|
||||||
|
noncesMu sync.Mutex
|
||||||
|
nonces map[string]struct{} // nonces collected from previous responses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover performs ACME server discovery using c.DirectoryURL.
|
||||||
|
//
|
||||||
|
// It caches successful result. So, subsequent calls will not result in
|
||||||
|
// a network round-trip. This also means mutating c.DirectoryURL after successful call
|
||||||
|
// of this method will have no effect.
|
||||||
|
func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||||
|
c.dirMu.Lock()
|
||||||
|
defer c.dirMu.Unlock()
|
||||||
|
if c.dir != nil {
|
||||||
|
return *c.dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.get(ctx, c.directoryURL(), wantStatus(http.StatusOK))
|
||||||
|
if err != nil {
|
||||||
|
return Directory{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
c.addNonce(res.Header)
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
Reg string `json:"new-reg"`
|
||||||
|
Authz string `json:"new-authz"`
|
||||||
|
Cert string `json:"new-cert"`
|
||||||
|
Revoke string `json:"revoke-cert"`
|
||||||
|
Meta struct {
|
||||||
|
Terms string `json:"terms-of-service"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
CAA []string `json:"caa-identities"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return Directory{}, err
|
||||||
|
}
|
||||||
|
c.dir = &Directory{
|
||||||
|
RegURL: v.Reg,
|
||||||
|
AuthzURL: v.Authz,
|
||||||
|
CertURL: v.Cert,
|
||||||
|
RevokeURL: v.Revoke,
|
||||||
|
Terms: v.Meta.Terms,
|
||||||
|
Website: v.Meta.Website,
|
||||||
|
CAA: v.Meta.CAA,
|
||||||
|
}
|
||||||
|
return *c.dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) directoryURL() string {
|
||||||
|
if c.DirectoryURL != "" {
|
||||||
|
return c.DirectoryURL
|
||||||
|
}
|
||||||
|
return LetsEncryptURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
||||||
|
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
||||||
|
// with a different duration.
|
||||||
|
// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
|
||||||
|
//
|
||||||
|
// In the case where CA server does not provide the issued certificate in the response,
|
||||||
|
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||||
|
// In such a scenario, the caller can cancel the polling with ctx.
|
||||||
|
//
|
||||||
|
// CreateCert returns an error if the CA's response or chain was unreasonably large.
|
||||||
|
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||||
|
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
|
||||||
|
if _, err := c.Discover(ctx); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
CSR string `json:"csr"`
|
||||||
|
NotBefore string `json:"notBefore,omitempty"`
|
||||||
|
NotAfter string `json:"notAfter,omitempty"`
|
||||||
|
}{
|
||||||
|
Resource: "new-cert",
|
||||||
|
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||||
|
}
|
||||||
|
now := timeNow()
|
||||||
|
req.NotBefore = now.Format(time.RFC3339)
|
||||||
|
if exp > 0 {
|
||||||
|
req.NotAfter = now.Add(exp).Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.post(ctx, c.Key, c.dir.CertURL, req, wantStatus(http.StatusCreated))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
curl := res.Header.Get("Location") // cert permanent URL
|
||||||
|
if res.ContentLength == 0 {
|
||||||
|
// no cert in the body; poll until we get it
|
||||||
|
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||||
|
return cert, curl, err
|
||||||
|
}
|
||||||
|
// slurp issued cert and CA chain, if requested
|
||||||
|
cert, err := c.responseCert(ctx, res, bundle)
|
||||||
|
return cert, curl, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchCert retrieves already issued certificate from the given url, in DER format.
|
||||||
|
// It retries the request until the certificate is successfully retrieved,
|
||||||
|
// context is cancelled by the caller or an error response is received.
|
||||||
|
//
|
||||||
|
// The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
|
||||||
|
//
|
||||||
|
// FetchCert returns an error if the CA's response or chain was unreasonably large.
|
||||||
|
// Callers are encouraged to parse the returned value to ensure the certificate is valid
|
||||||
|
// and has expected features.
|
||||||
|
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||||
|
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.responseCert(ctx, res, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
|
||||||
|
//
|
||||||
|
// The key argument, used to sign the request, must be authorized
|
||||||
|
// to revoke the certificate. It's up to the CA to decide which keys are authorized.
|
||||||
|
// For instance, the key pair of the certificate may be authorized.
|
||||||
|
// If the key is nil, c.Key is used instead.
|
||||||
|
func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
|
||||||
|
if _, err := c.Discover(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Cert string `json:"certificate"`
|
||||||
|
Reason int `json:"reason"`
|
||||||
|
}{
|
||||||
|
Resource: "revoke-cert",
|
||||||
|
Cert: base64.RawURLEncoding.EncodeToString(cert),
|
||||||
|
Reason: int(reason),
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
key = c.Key
|
||||||
|
}
|
||||||
|
res, err := c.post(ctx, key, c.dir.RevokeURL, body, wantStatus(http.StatusOK))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
|
||||||
|
// during account registration. See Register method of Client for more details.
|
||||||
|
func AcceptTOS(tosURL string) bool { return true }
|
||||||
|
|
||||||
|
// Register creates a new account registration by following the "new-reg" flow.
|
||||||
|
// It returns the registered account. The account is not modified.
|
||||||
|
//
|
||||||
|
// The registration may require the caller to agree to the CA's Terms of Service (TOS).
|
||||||
|
// If so, and the account has not indicated the acceptance of the terms (see Account for details),
|
||||||
|
// Register calls prompt with a TOS URL provided by the CA. Prompt should report
|
||||||
|
// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
|
||||||
|
func (c *Client) Register(ctx context.Context, a *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||||
|
if _, err := c.Discover(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if a, err = c.doReg(ctx, c.dir.RegURL, "new-reg", a); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var accept bool
|
||||||
|
if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms {
|
||||||
|
accept = prompt(a.CurrentTerms)
|
||||||
|
}
|
||||||
|
if accept {
|
||||||
|
a.AgreedTerms = a.CurrentTerms
|
||||||
|
a, err = c.UpdateReg(ctx, a)
|
||||||
|
}
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReg retrieves an existing registration.
|
||||||
|
// The url argument is an Account URI.
|
||||||
|
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
|
||||||
|
a, err := c.doReg(ctx, url, "reg", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.URI = url
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReg updates an existing registration.
|
||||||
|
// It returns an updated account copy. The provided account is not modified.
|
||||||
|
func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) {
|
||||||
|
uri := a.URI
|
||||||
|
a, err := c.doReg(ctx, uri, "reg", a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.URI = uri
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize performs the initial step in an authorization flow.
|
||||||
|
// The caller will then need to choose from and perform a set of returned
|
||||||
|
// challenges using c.Accept in order to successfully complete authorization.
|
||||||
|
//
|
||||||
|
// If an authorization has been previously granted, the CA may return
|
||||||
|
// a valid authorization (Authorization.Status is StatusValid). If so, the caller
|
||||||
|
// need not fulfill any challenge and can proceed to requesting a certificate.
|
||||||
|
func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
|
||||||
|
return c.authorize(ctx, "dns", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeIP is the same as Authorize but requests IP address authorization.
|
||||||
|
// Clients which successfully obtain such authorization may request to issue
|
||||||
|
// a certificate for IP addresses.
|
||||||
|
//
|
||||||
|
// See the ACME spec extension for more details about IP address identifiers:
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-ip.
|
||||||
|
func (c *Client) AuthorizeIP(ctx context.Context, ipaddr string) (*Authorization, error) {
|
||||||
|
return c.authorize(ctx, "ip", ipaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization, error) {
|
||||||
|
if _, err := c.Discover(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type authzID struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
req := struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Identifier authzID `json:"identifier"`
|
||||||
|
}{
|
||||||
|
Resource: "new-authz",
|
||||||
|
Identifier: authzID{Type: typ, Value: val},
|
||||||
|
}
|
||||||
|
res, err := c.post(ctx, c.Key, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var v wireAuthz
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||||
|
}
|
||||||
|
if v.Status != StatusPending && v.Status != StatusValid {
|
||||||
|
return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
|
||||||
|
}
|
||||||
|
return v.authorization(res.Header.Get("Location")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorization retrieves an authorization identified by the given URL.
|
||||||
|
//
|
||||||
|
// If a caller needs to poll an authorization until its status is final,
|
||||||
|
// see the WaitAuthorization method.
|
||||||
|
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||||
|
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var v wireAuthz
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||||
|
}
|
||||||
|
return v.authorization(url), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAuthorization relinquishes an existing authorization identified
|
||||||
|
// by the given URL.
|
||||||
|
// The url argument is an Authorization.URI value.
|
||||||
|
//
|
||||||
|
// If successful, the caller will be required to obtain a new authorization
|
||||||
|
// using the Authorize method before being able to request a new certificate
|
||||||
|
// for the domain associated with the authorization.
|
||||||
|
//
|
||||||
|
// It does not revoke existing certificates.
|
||||||
|
func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||||
|
req := struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Delete bool `json:"delete"`
|
||||||
|
}{
|
||||||
|
Resource: "authz",
|
||||||
|
Status: "deactivated",
|
||||||
|
Delete: true,
|
||||||
|
}
|
||||||
|
res, err := c.post(ctx, c.Key, url, req, wantStatus(http.StatusOK))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitAuthorization polls an authorization at the given URL
|
||||||
|
// until it is in one of the final states, StatusValid or StatusInvalid,
|
||||||
|
// the ACME CA responded with a 4xx error code, or the context is done.
|
||||||
|
//
|
||||||
|
// It returns a non-nil Authorization only if its Status is StatusValid.
|
||||||
|
// In all other cases WaitAuthorization returns an error.
|
||||||
|
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
|
||||||
|
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||||
|
for {
|
||||||
|
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw wireAuthz
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&raw)
|
||||||
|
res.Body.Close()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
// Skip and retry.
|
||||||
|
case raw.Status == StatusValid:
|
||||||
|
return raw.authorization(url), nil
|
||||||
|
case raw.Status == StatusInvalid:
|
||||||
|
return nil, raw.error(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential backoff is implemented in c.get above.
|
||||||
|
// This is just to prevent continuously hitting the CA
|
||||||
|
// while waiting for a final authorization status.
|
||||||
|
d := retryAfter(res.Header.Get("Retry-After"))
|
||||||
|
if d == 0 {
|
||||||
|
// Given that the fastest challenges TLS-SNI and HTTP-01
|
||||||
|
// require a CA to make at least 1 network round trip
|
||||||
|
// and most likely persist a challenge state,
|
||||||
|
// this default delay seems reasonable.
|
||||||
|
d = time.Second
|
||||||
|
}
|
||||||
|
t := time.NewTimer(d)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Stop()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-t.C:
|
||||||
|
// Retry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChallenge retrieves the current status of an challenge.
|
||||||
|
//
|
||||||
|
// A client typically polls a challenge status using this method.
|
||||||
|
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
|
||||||
|
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
v := wireChallenge{URI: url}
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||||
|
}
|
||||||
|
return v.challenge(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept informs the server that the client accepts one of its challenges
|
||||||
|
// previously obtained with c.Authorize.
|
||||||
|
//
|
||||||
|
// The server will then perform the validation asynchronously.
|
||||||
|
func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
|
||||||
|
auth, err := keyAuth(c.Key.Public(), chal.Token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Auth string `json:"keyAuthorization"`
|
||||||
|
}{
|
||||||
|
Resource: "challenge",
|
||||||
|
Type: chal.Type,
|
||||||
|
Auth: auth,
|
||||||
|
}
|
||||||
|
res, err := c.post(ctx, c.Key, chal.URI, req, wantStatus(
|
||||||
|
http.StatusOK, // according to the spec
|
||||||
|
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var v wireChallenge
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||||
|
}
|
||||||
|
return v.challenge(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
|
||||||
|
// A TXT record containing the returned value must be provisioned under
|
||||||
|
// "_acme-challenge" name of the domain being validated.
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
func (c *Client) DNS01ChallengeRecord(token string) (string, error) {
|
||||||
|
ka, err := keyAuth(c.Key.Public(), token)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b := sha256.Sum256([]byte(ka))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP01ChallengeResponse returns the response for an http-01 challenge.
|
||||||
|
// Servers should respond with the value to HTTP requests at the URL path
|
||||||
|
// provided by HTTP01ChallengePath to validate the challenge and prove control
|
||||||
|
// over a domain name.
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
func (c *Client) HTTP01ChallengeResponse(token string) (string, error) {
|
||||||
|
return keyAuth(c.Key.Public(), token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge
|
||||||
|
// should be provided by the servers.
|
||||||
|
// The response value can be obtained with HTTP01ChallengeResponse.
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
func (c *Client) HTTP01ChallengePath(token string) string {
|
||||||
|
return "/.well-known/acme-challenge/" + token
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||||
|
// Servers can present the certificate to validate the challenge and prove control
|
||||||
|
// over a domain name.
|
||||||
|
//
|
||||||
|
// The implementation is incomplete in that the returned value is a single certificate,
|
||||||
|
// computed only for Z0 of the key authorization. ACME CAs are expected to update
|
||||||
|
// their implementations to use the newer version, TLS-SNI-02.
|
||||||
|
// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
// If a WithKey option is provided, its private part signs the returned cert,
|
||||||
|
// and the public part is used to specify the signee.
|
||||||
|
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||||
|
//
|
||||||
|
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||||
|
// the server name of the TLS ClientHello matches exactly the returned name value.
|
||||||
|
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||||
|
ka, err := keyAuth(c.Key.Public(), token)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, "", err
|
||||||
|
}
|
||||||
|
b := sha256.Sum256([]byte(ka))
|
||||||
|
h := hex.EncodeToString(b[:])
|
||||||
|
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||||
|
cert, err = tlsChallengeCert([]string{name}, opt)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, "", err
|
||||||
|
}
|
||||||
|
return cert, name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||||
|
// Servers can present the certificate to validate the challenge and prove control
|
||||||
|
// over a domain name. For more details on TLS-SNI-02 see
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
// If a WithKey option is provided, its private part signs the returned cert,
|
||||||
|
// and the public part is used to specify the signee.
|
||||||
|
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||||
|
//
|
||||||
|
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||||
|
// the server name in the TLS ClientHello matches exactly the returned name value.
|
||||||
|
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||||
|
b := sha256.Sum256([]byte(token))
|
||||||
|
h := hex.EncodeToString(b[:])
|
||||||
|
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||||
|
|
||||||
|
ka, err := keyAuth(c.Key.Public(), token)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, "", err
|
||||||
|
}
|
||||||
|
b = sha256.Sum256([]byte(ka))
|
||||||
|
h = hex.EncodeToString(b[:])
|
||||||
|
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||||
|
|
||||||
|
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, "", err
|
||||||
|
}
|
||||||
|
return cert, sanA, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||||
|
// Servers can present the certificate to validate the challenge and prove control
|
||||||
|
// over a domain name. For more details on TLS-ALPN-01 see
|
||||||
|
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||||
|
//
|
||||||
|
// The token argument is a Challenge.Token value.
|
||||||
|
// If a WithKey option is provided, its private part signs the returned cert,
|
||||||
|
// and the public part is used to specify the signee.
|
||||||
|
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||||
|
//
|
||||||
|
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||||
|
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||||
|
// has been specified.
|
||||||
|
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||||
|
ka, err := keyAuth(c.Key.Public(), token)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
shasum := sha256.Sum256([]byte(ka))
|
||||||
|
extValue, err := asn1.Marshal(shasum[:])
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
acmeExtension := pkix.Extension{
|
||||||
|
Id: idPeACMEIdentifierV1,
|
||||||
|
Critical: true,
|
||||||
|
Value: extValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := defaultTLSChallengeCertTemplate()
|
||||||
|
|
||||||
|
var newOpt []CertOption
|
||||||
|
for _, o := range opt {
|
||||||
|
switch o := o.(type) {
|
||||||
|
case *certOptTemplate:
|
||||||
|
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||||
|
tmpl = &t
|
||||||
|
default:
|
||||||
|
newOpt = append(newOpt, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||||
|
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||||
|
return tlsChallengeCert([]string{domain}, newOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doReg sends all types of registration requests.
|
||||||
|
// The type of request is identified by typ argument, which is a "resource"
|
||||||
|
// in the ACME spec terms.
|
||||||
|
//
|
||||||
|
// A non-nil acct argument indicates whether the intention is to mutate data
|
||||||
|
// of the Account. Only Contact and Agreement of its fields are used
|
||||||
|
// in such cases.
|
||||||
|
func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) {
|
||||||
|
req := struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Contact []string `json:"contact,omitempty"`
|
||||||
|
Agreement string `json:"agreement,omitempty"`
|
||||||
|
}{
|
||||||
|
Resource: typ,
|
||||||
|
}
|
||||||
|
if acct != nil {
|
||||||
|
req.Contact = acct.Contact
|
||||||
|
req.Agreement = acct.AgreedTerms
|
||||||
|
}
|
||||||
|
res, err := c.post(ctx, c.Key, url, req, wantStatus(
|
||||||
|
http.StatusOK, // updates and deletes
|
||||||
|
http.StatusCreated, // new account creation
|
||||||
|
http.StatusAccepted, // Let's Encrypt divergent implementation
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
Contact []string
|
||||||
|
Agreement string
|
||||||
|
Authorizations string
|
||||||
|
Certificates string
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||||
|
}
|
||||||
|
var tos string
|
||||||
|
if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 {
|
||||||
|
tos = v[0]
|
||||||
|
}
|
||||||
|
var authz string
|
||||||
|
if v := linkHeader(res.Header, "next"); len(v) > 0 {
|
||||||
|
authz = v[0]
|
||||||
|
}
|
||||||
|
return &Account{
|
||||||
|
URI: res.Header.Get("Location"),
|
||||||
|
Contact: v.Contact,
|
||||||
|
AgreedTerms: v.Agreement,
|
||||||
|
CurrentTerms: tos,
|
||||||
|
Authz: authz,
|
||||||
|
Authorizations: v.Authorizations,
|
||||||
|
Certificates: v.Certificates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// popNonce returns a nonce value previously stored with c.addNonce
|
||||||
|
// or fetches a fresh one from a URL by issuing a HEAD request.
|
||||||
|
// It first tries c.directoryURL() and then the provided url if the former fails.
|
||||||
|
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||||
|
c.noncesMu.Lock()
|
||||||
|
defer c.noncesMu.Unlock()
|
||||||
|
if len(c.nonces) == 0 {
|
||||||
|
dirURL := c.directoryURL()
|
||||||
|
v, err := c.fetchNonce(ctx, dirURL)
|
||||||
|
if err != nil && url != dirURL {
|
||||||
|
v, err = c.fetchNonce(ctx, url)
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
var nonce string
|
||||||
|
for nonce = range c.nonces {
|
||||||
|
delete(c.nonces, nonce)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nonce, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearNonces clears any stored nonces
|
||||||
|
func (c *Client) clearNonces() {
|
||||||
|
c.noncesMu.Lock()
|
||||||
|
defer c.noncesMu.Unlock()
|
||||||
|
c.nonces = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNonce stores a nonce value found in h (if any) for future use.
|
||||||
|
func (c *Client) addNonce(h http.Header) {
|
||||||
|
v := nonceFromHeader(h)
|
||||||
|
if v == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.noncesMu.Lock()
|
||||||
|
defer c.noncesMu.Unlock()
|
||||||
|
if len(c.nonces) >= maxNonces {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.nonces == nil {
|
||||||
|
c.nonces = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
c.nonces[v] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||||
|
r, err := http.NewRequest("HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resp, err := c.doNoRetry(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
nonce := nonceFromHeader(resp.Header)
|
||||||
|
if nonce == "" {
|
||||||
|
if resp.StatusCode > 299 {
|
||||||
|
return "", responseError(resp)
|
||||||
|
}
|
||||||
|
return "", errors.New("acme: nonce not found")
|
||||||
|
}
|
||||||
|
return nonce, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonceFromHeader(h http.Header) string {
|
||||||
|
return h.Get("Replay-Nonce")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) {
|
||||||
|
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: response stream: %v", err)
|
||||||
|
}
|
||||||
|
if len(b) > maxCertSize {
|
||||||
|
return nil, errors.New("acme: certificate is too big")
|
||||||
|
}
|
||||||
|
cert := [][]byte{b}
|
||||||
|
if !bundle {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append CA chain cert(s).
|
||||||
|
// At least one is required according to the spec:
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1
|
||||||
|
up := linkHeader(res.Header, "up")
|
||||||
|
if len(up) == 0 {
|
||||||
|
return nil, errors.New("acme: rel=up link not found")
|
||||||
|
}
|
||||||
|
if len(up) > maxChainLen {
|
||||||
|
return nil, errors.New("acme: rel=up link is too large")
|
||||||
|
}
|
||||||
|
for _, url := range up {
|
||||||
|
cc, err := c.chainCert(ctx, url, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert = append(cert, cc...)
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// chainCert fetches CA certificate chain recursively by following "up" links.
|
||||||
|
// Each recursive call increments the depth by 1, resulting in an error
|
||||||
|
// if the recursion level reaches maxChainLen.
|
||||||
|
//
|
||||||
|
// First chainCert call starts with depth of 0.
|
||||||
|
func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) {
|
||||||
|
if depth >= maxChainLen {
|
||||||
|
return nil, errors.New("acme: certificate chain is too deep")
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b) > maxCertSize {
|
||||||
|
return nil, errors.New("acme: certificate is too big")
|
||||||
|
}
|
||||||
|
chain := [][]byte{b}
|
||||||
|
|
||||||
|
uplink := linkHeader(res.Header, "up")
|
||||||
|
if len(uplink) > maxChainLen {
|
||||||
|
return nil, errors.New("acme: certificate chain is too large")
|
||||||
|
}
|
||||||
|
for _, up := range uplink {
|
||||||
|
cc, err := c.chainCert(ctx, up, depth+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
chain = append(chain, cc...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// linkHeader returns URI-Reference values of all Link headers
|
||||||
|
// with relation-type rel.
|
||||||
|
// See https://tools.ietf.org/html/rfc5988#section-5 for details.
|
||||||
|
func linkHeader(h http.Header, rel string) []string {
|
||||||
|
var links []string
|
||||||
|
for _, v := range h["Link"] {
|
||||||
|
parts := strings.Split(v, ";")
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if !strings.HasPrefix(p, "rel=") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v := strings.Trim(p[4:], `"`); v == rel {
|
||||||
|
links = append(links, strings.Trim(parts[0], "<>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyAuth generates a key authorization string for a given token.
|
||||||
|
func keyAuth(pub crypto.PublicKey, token string) (string, error) {
|
||||||
|
th, err := JWKThumbprint(pub)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", token, th), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
|
||||||
|
func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||||
|
return &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(24 * time.Hour),
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||||
|
// with the given SANs and auto-generated public/private key pair.
|
||||||
|
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||||
|
// To create a cert with a custom key pair, specify WithKey option.
|
||||||
|
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||||
|
var key crypto.Signer
|
||||||
|
tmpl := defaultTLSChallengeCertTemplate()
|
||||||
|
for _, o := range opt {
|
||||||
|
switch o := o.(type) {
|
||||||
|
case *certOptKey:
|
||||||
|
if key != nil {
|
||||||
|
return tls.Certificate{}, errors.New("acme: duplicate key option")
|
||||||
|
}
|
||||||
|
key = o.key
|
||||||
|
case *certOptTemplate:
|
||||||
|
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||||
|
tmpl = &t
|
||||||
|
default:
|
||||||
|
// package's fault, if we let this happen:
|
||||||
|
panic(fmt.Sprintf("unsupported option type %T", o))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
var err error
|
||||||
|
if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpl.DNSNames = san
|
||||||
|
if len(san) > 0 {
|
||||||
|
tmpl.Subject.CommonName = san[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
return tls.Certificate{
|
||||||
|
Certificate: [][]byte{der},
|
||||||
|
PrivateKey: key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodePEM returns b encoded as PEM with block of type typ.
|
||||||
|
func encodePEM(typ string, b []byte) []byte {
|
||||||
|
pb := &pem.Block{Type: typ, Bytes: b}
|
||||||
|
return pem.EncodeToMemory(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeNow is useful for testing for fixed current time.
|
||||||
|
var timeNow = time.Now
|
1137
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
Normal file
1137
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
136
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
Normal file
136
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrCacheMiss is returned when a certificate is not found in cache.
|
||||||
|
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
||||||
|
|
||||||
|
// Cache is used by Manager to store and retrieve previously obtained certificates
|
||||||
|
// and other account data as opaque blobs.
|
||||||
|
//
|
||||||
|
// Cache implementations should not rely on the key naming pattern. Keys can
|
||||||
|
// include any printable ASCII characters, except the following: \/:*?"<>|
|
||||||
|
type Cache interface {
|
||||||
|
// Get returns a certificate data for the specified key.
|
||||||
|
// If there's no such key, Get returns ErrCacheMiss.
|
||||||
|
Get(ctx context.Context, key string) ([]byte, error)
|
||||||
|
|
||||||
|
// Put stores the data in the cache under the specified key.
|
||||||
|
// Underlying implementations may use any data storage format,
|
||||||
|
// as long as the reverse operation, Get, results in the original data.
|
||||||
|
Put(ctx context.Context, key string, data []byte) error
|
||||||
|
|
||||||
|
// Delete removes a certificate data from the cache under the specified key.
|
||||||
|
// If there's no such key in the cache, Delete returns nil.
|
||||||
|
Delete(ctx context.Context, key string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirCache implements Cache using a directory on the local filesystem.
|
||||||
|
// If the directory does not exist, it will be created with 0700 permissions.
|
||||||
|
type DirCache string
|
||||||
|
|
||||||
|
// Get reads a certificate data from the specified file name.
|
||||||
|
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
||||||
|
name = filepath.Join(string(d), name)
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
done = make(chan struct{})
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
data, err = ioutil.ReadFile(name)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrCacheMiss
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put writes the certificate data to the specified file name.
|
||||||
|
// The file will be created with 0600 permissions.
|
||||||
|
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||||
|
if err := os.MkdirAll(string(d), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
var tmp string
|
||||||
|
if tmp, err = d.writeTempFile(name, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tmp)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Don't overwrite the file if the context was canceled.
|
||||||
|
default:
|
||||||
|
newName := filepath.Join(string(d), name)
|
||||||
|
err = os.Rename(tmp, newName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the specified file name.
|
||||||
|
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||||
|
name = filepath.Join(string(d), name)
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
done = make(chan struct{})
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
err = os.Remove(name)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
||||||
|
func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
|
||||||
|
// TempFile uses 0600 permissions
|
||||||
|
f, err := ioutil.TempFile(string(d), prefix)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if reterr != nil {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, err := f.Write(b); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return f.Name(), f.Close()
|
||||||
|
}
|
157
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
157
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewListener returns a net.Listener that listens on the standard TLS
|
||||||
|
// port (443) on all interfaces and returns *tls.Conn connections with
|
||||||
|
// LetsEncrypt certificates for the provided domain or domains.
|
||||||
|
//
|
||||||
|
// It enables one-line HTTPS servers:
|
||||||
|
//
|
||||||
|
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||||
|
//
|
||||||
|
// NewListener is a convenience function for a common configuration.
|
||||||
|
// More complex or custom configurations can use the autocert.Manager
|
||||||
|
// type instead.
|
||||||
|
//
|
||||||
|
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
||||||
|
// Service. If domains is not empty, the provided domains are passed
|
||||||
|
// to HostWhitelist. If domains is empty, the listener will do
|
||||||
|
// LetsEncrypt challenges for any requested domain, which is not
|
||||||
|
// recommended.
|
||||||
|
//
|
||||||
|
// Certificates are cached in a "golang-autocert" directory under an
|
||||||
|
// operating system-specific cache or temp directory. This may not
|
||||||
|
// be suitable for servers spanning multiple machines.
|
||||||
|
//
|
||||||
|
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||||
|
// should only be used with servers that support HTTP/2.
|
||||||
|
//
|
||||||
|
// The returned Listener also enables TCP keep-alives on the accepted
|
||||||
|
// connections. The returned *tls.Conn are returned before their TLS
|
||||||
|
// handshake has completed.
|
||||||
|
func NewListener(domains ...string) net.Listener {
|
||||||
|
m := &Manager{
|
||||||
|
Prompt: AcceptTOS,
|
||||||
|
}
|
||||||
|
if len(domains) > 0 {
|
||||||
|
m.HostPolicy = HostWhitelist(domains...)
|
||||||
|
}
|
||||||
|
dir := cacheDir()
|
||||||
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||||
|
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
||||||
|
} else {
|
||||||
|
m.Cache = DirCache(dir)
|
||||||
|
}
|
||||||
|
return m.Listener()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener listens on the standard TLS port (443) on all interfaces
|
||||||
|
// and returns a net.Listener returning *tls.Conn connections.
|
||||||
|
//
|
||||||
|
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||||
|
// should only be used with servers that support HTTP/2.
|
||||||
|
//
|
||||||
|
// The returned Listener also enables TCP keep-alives on the accepted
|
||||||
|
// connections. The returned *tls.Conn are returned before their TLS
|
||||||
|
// handshake has completed.
|
||||||
|
//
|
||||||
|
// Unlike NewListener, it is the caller's responsibility to initialize
|
||||||
|
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||||
|
func (m *Manager) Listener() net.Listener {
|
||||||
|
ln := &listener{
|
||||||
|
m: m,
|
||||||
|
conf: m.TLSConfig(),
|
||||||
|
}
|
||||||
|
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
|
||||||
|
type listener struct {
|
||||||
|
m *Manager
|
||||||
|
conf *tls.Config
|
||||||
|
|
||||||
|
tcpListener net.Listener
|
||||||
|
tcpListenErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Accept() (net.Conn, error) {
|
||||||
|
if ln.tcpListenErr != nil {
|
||||||
|
return nil, ln.tcpListenErr
|
||||||
|
}
|
||||||
|
conn, err := ln.tcpListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tcpConn := conn.(*net.TCPConn)
|
||||||
|
|
||||||
|
// Because Listener is a convenience function, help out with
|
||||||
|
// this too. This is not possible for the caller to set once
|
||||||
|
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
||||||
|
// If callers don't want this, they can do things the manual
|
||||||
|
// way and tweak as needed. But this is what net/http does
|
||||||
|
// itself, so copy that. If net/http changes, we can change
|
||||||
|
// here too.
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
|
||||||
|
return tls.Server(tcpConn, ln.conf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Addr() net.Addr {
|
||||||
|
if ln.tcpListener != nil {
|
||||||
|
return ln.tcpListener.Addr()
|
||||||
|
}
|
||||||
|
// net.Listen failed. Return something non-nil in case callers
|
||||||
|
// call Addr before Accept:
|
||||||
|
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *listener) Close() error {
|
||||||
|
if ln.tcpListenErr != nil {
|
||||||
|
return ln.tcpListenErr
|
||||||
|
}
|
||||||
|
return ln.tcpListener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func homeDir() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||||
|
}
|
||||||
|
if h := os.Getenv("HOME"); h != "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheDir() string {
|
||||||
|
const base = "golang-autocert"
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||||
|
case "windows":
|
||||||
|
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||||
|
if v := os.Getenv(ev); v != "" {
|
||||||
|
return filepath.Join(v, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Worst case:
|
||||||
|
return filepath.Join(homeDir(), base)
|
||||||
|
}
|
||||||
|
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||||
|
return filepath.Join(xdg, base)
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir(), ".cache", base)
|
||||||
|
}
|
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
Normal file
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package autocert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
||||||
|
const renewJitter = time.Hour
|
||||||
|
|
||||||
|
// domainRenewal tracks the state used by the periodic timers
|
||||||
|
// renewing a single domain's cert.
|
||||||
|
type domainRenewal struct {
|
||||||
|
m *Manager
|
||||||
|
ck certKey
|
||||||
|
key crypto.Signer
|
||||||
|
|
||||||
|
timerMu sync.Mutex
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// start starts a cert renewal timer at the time
|
||||||
|
// defined by the certificate expiration time exp.
|
||||||
|
//
|
||||||
|
// If the timer is already started, calling start is a noop.
|
||||||
|
func (dr *domainRenewal) start(exp time.Time) {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops the cert renewal timer.
|
||||||
|
// If the timer is already stopped, calling stop is a noop.
|
||||||
|
func (dr *domainRenewal) stop() {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dr.timer.Stop()
|
||||||
|
dr.timer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// renew is called periodically by a timer.
|
||||||
|
// The first renew call is kicked off by dr.start.
|
||||||
|
func (dr *domainRenewal) renew() {
|
||||||
|
dr.timerMu.Lock()
|
||||||
|
defer dr.timerMu.Unlock()
|
||||||
|
if dr.timer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
// TODO: rotate dr.key at some point?
|
||||||
|
next, err := dr.do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
next = renewJitter / 2
|
||||||
|
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||||
|
}
|
||||||
|
dr.timer = time.AfterFunc(next, dr.renew)
|
||||||
|
testDidRenewLoop(next, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateState locks and replaces the relevant Manager.state item with the given
|
||||||
|
// state. It additionally updates dr.key with the given state's key.
|
||||||
|
func (dr *domainRenewal) updateState(state *certState) {
|
||||||
|
dr.m.stateMu.Lock()
|
||||||
|
defer dr.m.stateMu.Unlock()
|
||||||
|
dr.key = state.key
|
||||||
|
dr.m.state[dr.ck] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
||||||
|
// Instead, it requests a new certificate independently and, upon success,
|
||||||
|
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
||||||
|
//
|
||||||
|
// It may lock and update the Manager.state if the expiration date of the currently
|
||||||
|
// cached cert is far enough in the future.
|
||||||
|
//
|
||||||
|
// The returned value is a time interval after which the renewal should occur again.
|
||||||
|
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||||
|
// a race is likely unavoidable in a distributed environment
|
||||||
|
// but we try nonetheless
|
||||||
|
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||||
|
next := dr.next(tlscert.Leaf.NotAfter)
|
||||||
|
if next > dr.m.renewBefore()+renewJitter {
|
||||||
|
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||||
|
if ok {
|
||||||
|
state := &certState{
|
||||||
|
key: signer,
|
||||||
|
cert: tlscert.Certificate,
|
||||||
|
leaf: tlscert.Leaf,
|
||||||
|
}
|
||||||
|
dr.updateState(state)
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
state := &certState{
|
||||||
|
key: dr.key,
|
||||||
|
cert: der,
|
||||||
|
leaf: leaf,
|
||||||
|
}
|
||||||
|
tlscert, err := state.tlscert()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dr.updateState(state)
|
||||||
|
return dr.next(leaf.NotAfter), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
||||||
|
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
|
||||||
|
// add a bit of randomness to renew deadline
|
||||||
|
n := pseudoRand.int63n(int64(renewJitter))
|
||||||
|
d -= time.Duration(n)
|
||||||
|
if d < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
var testDidRenewLoop = func(next time.Duration, err error) {}
|
299
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
Normal file
299
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// retryTimer encapsulates common logic for retrying unsuccessful requests.
|
||||||
|
// It is not safe for concurrent use.
|
||||||
|
type retryTimer struct {
|
||||||
|
// backoffFn provides backoff delay sequence for retries.
|
||||||
|
// See Client.RetryBackoff doc comment.
|
||||||
|
backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
|
||||||
|
// n is the current retry attempt.
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *retryTimer) inc() {
|
||||||
|
t.n++
|
||||||
|
}
|
||||||
|
|
||||||
|
// backoff pauses the current goroutine as described in Client.RetryBackoff.
|
||||||
|
func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
|
||||||
|
d := t.backoffFn(t.n, r, res)
|
||||||
|
if d <= 0 {
|
||||||
|
return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
|
||||||
|
}
|
||||||
|
wakeup := time.NewTimer(d)
|
||||||
|
defer wakeup.Stop()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-wakeup.C:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) retryTimer() *retryTimer {
|
||||||
|
f := c.RetryBackoff
|
||||||
|
if f == nil {
|
||||||
|
f = defaultBackoff
|
||||||
|
}
|
||||||
|
return &retryTimer{backoffFn: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultBackoff provides default Client.RetryBackoff implementation
|
||||||
|
// using a truncated exponential backoff algorithm,
|
||||||
|
// as described in Client.RetryBackoff.
|
||||||
|
//
|
||||||
|
// The n argument is always bounded between 1 and 30.
|
||||||
|
// The returned value is always greater than 0.
|
||||||
|
func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||||
|
const max = 10 * time.Second
|
||||||
|
var jitter time.Duration
|
||||||
|
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
|
||||||
|
// Set the minimum to 1ms to avoid a case where
|
||||||
|
// an invalid Retry-After value is parsed into 0 below,
|
||||||
|
// resulting in the 0 returned value which would unintentionally
|
||||||
|
// stop the retries.
|
||||||
|
jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
|
||||||
|
}
|
||||||
|
if v, ok := res.Header["Retry-After"]; ok {
|
||||||
|
return retryAfter(v[0]) + jitter
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
if n > 30 {
|
||||||
|
n = 30
|
||||||
|
}
|
||||||
|
d := time.Duration(1<<uint(n-1))*time.Second + jitter
|
||||||
|
if d > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryAfter parses a Retry-After HTTP header value,
|
||||||
|
// trying to convert v into an int (seconds) or use http.ParseTime otherwise.
|
||||||
|
// It returns zero value if v cannot be parsed.
|
||||||
|
func retryAfter(v string) time.Duration {
|
||||||
|
if i, err := strconv.Atoi(v); err == nil {
|
||||||
|
return time.Duration(i) * time.Second
|
||||||
|
}
|
||||||
|
t, err := http.ParseTime(v)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return t.Sub(timeNow())
|
||||||
|
}
|
||||||
|
|
||||||
|
// resOkay is a function that reports whether the provided response is okay.
|
||||||
|
// It is expected to keep the response body unread.
|
||||||
|
type resOkay func(*http.Response) bool
|
||||||
|
|
||||||
|
// wantStatus returns a function which reports whether the code
|
||||||
|
// matches the status code of a response.
|
||||||
|
func wantStatus(codes ...int) resOkay {
|
||||||
|
return func(res *http.Response) bool {
|
||||||
|
for _, code := range codes {
|
||||||
|
if code == res.StatusCode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get issues an unsigned GET request to the specified URL.
|
||||||
|
// It returns a non-error value only when ok reports true.
|
||||||
|
//
|
||||||
|
// get retries unsuccessful attempts according to c.RetryBackoff
|
||||||
|
// until the context is done or a non-retriable error is received.
|
||||||
|
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||||
|
retry := c.retryTimer()
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := c.doNoRetry(ctx, req)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
case ok(res):
|
||||||
|
return res, nil
|
||||||
|
case isRetriable(res.StatusCode):
|
||||||
|
retry.inc()
|
||||||
|
resErr := responseError(res)
|
||||||
|
res.Body.Close()
|
||||||
|
// Ignore the error value from retry.backoff
|
||||||
|
// and return the one from last retry, as received from the CA.
|
||||||
|
if retry.backoff(ctx, req, res) != nil {
|
||||||
|
return nil, resErr
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
defer res.Body.Close()
|
||||||
|
return nil, responseError(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// post issues a signed POST request in JWS format using the provided key
|
||||||
|
// to the specified URL.
|
||||||
|
// It returns a non-error value only when ok reports true.
|
||||||
|
//
|
||||||
|
// post retries unsuccessful attempts according to c.RetryBackoff
|
||||||
|
// until the context is done or a non-retriable error is received.
|
||||||
|
// It uses postNoRetry to make individual requests.
|
||||||
|
func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
|
||||||
|
retry := c.retryTimer()
|
||||||
|
for {
|
||||||
|
res, req, err := c.postNoRetry(ctx, key, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok(res) {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
resErr := responseError(res)
|
||||||
|
res.Body.Close()
|
||||||
|
switch {
|
||||||
|
// Check for bad nonce before isRetriable because it may have been returned
|
||||||
|
// with an unretriable response code such as 400 Bad Request.
|
||||||
|
case isBadNonce(resErr):
|
||||||
|
// Consider any previously stored nonce values to be invalid.
|
||||||
|
c.clearNonces()
|
||||||
|
case !isRetriable(res.StatusCode):
|
||||||
|
return nil, resErr
|
||||||
|
}
|
||||||
|
retry.inc()
|
||||||
|
// Ignore the error value from retry.backoff
|
||||||
|
// and return the one from last retry, as received from the CA.
|
||||||
|
if err := retry.backoff(ctx, req, res); err != nil {
|
||||||
|
return nil, resErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// postNoRetry signs the body with the given key and POSTs it to the provided url.
|
||||||
|
// The body argument must be JSON-serializable.
|
||||||
|
// It is used by c.post to retry unsuccessful attempts.
|
||||||
|
func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
|
||||||
|
nonce, err := c.popNonce(ctx, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
b, err := jwsEncodeJSON(body, key, nonce)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/jose+json")
|
||||||
|
res, err := c.doNoRetry(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
c.addNonce(res.Header)
|
||||||
|
return res, req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doNoRetry issues a request req, replacing its context (if any) with ctx.
|
||||||
|
func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||||
|
req.Header.Set("User-Agent", c.userAgent())
|
||||||
|
res, err := c.httpClient().Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Prefer the unadorned context error.
|
||||||
|
// (The acme package had tests assuming this, previously from ctxhttp's
|
||||||
|
// behavior, predating net/http supporting contexts natively)
|
||||||
|
// TODO(bradfitz): reconsider this in the future. But for now this
|
||||||
|
// requires no test updates.
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) httpClient() *http.Client {
|
||||||
|
if c.HTTPClient != nil {
|
||||||
|
return c.HTTPClient
|
||||||
|
}
|
||||||
|
return http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageVersion is the version of the module that contains this package, for
|
||||||
|
// sending as part of the User-Agent header. It's set in version_go112.go.
|
||||||
|
var packageVersion string
|
||||||
|
|
||||||
|
// userAgent returns the User-Agent header value. It includes the package name,
|
||||||
|
// the module version (if available), and the c.UserAgent value (if set).
|
||||||
|
func (c *Client) userAgent() string {
|
||||||
|
ua := "golang.org/x/crypto/acme"
|
||||||
|
if packageVersion != "" {
|
||||||
|
ua += "@" + packageVersion
|
||||||
|
}
|
||||||
|
if c.UserAgent != "" {
|
||||||
|
ua = c.UserAgent + " " + ua
|
||||||
|
}
|
||||||
|
return ua
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBadNonce reports whether err is an ACME "badnonce" error.
|
||||||
|
func isBadNonce(err error) bool {
|
||||||
|
// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
|
||||||
|
// However, ACME servers in the wild return their versions of the error.
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
|
||||||
|
// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
|
||||||
|
ae, ok := err.(*Error)
|
||||||
|
return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRetriable reports whether a request can be retried
|
||||||
|
// based on the response status code.
|
||||||
|
//
|
||||||
|
// Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
|
||||||
|
// Callers should parse the response and check with isBadNonce.
|
||||||
|
func isRetriable(code int) bool {
|
||||||
|
return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseError creates an error of Error type from resp.
|
||||||
|
func responseError(resp *http.Response) error {
|
||||||
|
// don't care if ReadAll returns an error:
|
||||||
|
// json.Unmarshal will fail in that case anyway
|
||||||
|
b, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
e := &wireError{Status: resp.StatusCode}
|
||||||
|
if err := json.Unmarshal(b, e); err != nil {
|
||||||
|
// this is not a regular error response:
|
||||||
|
// populate detail with anything we received,
|
||||||
|
// e.Status will already contain HTTP response code value
|
||||||
|
e.Detail = string(b)
|
||||||
|
if e.Detail == "" {
|
||||||
|
e.Detail = resp.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.error(resp.Header)
|
||||||
|
}
|
156
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
Normal file
156
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
_ "crypto/sha512" // need for EC keys
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||||
|
// The result is serialized in JSON format.
|
||||||
|
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||||
|
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) {
|
||||||
|
jwk, err := jwkEncode(key.Public())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alg, sha := jwsHasher(key.Public())
|
||||||
|
if alg == "" || !sha.Available() {
|
||||||
|
return nil, ErrUnsupportedKey
|
||||||
|
}
|
||||||
|
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
||||||
|
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||||
|
cs, err := json.Marshal(claimset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payload := base64.RawURLEncoding.EncodeToString(cs)
|
||||||
|
hash := sha.New()
|
||||||
|
hash.Write([]byte(phead + "." + payload))
|
||||||
|
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := struct {
|
||||||
|
Protected string `json:"protected"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Sig string `json:"signature"`
|
||||||
|
}{
|
||||||
|
Protected: phead,
|
||||||
|
Payload: payload,
|
||||||
|
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||||
|
}
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||||
|
// The result is also suitable for creating a JWK thumbprint.
|
||||||
|
// https://tools.ietf.org/html/rfc7517
|
||||||
|
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||||
|
n := pub.N
|
||||||
|
e := big.NewInt(int64(pub.E))
|
||||||
|
// Field order is important.
|
||||||
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||||
|
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||||
|
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||||
|
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||||
|
), nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||||
|
p := pub.Curve.Params()
|
||||||
|
n := p.BitSize / 8
|
||||||
|
if p.BitSize%8 != 0 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
x := pub.X.Bytes()
|
||||||
|
if n > len(x) {
|
||||||
|
x = append(make([]byte, n-len(x)), x...)
|
||||||
|
}
|
||||||
|
y := pub.Y.Bytes()
|
||||||
|
if n > len(y) {
|
||||||
|
y = append(make([]byte, n-len(y)), y...)
|
||||||
|
}
|
||||||
|
// Field order is important.
|
||||||
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||||
|
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||||
|
p.Name,
|
||||||
|
base64.RawURLEncoding.EncodeToString(x),
|
||||||
|
base64.RawURLEncoding.EncodeToString(y),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
return "", ErrUnsupportedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwsSign signs the digest using the given key.
|
||||||
|
// The hash is unused for ECDSA keys.
|
||||||
|
//
|
||||||
|
// Note: non-stdlib crypto.Signer implementations are expected to return
|
||||||
|
// the signature in the format as specified in RFC7518.
|
||||||
|
// See https://tools.ietf.org/html/rfc7518 for more details.
|
||||||
|
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||||
|
if key, ok := key.(*ecdsa.PrivateKey); ok {
|
||||||
|
// The key.Sign method of ecdsa returns ASN1-encoded signature.
|
||||||
|
// So, we use the package Sign function instead
|
||||||
|
// to get R and S values directly and format the result accordingly.
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rb, sb := r.Bytes(), s.Bytes()
|
||||||
|
size := key.Params().BitSize / 8
|
||||||
|
if size%8 > 0 {
|
||||||
|
size++
|
||||||
|
}
|
||||||
|
sig := make([]byte, size*2)
|
||||||
|
copy(sig[size-len(rb):], rb)
|
||||||
|
copy(sig[size*2-len(sb):], sb)
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
return key.Sign(rand.Reader, digest, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||||
|
// to use for signing a digest with the provided key.
|
||||||
|
// It returns ("", 0) if the key is not supported.
|
||||||
|
func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return "RS256", crypto.SHA256
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
switch pub.Params().Name {
|
||||||
|
case "P-256":
|
||||||
|
return "ES256", crypto.SHA256
|
||||||
|
case "P-384":
|
||||||
|
return "ES384", crypto.SHA384
|
||||||
|
case "P-521":
|
||||||
|
return "ES512", crypto.SHA512
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||||
|
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||||
|
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
||||||
|
jwk, err := jwkEncode(pub)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b := sha256.Sum256([]byte(jwk))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||||
|
}
|
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
Normal file
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||||
|
const (
|
||||||
|
StatusUnknown = "unknown"
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusProcessing = "processing"
|
||||||
|
StatusValid = "valid"
|
||||||
|
StatusInvalid = "invalid"
|
||||||
|
StatusRevoked = "revoked"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CRLReasonCode identifies the reason for a certificate revocation.
|
||||||
|
type CRLReasonCode int
|
||||||
|
|
||||||
|
// CRL reason codes as defined in RFC 5280.
|
||||||
|
const (
|
||||||
|
CRLReasonUnspecified CRLReasonCode = 0
|
||||||
|
CRLReasonKeyCompromise CRLReasonCode = 1
|
||||||
|
CRLReasonCACompromise CRLReasonCode = 2
|
||||||
|
CRLReasonAffiliationChanged CRLReasonCode = 3
|
||||||
|
CRLReasonSuperseded CRLReasonCode = 4
|
||||||
|
CRLReasonCessationOfOperation CRLReasonCode = 5
|
||||||
|
CRLReasonCertificateHold CRLReasonCode = 6
|
||||||
|
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
||||||
|
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
||||||
|
CRLReasonAACompromise CRLReasonCode = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||||
|
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||||
|
|
||||||
|
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||||
|
type Error struct {
|
||||||
|
// StatusCode is The HTTP status code generated by the origin server.
|
||||||
|
StatusCode int
|
||||||
|
// ProblemType is a URI reference that identifies the problem type,
|
||||||
|
// typically in a "urn:acme:error:xxx" form.
|
||||||
|
ProblemType string
|
||||||
|
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||||
|
Detail string
|
||||||
|
// Header is the original server error response headers.
|
||||||
|
// It may be nil.
|
||||||
|
Header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizationError indicates that an authorization for an identifier
|
||||||
|
// did not succeed.
|
||||||
|
// It contains all errors from Challenge items of the failed Authorization.
|
||||||
|
type AuthorizationError struct {
|
||||||
|
// URI uniquely identifies the failed Authorization.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Identifier is an AuthzID.Value of the failed Authorization.
|
||||||
|
Identifier string
|
||||||
|
|
||||||
|
// Errors is a collection of non-nil error values of Challenge items
|
||||||
|
// of the failed Authorization.
|
||||||
|
Errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthorizationError) Error() string {
|
||||||
|
e := make([]string, len(a.Errors))
|
||||||
|
for i, err := range a.Errors {
|
||||||
|
e[i] = err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit reports whether err represents a rate limit error and
|
||||||
|
// any Retry-After duration returned by the server.
|
||||||
|
//
|
||||||
|
// See the following for more details on rate limiting:
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
||||||
|
func RateLimit(err error) (time.Duration, bool) {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
// Some CA implementations may return incorrect values.
|
||||||
|
// Use case-insensitive comparison.
|
||||||
|
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if e.Header == nil {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
return retryAfter(e.Header.Get("Retry-After")), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account is a user account. It is associated with a private key.
|
||||||
|
type Account struct {
|
||||||
|
// URI is the account unique ID, which is also a URL used to retrieve
|
||||||
|
// account data from the CA.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Contact is a slice of contact info used during registration.
|
||||||
|
Contact []string
|
||||||
|
|
||||||
|
// The terms user has agreed to.
|
||||||
|
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
||||||
|
// to the actual Terms of Service of the CA.
|
||||||
|
AgreedTerms string
|
||||||
|
|
||||||
|
// Actual terms of a CA.
|
||||||
|
CurrentTerms string
|
||||||
|
|
||||||
|
// Authz is the authorization URL used to initiate a new authz flow.
|
||||||
|
Authz string
|
||||||
|
|
||||||
|
// Authorizations is a URI from which a list of authorizations
|
||||||
|
// granted to this account can be fetched via a GET request.
|
||||||
|
Authorizations string
|
||||||
|
|
||||||
|
// Certificates is a URI from which a list of certificates
|
||||||
|
// issued for this account can be fetched via a GET request.
|
||||||
|
Certificates string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory is ACME server discovery data.
|
||||||
|
type Directory struct {
|
||||||
|
// RegURL is an account endpoint URL, allowing for creating new
|
||||||
|
// and modifying existing accounts.
|
||||||
|
RegURL string
|
||||||
|
|
||||||
|
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||||
|
AuthzURL string
|
||||||
|
|
||||||
|
// CertURL is a new certificate issuance endpoint URL.
|
||||||
|
CertURL string
|
||||||
|
|
||||||
|
// RevokeURL is used to initiate a certificate revocation flow.
|
||||||
|
RevokeURL string
|
||||||
|
|
||||||
|
// Term is a URI identifying the current terms of service.
|
||||||
|
Terms string
|
||||||
|
|
||||||
|
// Website is an HTTP or HTTPS URL locating a website
|
||||||
|
// providing more information about the ACME server.
|
||||||
|
Website string
|
||||||
|
|
||||||
|
// CAA consists of lowercase hostname elements, which the ACME server
|
||||||
|
// recognises as referring to itself for the purposes of CAA record validation
|
||||||
|
// as defined in RFC6844.
|
||||||
|
CAA []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Challenge encodes a returned CA challenge.
|
||||||
|
// Its Error field may be non-nil if the challenge is part of an Authorization
|
||||||
|
// with StatusInvalid.
|
||||||
|
type Challenge struct {
|
||||||
|
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// URI is where a challenge response can be posted to.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Token is a random value that uniquely identifies the challenge.
|
||||||
|
Token string
|
||||||
|
|
||||||
|
// Status identifies the status of this challenge.
|
||||||
|
Status string
|
||||||
|
|
||||||
|
// Error indicates the reason for an authorization failure
|
||||||
|
// when this challenge was used.
|
||||||
|
// The type of a non-nil value is *Error.
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization encodes an authorization response.
|
||||||
|
type Authorization struct {
|
||||||
|
// URI uniquely identifies a authorization.
|
||||||
|
URI string
|
||||||
|
|
||||||
|
// Status identifies the status of an authorization.
|
||||||
|
Status string
|
||||||
|
|
||||||
|
// Identifier is what the account is authorized to represent.
|
||||||
|
Identifier AuthzID
|
||||||
|
|
||||||
|
// Challenges that the client needs to fulfill in order to prove possession
|
||||||
|
// of the identifier (for pending authorizations).
|
||||||
|
// For final authorizations, the challenges that were used.
|
||||||
|
Challenges []*Challenge
|
||||||
|
|
||||||
|
// A collection of sets of challenges, each of which would be sufficient
|
||||||
|
// to prove possession of the identifier.
|
||||||
|
// Clients must complete a set of challenges that covers at least one set.
|
||||||
|
// Challenges are identified by their indices in the challenges array.
|
||||||
|
// If this field is empty, the client needs to complete all challenges.
|
||||||
|
Combinations [][]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthzID is an identifier that an account is authorized to represent.
|
||||||
|
type AuthzID struct {
|
||||||
|
Type string // The type of identifier, e.g. "dns".
|
||||||
|
Value string // The identifier itself, e.g. "example.org".
|
||||||
|
}
|
||||||
|
|
||||||
|
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||||
|
type wireAuthz struct {
|
||||||
|
Status string
|
||||||
|
Challenges []wireChallenge
|
||||||
|
Combinations [][]int
|
||||||
|
Identifier struct {
|
||||||
|
Type string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||||
|
a := &Authorization{
|
||||||
|
URI: uri,
|
||||||
|
Status: z.Status,
|
||||||
|
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||||
|
Combinations: z.Combinations, // shallow copy
|
||||||
|
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||||
|
}
|
||||||
|
for i, v := range z.Challenges {
|
||||||
|
a.Challenges[i] = v.challenge()
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||||
|
err := &AuthorizationError{
|
||||||
|
URI: uri,
|
||||||
|
Identifier: z.Identifier.Value,
|
||||||
|
}
|
||||||
|
for _, raw := range z.Challenges {
|
||||||
|
if raw.Error != nil {
|
||||||
|
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wireChallenge is ACME JSON challenge representation.
|
||||||
|
type wireChallenge struct {
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Type string
|
||||||
|
Token string
|
||||||
|
Status string
|
||||||
|
Error *wireError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wireChallenge) challenge() *Challenge {
|
||||||
|
v := &Challenge{
|
||||||
|
URI: c.URI,
|
||||||
|
Type: c.Type,
|
||||||
|
Token: c.Token,
|
||||||
|
Status: c.Status,
|
||||||
|
}
|
||||||
|
if v.Status == "" {
|
||||||
|
v.Status = StatusPending
|
||||||
|
}
|
||||||
|
if c.Error != nil {
|
||||||
|
v.Error = c.Error.error(nil)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// wireError is a subset of fields of the Problem Details object
|
||||||
|
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
||||||
|
type wireError struct {
|
||||||
|
Status int
|
||||||
|
Type string
|
||||||
|
Detail string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wireError) error(h http.Header) *Error {
|
||||||
|
return &Error{
|
||||||
|
StatusCode: e.Status,
|
||||||
|
ProblemType: e.Type,
|
||||||
|
Detail: e.Detail,
|
||||||
|
Header: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||||
|
// customizing a temporary certificate for TLS-based challenges.
|
||||||
|
type CertOption interface {
|
||||||
|
privateCertOpt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKey creates an option holding a private/public key pair.
|
||||||
|
// The private part signs a certificate, and the public part represents the signee.
|
||||||
|
func WithKey(key crypto.Signer) CertOption {
|
||||||
|
return &certOptKey{key}
|
||||||
|
}
|
||||||
|
|
||||||
|
type certOptKey struct {
|
||||||
|
key crypto.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*certOptKey) privateCertOpt() {}
|
||||||
|
|
||||||
|
// WithTemplate creates an option for specifying a certificate template.
|
||||||
|
// See x509.CreateCertificate for template usage details.
|
||||||
|
//
|
||||||
|
// In TLS ChallengeCert methods, the template is also used as parent,
|
||||||
|
// resulting in a self-signed certificate.
|
||||||
|
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||||
|
func WithTemplate(t *x509.Certificate) CertOption {
|
||||||
|
return (*certOptTemplate)(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type certOptTemplate x509.Certificate
|
||||||
|
|
||||||
|
func (*certOptTemplate) privateCertOpt() {}
|
27
vendor/golang.org/x/crypto/acme/version_go112.go
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/acme/version_go112.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.12
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import "runtime/debug"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Set packageVersion if the binary was built in modules mode and x/crypto
|
||||||
|
// was not replaced with a different module.
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, m := range info.Deps {
|
||||||
|
if m.Path != "golang.org/x/crypto" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Replace == nil {
|
||||||
|
packageVersion = m.Version
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user