Compare commits

...

60 Commits

Author SHA1 Message Date
4e979d76a3 move scanip from cmdline to config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-09-11 13:28:09 +02:00
6561a77d6d updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-19 03:25:49 +02:00
b8521f050a updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
* added version in user agent
* lint code
* removed compat with config v2
2024-07-17 23:13:21 +02:00
1f56bd64ca Merge pull request 'dependencies + new index' (#6) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #6
2024-05-30 09:12:54 +02:00
eb9bdf35cd update indices on event table
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-30 09:10:36 +02:00
938ef9fb3f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-30 09:09:13 +02:00
f8ecc86006 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-11 18:03:51 +02:00
63855a9e21 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-03-21 07:42:39 +01:00
45f86d5598 Merge pull request 'updated dependencies - fosdem 2024 commit' (#5) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: #5
2024-02-03 15:24:43 +01:00
c782383171 updated dependencies - fosdem 2024 commit
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-03 13:23:32 +01:00
08f9378de8 added indices to event table
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-31 01:14:40 +01:00
d7fcd209a4 updated gcConnOnError func
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-26 16:26:17 +01:00
5858b1398f added conn close on ping call
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-26 13:15:11 +01:00
583742eef6 added ping method
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-24 13:57:51 +01:00
f1f6b2dbc9 replaced classic map by sync.Map
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-24 10:22:26 +01:00
2a52ef3157 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-24 09:36:41 +01:00
137cbd8d01 updated functions names 2023-12-24 09:36:33 +01:00
d03dc8b6cd Merge pull request 'update to 1.6.0' (#4) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #4
2023-11-10 23:34:38 +01:00
8deaf69baf added switch to manually migrate data
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-10 23:24:23 +01:00
246406f638 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-10 23:16:44 +01:00
8d45b04e99 code cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-03 11:02:06 +01:00
410d4dd810 feat: implement new config handling
All checks were successful
continuous-integration/drone/push Build is passing
* new config endpoint with v=2 param
* fix messages in websockets handlers
2023-11-03 10:06:17 +01:00
580ded68bb removed zmq from drone
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-11-01 18:03:40 +01:00
b1d681f13f feat: added ip type on event
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-28 16:08:50 +02:00
491718ff80 change on ip model
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-21 02:39:28 +02:00
9e5ffdd6b6 updated dependencies 2023-10-21 02:38:57 +02:00
74c266d853 updates on multithreading (#3)
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #3
2023-09-16 09:57:43 +02:00
995a10ca17 Merge pull request 'updates on dependencies + multithreading fixes' (#2) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #2
2023-09-14 15:42:49 +02:00
ee28c124a0 fix on database package
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-14 15:39:42 +02:00
9bea58a890 fix: fixes on debug mode + multithreading
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-09-14 15:33:09 +02:00
945b53f28c updated dependencies (#1)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #1
2023-09-10 20:41:55 +02:00
46b3c1abc3 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-10 20:40:56 +02:00
fbf9382ca4 updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-10 20:37:45 +02:00
e068b4740c updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-10 20:36:57 +02:00
8862ac15b0 Merge branch 'develop-websocket'
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-06-27 12:00:30 +02:00
16b150503f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-09 13:44:44 +02:00
62ed0e030c added error handling
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-05 22:00:52 +02:00
670f8a6f9f updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-27 18:36:15 +02:00
e5658ff99f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-11 07:36:48 +02:00
2f60ee84e1 added timeout on ip scan when error 2023-05-01 08:20:03 +02:00
a735f54024 updated WelcomeAgents func
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-29 11:14:35 +02:00
efc26fb27a updated README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-28 18:18:35 +02:00
6b569681fe updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-28 17:51:43 +02:00
2ed127c238 updated dependencies 2023-04-10 18:08:34 +02:00
5da8a5c952 fix: re-added gcConnOnError, addcheck on send 2023-04-10 17:10:04 +02:00
b99badcb87 fix: tmp remove gcConnOnError 2023-04-10 16:59:48 +02:00
1802abe553 fix: gcConnOnError 2023-04-10 16:53:53 +02:00
77919b208f fix: gcConnOnError 2023-04-10 15:28:36 +02:00
8b617a8f9f fix: gcConn 2023-04-10 12:04:40 +02:00
447f756922 updated ipbl websocket feat 2023-04-10 11:57:25 +02:00
abc02caf92 updated ipbl websocket feat 2023-04-09 17:38:19 +02:00
14d72693dd more stable ipbl websocket feat 2023-04-09 15:05:31 +02:00
f5ed3ee455 updated ipbl websocket feat 2023-04-09 01:41:52 +02:00
e84ca893d0 updated dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-26 11:18:08 +02:00
0354fe3255 updated ws sockets
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-12 14:25:40 +01:00
697df68640 updated ws feature
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-05 23:05:05 +01:00
bb76849f33 remove zmq from project
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-05 22:37:17 +01:00
98f07aaff8 updated dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-05 22:32:40 +01:00
118588e5bb added initial websocket feature / remove zeromq
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-05 22:30:42 +01:00
05a275c9d3 updated dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-18 22:47:12 +01:00
524 changed files with 36109 additions and 16693 deletions

View File

@ -16,8 +16,6 @@ steps:
environment: environment:
VERSION: dev VERSION: dev
commands: commands:
- apt-get update -y
- apt-get install libczmq-dev -y
- go generate $SRCFILES - go generate $SRCFILES
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
when: when:
@ -42,8 +40,6 @@ steps:
image: golang image: golang
commands: commands:
- export VERSION=$DRONE_TAG - export VERSION=$DRONE_TAG
- apt-get update -y
- apt-get install libczmq-dev -y
- go generate $SRCFILES - go generate $SRCFILES
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME - tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
@ -84,8 +80,6 @@ steps:
environment: environment:
VERSION: dev VERSION: dev
commands: commands:
- apt-get update -y
- apt-get install libczmq-dev -y
- go generate $SRCFILES - go generate $SRCFILES
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
when: when:
@ -110,8 +104,6 @@ steps:
image: golang image: golang
commands: commands:
- export VERSION=$DRONE_TAG - export VERSION=$DRONE_TAG
- apt-get update -y
- apt-get install libczmq-dev -y
- go generate $SRCFILES - go generate $SRCFILES
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME - tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
*.ini *.ini
*.swp *.swp
/test /test
/old
version.go version.go

View File

@ -35,7 +35,7 @@ db_table="ipbl"
## License ## License
```text ```text
Copyright (c) 2020, 2021, 2022 PaulBSD Copyright (c) 2021, 2022, 2023 PaulBSD
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -6,11 +6,14 @@ import (
"log" "log"
"os" "os"
"net/http"
_ "net/http/pprof"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"git.paulbsd.com/paulbsd/ipbl/src/database" "git.paulbsd.com/paulbsd/ipbl/src/database"
"git.paulbsd.com/paulbsd/ipbl/src/models" "git.paulbsd.com/paulbsd/ipbl/src/models"
"git.paulbsd.com/paulbsd/ipbl/src/routers" "git.paulbsd.com/paulbsd/ipbl/src/routers"
"git.paulbsd.com/paulbsd/ipbl/src/zmqrouter" "git.paulbsd.com/paulbsd/ipbl/src/ws"
"git.paulbsd.com/paulbsd/ipbl/utils" "git.paulbsd.com/paulbsd/ipbl/utils"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -33,10 +36,17 @@ func main() {
} }
defer cfg.Db.Close() defer cfg.Db.Close()
if !cfg.Switchs.NoScanIP { if cfg.Options.ScanIP {
go models.ScanIP(&cfg) go models.ScanIP(&cfg)
} }
go zmqrouter.Init(&cfg)
if cfg.Options.Debug {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
ws.Init(&cfg)
go func() { err = routers.RunServer(&ctx, &cfg) }() go func() { err = routers.RunServer(&ctx, &cfg) }()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)

25
go.mod
View File

@ -1,23 +1,23 @@
module git.paulbsd.com/paulbsd/ipbl module git.paulbsd.com/paulbsd/ipbl
go 1.19 go 1.23
require ( require (
github.com/labstack/echo/v4 v4.10.0 github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.9
golang.org/x/net v0.28.0
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/zeromq/goczmq.v4 v4.1.0 xorm.io/xorm v1.3.9
xorm.io/xorm v1.3.2
) )
require ( require (
github.com/goccy/go-json v0.10.0 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/labstack/gommon v0.4.0 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
@ -25,9 +25,8 @@ require (
github.com/syndtr/goleveldb v1.0.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.5.0 // indirect golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.5.0 // indirect golang.org/x/sys v0.24.0 // indirect
golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.17.0 // indirect
golang.org/x/text v0.6.0 // indirect xorm.io/builder v0.3.13 // indirect
xorm.io/builder v0.3.12 // indirect
) )

644
go.sum
View File

@ -1,97 +1,19 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -99,187 +21,42 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -290,407 +67,114 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/zeromq/goczmq.v4 v4.1.0 h1:CE+FE81mGVs2aSlnbfLuS1oAwdcVywyMM2AC1g33imI= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/zeromq/goczmq.v4 v4.1.0/go.mod h1:h4IlfePEYMpFdywGr5gAwKhBBj+hiBl/nF4VoSE4k+0= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=

View File

@ -10,31 +10,28 @@ import (
func (cfg *Config) GetConfig() error { func (cfg *Config) GetConfig() error {
var configfile string var configfile string
var debug bool
var drop bool var drop bool
var init bool var init bool
var port int var port int
var version bool var version bool
var noscanip bool var migrate bool
flag.Usage = utils.Usage flag.Usage = utils.Usage
flag.StringVar(&configfile, "configfile", "ipbl.ini", "Configuration file to use with ipbl section") flag.StringVar(&configfile, "configfile", "ipbl.ini", "Configuration file to use with ipbl section")
flag.IntVar(&port, "port", 8099, "Web service port to use") flag.IntVar(&port, "port", 8099, "Web service port to use")
flag.BoolVar(&debug, "debug", false, "If debug logging must be enabled")
flag.BoolVar(&drop, "drop", false, "If dropping tables must occur") flag.BoolVar(&drop, "drop", false, "If dropping tables must occur")
flag.BoolVar(&init, "init", false, "If init of database must be done") flag.BoolVar(&init, "init", false, "If init of database must be done")
flag.BoolVar(&version, "version", false, "Show version") flag.BoolVar(&version, "version", false, "Show version")
flag.BoolVar(&noscanip, "noscanip", false, "Do not run scanip") flag.BoolVar(&migrate, "migrate", false, "Do migrations on version changes")
flag.Parse() flag.Parse()
cfg.Switchs.Debug = debug
cfg.Switchs.Drop = drop cfg.Switchs.Drop = drop
cfg.Switchs.Init = init cfg.Switchs.Init = init
cfg.Switchs.Port = port cfg.Switchs.Port = port
cfg.Switchs.Version = version cfg.Switchs.Version = version
cfg.Switchs.NoScanIP = noscanip cfg.Switchs.Migrate = migrate
var inicfg, err = ini.Load(configfile) var inicfg, err = ini.Load(configfile)
if err != nil { if err != nil {
@ -48,8 +45,10 @@ func (cfg *Config) GetConfig() error {
cfg.DbParams.DbPassword = ipblsection.Key("password").MustString("password") cfg.DbParams.DbPassword = ipblsection.Key("password").MustString("password")
cfg.DbParams.DbDatabase = ipblsection.Key("database").MustString("database") cfg.DbParams.DbDatabase = ipblsection.Key("database").MustString("database")
cfg.Options.ZMQChannel = "ipbl" cfg.Options.SocketChannel = "ipbl"
cfg.Options.HideBanner = ipblsection.Key("hidebanner").MustBool(false) cfg.Options.HideBanner = ipblsection.Key("hidebanner").MustBool(false)
cfg.Options.Debug = ipblsection.Key("debug").MustBool(false)
cfg.Options.ScanIP = ipblsection.Key("scanip").MustBool(false)
return nil return nil
} }
@ -63,17 +62,18 @@ type Config struct {
DbDatabase string DbDatabase string
} }
Options struct { Options struct {
Version string Version string
HideBanner bool HideBanner bool
ZMQChannel string SocketChannel string
Debug bool
ScanIP bool
} }
Switchs struct { Switchs struct {
Port int Port int
NoFeed bool NoFeed bool
Debug bool Drop bool
Drop bool Init bool
Init bool Version bool
Version bool Migrate bool
NoScanIP bool
} }
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"git.paulbsd.com/paulbsd/ipbl/src/models" "git.paulbsd.com/paulbsd/ipbl/src/models"
@ -30,7 +31,7 @@ func Initialize(ctx *context.Context, cfg *config.Config) (err error) {
} }
cfg.Db.SetMapper(names.GonicMapper{}) cfg.Db.SetMapper(names.GonicMapper{})
cfg.Db.SetQuotePolicy(dialects.QuotePolicyReserved) cfg.Db.SetQuotePolicy(dialects.QuotePolicyReserved)
cfg.Db.ShowSQL(cfg.Switchs.Debug) cfg.Db.ShowSQL(cfg.Options.Debug)
//cfg.Db.SetDefaultCacher(cacher) //cfg.Db.SetDefaultCacher(cacher)
@ -42,5 +43,35 @@ func Initialize(ctx *context.Context, cfg *config.Config) (err error) {
log.Println("Syncing tables") log.Println("Syncing tables")
err = models.NewEngine(ctx, cfg) err = models.NewEngine(ctx, cfg)
if cfg.Switchs.Migrate {
migrate(cfg)
}
return
}
func migrate(cfg *config.Config) (err error) {
var cfgsets []models.CfgSet
num, err := cfg.Db.FindAndCount(&cfgsets)
fmt.Printf("%d sets found\n", num)
if err != nil {
fmt.Println(err)
}
for _, cfgset := range cfgsets {
var reg = cfgset.Regex
reg = strings.Trim(reg, "()")
rs := strings.Split(reg, "|")
for _, v := range rs {
if v != "" {
n := models.CfgExpr{
Expr: v,
Set: &cfgset,
Enabled: true,
}
cfg.Db.Insert(&n)
}
}
}
return return
} }

View File

@ -10,40 +10,39 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
// const ipv4_regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})/`
const ipv4_cidr_regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|)){4}\/([1-3])?([0-9])?$)` const ipv4_cidr_regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|)){4}\/([1-3])?([0-9])?$)`
func GetTrustlists(cfg config.Config) (res []string, err error) { func GetTrustlists(cfg *config.Config) (res []string, err error) {
var w []CfgTrustlist var ctl []CfgTrustlist
err = cfg.Db.Find(&w) err = cfg.Db.Find(&ctl)
if len(w) > 0 { if len(ctl) > 0 {
for _, a := range w { for _, a := range ctl {
res = append(res, a.IP) res = append(res, a.IP)
} }
} }
return return
} }
func (wl CfgTrustlist) InsertOrUpdate(cfg config.Config) (err error) { func (tl CfgTrustlist) InsertOrUpdate(cfg *config.Config) (err error) {
var w = Cfg{Key: "trustlist"} var w = Cfg{Key: "trustlist"}
exists, _ := cfg.Db.Get(&w) exists, _ := cfg.Db.Get(&w)
if exists { if exists {
existing, _ := GetTrustlists(cfg) existing, _ := GetTrustlists(cfg)
for _, j := range existing { for _, j := range existing {
if j == wl.IP { if j == tl.IP {
return fmt.Errorf("ip %s already in config", j) return fmt.Errorf("ip %s already in config", j)
} }
} }
existing = append(existing, wl.IP) existing = append(existing, tl.IP)
w.Value = strings.Join(existing, ",") w.Value = strings.Join(existing, ",")
cfg.Db.ID(w.ID).Update(&w) cfg.Db.ID(w.ID).Update(&w)
} }
return fmt.Errorf("no trustlist updated") return fmt.Errorf("no trustlist updated")
} }
func (wl CfgTrustlist) Delete(cfg config.Config, ip string) (affected int64, err error) { func (tl CfgTrustlist) Delete(cfg *config.Config, ip string) (affected int64, err error) {
var w = CfgTrustlist{IP: ip} var w = CfgTrustlist{IP: ip}
exists, _ := cfg.Db.Get(&w) exists, _ := cfg.Db.Get(&w)
@ -54,21 +53,34 @@ func (wl CfgTrustlist) Delete(cfg config.Config, ip string) (affected int64, err
return return
} }
func (wl CfgTrustlist) Verify() bool { func (tl CfgTrustlist) Verify() bool {
reg := regexp.MustCompile(ipv4_cidr_regex) reg := regexp.MustCompile(ipv4_cidr_regex)
return reg.MatchString(wl.IP) return reg.MatchString(tl.IP)
} }
func GetSets(cfg config.Config) (res []CfgSet, err error) { func (cfgset *CfgSet) BuildRegex(cfg *config.Config) {
var rr []CfgExpr
err := cfg.Db.Where("cfgset_id = $1 AND enabled", cfgset.ID).OrderBy("expr").Find(&rr)
if err != nil {
fmt.Println(err)
}
regs := []string{}
for _, v := range rr {
regs = append(regs, v.Expr)
}
cfgset.Regex = fmt.Sprintf("(%s)", strings.Join(regs, "|"))
}
func GetSets(cfg *config.Config) (res []CfgSet, err error) {
var w = []CfgSet{} var w = []CfgSet{}
if err := cfg.Db.Where("enabled is true").Find(&w); err == nil { if err := cfg.Db.Where("enabled").Find(&w); err == nil {
return w, err return w, err
} }
return return
} }
func GetGlobalConfig(cfg config.Config) (res []Cfg, err error) { func GetGlobalConfig(cfg *config.Config) (res []Cfg, err error) {
var w = []Cfg{} var w = []Cfg{}
if err := cfg.Db.Find(&w); err == nil { if err := cfg.Db.Find(&w); err == nil {
@ -77,7 +89,41 @@ func GetGlobalConfig(cfg config.Config) (res []Cfg, err error) {
return return
} }
func InsertOrUpdateSets(cfg config.Config, folders []CfgSet) (res string, err error) { func GetAllConfigv2(cfg *config.Config) (res CfgAllv2, err error) {
var c = make(map[string]string)
var tmpcfg = []Cfg{}
if err := cfg.Db.Find(&tmpcfg); err == nil {
for _, v := range tmpcfg {
c[v.Key] = v.Value
}
res.Cfg = c
}
var sets = []CfgSet{}
if err := cfg.Db.Find(&sets); err == nil {
for i, _ := range sets {
sets[i].BuildRegex(cfg)
}
res.Sets = sets
}
var tl = []CfgTrustlist{}
if err := cfg.Db.Find(&tl); err == nil {
for _, v := range tl {
res.Trustlists = append(res.Trustlists, v.IP)
}
}
var ws = []CfgWS{}
if err := cfg.Db.Find(&ws); err == nil {
res.WS = ws
}
return
}
func InsertOrUpdateSets(cfg *config.Config, folders []CfgSet) (res string, err error) {
var w = Cfg{Key: "folders"} var w = Cfg{Key: "folders"}
if exists, _ := cfg.Db.Get(&w); exists { if exists, _ := cfg.Db.Get(&w); exists {
@ -94,8 +140,8 @@ func InsertOrUpdateSets(cfg config.Config, folders []CfgSet) (res string, err er
return return
} }
func GetZMQ(cfg config.Config) (res []CfgZMQ, err error) { func GetWS(cfg *config.Config) (res []CfgWS, err error) {
var w = []CfgZMQ{} var w = []CfgWS{}
if err = cfg.Db.Find(&w); err == nil { if err = cfg.Db.Find(&w); err == nil {
return w, err return w, err
@ -103,7 +149,7 @@ func GetZMQ(cfg config.Config) (res []CfgZMQ, err error) {
return return
} }
func DiscoverURLS(cfg config.Config, routes []*echo.Route) (Discovery, error) { func DiscoverURLS(cfg *config.Config, routes []*echo.Route) (Discovery, error) {
var disc Discovery var disc Discovery
var urls = make(map[string]Url) var urls = make(map[string]Url)
for _, j := range routes { for _, j := range routes {
@ -118,6 +164,13 @@ func DiscoverURLS(cfg config.Config, routes []*echo.Route) (Discovery, error) {
return disc, nil return disc, nil
} }
type CfgAllv2 struct {
Cfg map[string]string `json:"cfg"`
Sets []CfgSet `json:"sets"`
Trustlists []string `json:"trustlists"`
WS []CfgWS `json:"ws"`
}
type Cfg struct { type Cfg struct {
ID int `xorm:"pk autoincr" json:"-"` ID int `xorm:"pk autoincr" json:"-"`
Key string `xorm:"text notnull unique" json:"key"` Key string `xorm:"text notnull unique" json:"key"`
@ -129,22 +182,28 @@ type CfgSet struct {
Path string `xorm:"text notnull default" json:"path"` Path string `xorm:"text notnull default" json:"path"`
Src string `xorm:"text notnull" json:"src"` Src string `xorm:"text notnull" json:"src"`
Filename string `xorm:"text notnull" json:"filename"` Filename string `xorm:"text notnull" json:"filename"`
Regex string `xorm:"text notnull" json:"regex"` Regex string `xorm:"-" json:"regex"`
Blocktime int64 `xorm:"notnull default 60" json:"blocktime"` Blocktime int64 `xorm:"notnull default 60" json:"blocktime"`
TryFail int64 `xorm:"notnull default 5" json:"tryfail"` TryFail int64 `xorm:"notnull default 5" json:"tryfail"`
Enabled bool `xorm:"notnull default true" json:"-"` Enabled bool `xorm:"notnull default true" json:"-"`
} }
type CfgExpr struct {
ID int `xorm:"pk autoincr" json:"-"`
Expr string `xorm:"text notnull unique(exprindex) index default ''"`
Enabled bool `xorm:"notnull default true" json:"-"`
Set *CfgSet `xorm:"cfgset_id int unique(exprindex) default null"`
}
type CfgTrustlist struct { type CfgTrustlist struct {
ID int `xorm:"pk autoincr" json:"-"` ID int `xorm:"pk autoincr" json:"-"`
IP string `xorm:"text notnull" json:"ip"` IP string `xorm:"text notnull" json:"ip"`
} }
type CfgZMQ struct { type CfgWS struct {
ID int `xorm:"pk autoincr" json:"-"` ID int `xorm:"pk autoincr" json:"-"`
Type string `xorm:"text notnull" json:"type"` Type string `xorm:"text notnull" json:"type"`
Hostname string `xorm:"text notnull" json:"hostname"` Endpoint string `xorm:"text notnull" json:"endpoint"`
Port int `json:"port"`
Subscription string `json:"subscription"` Subscription string `json:"subscription"`
} }

View File

@ -15,21 +15,6 @@ func (event *Event) Insert(cfg *config.Config) (err error) {
return return
} }
/*
func (event *Event) APIFormat() *APIEvent {
if event == nil {
return nil
}
return &APIEvent{
MSgType: "none",
IPData: {
IP: event.IP.IP,
Src: event.Src,
Hostname: event.Hostname.String,
},
}
}*/
func (event *Event) APIParse(session *xorm.Session, apievent APIEvent) (err error) { func (event *Event) APIParse(session *xorm.Session, apievent APIEvent) (err error) {
*event = Event{ *event = Event{
IP: &IP{IP: apievent.IPData.IP}, IP: &IP{IP: apievent.IPData.IP},
@ -44,10 +29,10 @@ func (event *Event) APIParse(session *xorm.Session, apievent APIEvent) (err erro
type Event struct { type Event struct {
ID int `xorm:"pk autoincr"` ID int `xorm:"pk autoincr"`
Src *Src `xorm:"int src_id"` Src *Src `xorm:"int src_id index"`
Host *Host `xorm:"int host_id"` Host *Host `xorm:"int host_id index"`
IP *IP `xorm:"int ip_id"` IP *IP `xorm:"int ip_id index"`
Created time.Time `xorm:"created notnull"` Created time.Time `xorm:"created notnull index"`
} }
type APIEvent struct { type APIEvent struct {
@ -56,6 +41,7 @@ type APIEvent struct {
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
IPData struct { IPData struct {
IP string `json:"ip"` IP string `json:"ip"`
Type int `json:"t"`
Src string `json:"src"` Src string `json:"src"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
Date string `json:"date"` Date string `json:"date"`

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"runtime"
"sync" "sync"
"time" "time"
@ -80,23 +81,10 @@ func (ip *IP) Get(session *xorm.Session) (apiip *APIIP, err error) {
} }
func (ip *IP) InsertOrUpdate(session *xorm.Session) (numinsert int64, numupdate int64, err error) { func (ip *IP) InsertOrUpdate(session *xorm.Session) (numinsert int64, numupdate int64, err error) {
//var ips = []IP{}
//err = session.Where("ip = ?", ip.IP).And("src = ?", ip.Src).And("hostname = ?", ip.Hostname).Find(&ips)
has, err := session.Get(ip) has, err := session.Get(ip)
if has { if has {
session.ID(ip.ID).Update(&IP{}) session.ID(ip.ID).Update(&IP{})
} }
/*if len(ips) > 0 {
numupdate, err = session.Where("id = ?", ips[0].ID).Cols("updated").Update(&IP{})
if err != nil {
log.Println(err)
}
} else {
numinsert, err = session.Insert(ip)
if err != nil {
log.Println(err)
}
}*/
session.Commit() session.Commit()
return return
} }
@ -107,53 +95,40 @@ func InsertIPBulk(session *xorm.Session, ips *[]IP) (numinsert int64, numupdate
} }
Cleanup(session) Cleanup(session)
return return
/*var iplist []string
hostname := (*ips)[0].Hostname.String
for _, ip := range *ips {
iplist = append(iplist, ip.IP)
}
var searchips []IP
cfg.Db.In("ip", iplist).Where("hostname = ?", hostname).Find(&searchips)
var toupdateips []string
for _, ip := range searchips {
toupdateips = append(toupdateips, ip.IP)
}
numupdate, _ = cfg.Db.In("ip", toupdateips).Where("hostname = ?", hostname).Cols("updated").Update(&IP{})
var toinsertip, _ = differ(*ips, searchips)
numinsert, err = cfg.Db.Insert(toinsertip)
Cleanup(cfg)
return*/
} }
func ScanIP(cfg *config.Config) (err error) { func ScanIP(cfg *config.Config) (err error) {
var numthreads = 8 var numthreads int = runtime.NumCPU() / 2
for { for {
session := cfg.Db.NewSession() session := cfg.Db.NewSession()
orphans := []IP{} orphans := []IP{}
err = session.Where(`((as_id IS NULL err = session.Where(`
OR country_id IS NULL (
OR city_id IS NULL (
OR rdns = '' as_id IS NULL
OR rdns IS NULL) AND updated < now()-'1d'::interval) OR OR country_id IS NULL
(as_id IS NULL OR city_id IS NULL
AND country_id IS NULL OR rdns = ''
AND city_id IS NULL OR rdns IS NULL
AND rdns IS NULL) )
`).Desc("updated").Limit(SCANLIMIT).Find(&orphans) AND updated < now()-'1d'::interval
)
OR
(
as_id IS NULL
AND country_id IS NULL
AND city_id IS NULL
AND rdns IS NULL
)
`).Desc("updated").Limit(SCANLIMIT).Find(&orphans)
session.Close() session.Close()
if err == nil && len(orphans) > 0 { if err == nil && len(orphans) > 0 {
orphanchan := make(chan IP) orphanchan := make(chan IP)
done := make(chan bool)
var wg sync.WaitGroup var wg sync.WaitGroup
for thr := range make([]int, numthreads) { for thr := range make([]int, numthreads) {
go ScanOrphan(&wg, orphanchan, done, thr, cfg) go ScanOrphan(&wg, orphanchan, thr, cfg)
} }
for _, orphan := range orphans { for _, orphan := range orphans {
@ -161,30 +136,31 @@ func ScanIP(cfg *config.Config) (err error) {
} }
close(orphanchan) close(orphanchan)
<-done
wg.Wait() wg.Wait()
} else { } else {
time.Sleep(30 * time.Second) time.Sleep(2 * time.Second)
} }
} }
} }
func ScanOrphan(wg *sync.WaitGroup, orphans chan IP, done chan bool, thr int, cfg *config.Config) (err error) { func ScanOrphan(wg *sync.WaitGroup, orphans chan IP, thr int, cfg *config.Config) (err error) {
wg.Add(1) wg.Add(1)
session := cfg.Db.NewSession()
defer session.Close()
queryclient := http.Client{}
for { for {
orphan, more := <-orphans orphan, more := <-orphans
if more { if more {
session := cfg.Db.NewSession()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
queryclient := http.Client{}
var query QueryIP var query QueryIP
query, err := QueryInfo(&queryclient, orphan.IP) query, err := QueryInfo(cfg, &queryclient, orphan.IP)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
time.Sleep(1 * time.Minute)
continue continue
} }
@ -202,63 +178,53 @@ func ScanOrphan(wg *sync.WaitGroup, orphans chan IP, done chan bool, thr int, cf
} }
orphan.Rdns = sql.NullString{String: query.Rdns, Valid: true} orphan.Rdns = sql.NullString{String: query.Rdns, Valid: true}
log.Printf("%s -> \"%s\"\n", orphan.IP, query.Rdns) if cfg.Options.Debug {
log.Printf("t%d: %s -> \"%s\"\n", thr, orphan.IP, query.Rdns)
orphan.GetOrCreate(session)
err = session.Commit()
if err != nil {
log.Println(err)
} }
err = session.Close()
_, err = orphan.GetOrCreate(session)
if err != nil { if err != nil {
log.Println(err) continue
} }
} else { } else {
//fmt.Printf("All orphan migrated on thread num %d\n", thr) log.Printf("All orphan migrated on thread num %d\n", thr)
wg.Done() wg.Done()
done <- true break
return nil
} }
} }
err = session.Commit()
if err != nil {
log.Println(err)
}
return nil
} }
func QueryInfo(client *http.Client, ip string) (query QueryIP, err error) { func QueryInfo(cfg *config.Config, client *http.Client, ip string) (query QueryIP, err error) {
var url = fmt.Sprintf("%s/%s", IPINFO_WS, ip) var url = fmt.Sprintf("%s/%s", IPINFO_WS, ip)
req, _ := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
}
req.Header.Add("Accept", "*/*") req.Header.Add("Accept", "*/*")
req.Header.Add("User-Agent", "ipbl") req.Header.Add("User-Agent", fmt.Sprintf("ipbl %s", cfg.Options.Version))
res, _ := client.Do(req) res, err := client.Do(req)
data, _ := io.ReadAll(res.Body) if err != nil {
log.Println(err)
return
}
data, err := io.ReadAll(res.Body)
if err != nil {
log.Println(err)
}
err = json.Unmarshal(data, &query) err = json.Unmarshal(data, &query)
if err != nil {
log.Println(err)
}
return return
} }
func Cleanup(session *xorm.Session) (err error) { func Cleanup(session *xorm.Session) (err error) {
results, _ := session.Query(`
SELECT *
FROM ip
WHERE ip IN (
SELECT ip
FROM ip
GROUP BY ip
HAVING COUNT(ip) > 1)
AND hostname IS NULL
ORDER BY updated DESC;`)
if len(results) > 0 {
_, err := session.Query(`
DELETE FROM ip
WHERE ip IN (
SELECT ip
FROM ip
GROUP by ip
HAVING COUNT(ip) > 1)
AND hostname IS NULL;`)
if err != nil {
log.Println("error deleting orphans")
}
}
session.Commit()
return return
} }
@ -295,13 +261,13 @@ func (ip *APIIP) BeforeInsert() (err error) {
type IP struct { type IP struct {
ID int `xorm:"pk autoincr"` ID int `xorm:"pk autoincr"`
IP string `xorm:"text notnull"` IP string `xorm:"text notnull unique"`
Rdns sql.NullString `xorm:"text default ''"` Rdns sql.NullString `xorm:"text index default ''"`
AutonomousSystem *AutonomousSystem `xorm:"as_id int index default null"` AutonomousSystem *AutonomousSystem `xorm:"as_id int index default null"`
City *City `xorm:"city_id int index default null"` City *City `xorm:"city_id int index default null"`
Country *Country `xorm:"country_id int index default null"` Country *Country `xorm:"country_id int index default null"`
Created time.Time `xorm:"created notnull"` Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"` Updated time.Time `xorm:"updated index notnull"`
} }
type APIIP struct { type APIIP struct {

View File

@ -5,15 +5,11 @@ import (
"fmt" "fmt"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"xorm.io/xorm"
"xorm.io/xorm/names" "xorm.io/xorm/names"
) )
var ( var (
x *xorm.Engine tables []interface{}
tables []interface{}
// HasEngine specifies if we have a xorm.Engine
HasEngine bool HasEngine bool
) )
@ -22,8 +18,9 @@ func init() {
new(AutonomousSystem), new(AutonomousSystem),
new(Cfg), new(Cfg),
new(CfgSet), new(CfgSet),
new(CfgExpr),
new(CfgTrustlist), new(CfgTrustlist),
new(CfgZMQ), new(CfgWS),
new(City), new(City),
new(Country), new(Country),
new(Event), new(Event),

View File

@ -2,8 +2,6 @@ package models
import "reflect" import "reflect"
//const keyname string = "id"
func Differ(sl1 []IP, sl2 []IP) (toinsert []IP, err error) { func Differ(sl1 []IP, sl2 []IP) (toinsert []IP, err error) {
var m = make(map[string]IPDiffer) var m = make(map[string]IPDiffer)
longslice := append(sl1, sl2...) longslice := append(sl1, sl2...)

View File

@ -3,23 +3,26 @@ package routers
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"net/http" "net/http"
"strconv" "strconv"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"git.paulbsd.com/paulbsd/ipbl/src/models" "git.paulbsd.com/paulbsd/ipbl/src/models"
"git.paulbsd.com/paulbsd/ipbl/src/ws"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) { func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) {
e.GET("/", func(c echo.Context) error { e.GET("/", func(c echo.Context) error {
return c.HTML(http.StatusOK, `<html> return c.HTML(http.StatusOK, `
<body> <html>
<p>Welcome to ipbl software (<a href="https://git.paulbsd.com/paulbsd/ipbl">https://git.paulbsd.com/paulbsd/ipbl</a>)</p> <head></head>
</body> <body>
</html>`) <p>Welcome to ipbl software (<a href="https://git.paulbsd.com/paulbsd/ipbl">https://git.paulbsd.com/paulbsd/ipbl</a>)</p>
</body>
</html>
`)
}) })
e.GET("/health", func(c echo.Context) error { e.GET("/health", func(c echo.Context) error {
@ -85,8 +88,7 @@ func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) {
ips = append(ips, *ip) ips = append(ips, *ip)
} }
numinsert, numupdate, _ := models.InsertIPBulk(session, &ips) numinsert, numupdate, _ := models.InsertIPBulk(session, &ips)
msg = fmt.Sprintf("zmq: Inserted %d IP, Updated %d IP", numinsert, numupdate) msg = fmt.Sprintf("Inserted %d IP, Updated %d IP", numinsert, numupdate)
log.Println(msg)
return Result(c, err, msg) return Result(c, err, msg)
}) })
e.POST("/event", func(c echo.Context) (err error) { e.POST("/event", func(c echo.Context) (err error) {
@ -115,18 +117,18 @@ func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) {
return Result(c, err, "") return Result(c, err, "")
}) })
e.GET("/config", func(c echo.Context) (err error) { e.GET("/config", func(c echo.Context) (err error) {
trustlists, err := models.GetGlobalConfig(*cfg) globalconfig, err := models.GetAllConfigv2(cfg)
return Result(c, err, trustlists) return Result(c, err, globalconfig)
}) })
e.GET("/config/trustlist", func(c echo.Context) (err error) { e.GET("/config/trustlist", func(c echo.Context) (err error) {
trustlists, err := models.GetTrustlists(*cfg) trustlists, err := models.GetTrustlists(cfg)
return Result(c, err, trustlists) return Result(c, err, trustlists)
}) })
e.POST("/config/trustlist", func(c echo.Context) (err error) { e.POST("/config/trustlist", func(c echo.Context) (err error) {
var cidr models.CfgTrustlist var cidr models.CfgTrustlist
err = c.Bind(&cidr) err = c.Bind(&cidr)
if err == nil && cidr.Verify() { if err == nil && cidr.Verify() {
err = cidr.InsertOrUpdate(*cfg) err = cidr.InsertOrUpdate(cfg)
return Result(c, err, cidr) return Result(c, err, cidr)
} }
return Result(c, err, nil) return Result(c, err, nil)
@ -134,19 +136,28 @@ func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) {
e.DELETE("/config/trustlist/:ip", func(c echo.Context) (err error) { e.DELETE("/config/trustlist/:ip", func(c echo.Context) (err error) {
var ip = c.Param("ip") var ip = c.Param("ip")
var cidr models.CfgTrustlist var cidr models.CfgTrustlist
_, err = cidr.Delete(*cfg, ip) _, err = cidr.Delete(cfg, ip)
return return
}) })
e.GET("/config/sets", func(c echo.Context) (err error) { e.GET("/config/sets", func(c echo.Context) (err error) {
sets, err := models.GetSets(*cfg) sets, err := models.GetSets(cfg)
return Result(c, err, sets) return Result(c, err, sets)
}) })
e.GET("/config/zmq", func(c echo.Context) (err error) { e.GET("/config/ws", func(c echo.Context) (err error) {
folders, err := models.GetZMQ(*cfg) folders, err := models.GetWS(cfg)
return Result(c, err, folders) return Result(c, err, folders)
}) })
e.GET("/discovery", func(c echo.Context) (err error) { e.GET("/discovery", func(c echo.Context) (err error) {
disc, err := models.DiscoverURLS(*cfg, e.Routes()) disc, err := models.DiscoverURLS(cfg, e.Routes())
return Result(c, err, disc) return Result(c, err, disc)
}) })
e.File("/test.html", "/home/paul/test.html")
e.GET("/wsps", func(c echo.Context) (err error) {
ws.HandleWSPS(&c, cfg)
return
})
e.GET("/wsrr", func(c echo.Context) (err error) {
ws.HandleWSRR(&c, cfg)
return
})
} }

66
src/ws/init.go Normal file
View File

@ -0,0 +1,66 @@
package ws
import (
"sync"
"time"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"golang.org/x/net/websocket"
)
var listeners sync.Map
func Init(cfg *config.Config) {
}
func welcomeAgents(ws *websocket.Conn, welcome wsWelcome, t string) {
connectinfoVal, ok := listeners.Load(welcome.Hostname)
if !ok {
switch t {
case "ps":
connectinfo := connectionInfo{
ConnectionPS: ws,
InitDate: time.Now(),
}
listeners.Store(welcome.Hostname, &connectinfo)
case "rr":
connectinfo := connectionInfo{
ConnectionRR: ws,
InitDate: time.Now(),
}
listeners.Store(welcome.Hostname, &connectinfo)
}
} else {
connectinfo := connectinfoVal.(*connectionInfo)
switch t {
case "ps":
connectinfo.ConnectionPS = ws
case "rr":
connectinfo.ConnectionRR = ws
}
}
}
func gcConnOnError(ws *websocket.Conn) (err error) {
listeners.Range(func(index, value interface{}) bool {
if value.(*connectionInfo).ConnectionPS == ws || value.(*connectionInfo).ConnectionRR == ws {
value.(*connectionInfo).ConnectionPS.Close()
value.(*connectionInfo).ConnectionRR.Close()
listeners.Delete(index)
}
return true
})
return err
}
type connectionInfo struct {
ConnectionPS *websocket.Conn
ConnectionRR *websocket.Conn
InitDate time.Time
}
// WSWelcome
type wsWelcome struct {
Hostname string
}

44
src/ws/pubsub.go Normal file
View File

@ -0,0 +1,44 @@
package ws
import (
"encoding/json"
"log"
"time"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"github.com/labstack/echo/v4"
"golang.org/x/net/websocket"
)
// HandleWSPS handle pub sub flows
func HandleWSPS(c *echo.Context, cfg *config.Config) (err error) {
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
var welcome = wsWelcome{}
var msg []byte
err := websocket.Message.Receive(ws, &msg)
if err != nil {
log.Println(err)
}
err = json.Unmarshal(msg, &welcome)
if err != nil {
log.Println(err)
} else {
welcomeAgents(ws, welcome, "ps")
}
err = websocket.Message.Receive(ws, "OK")
if err != nil {
log.Printf("disconnect: %s (from pubsub channel)\n", welcome.Hostname)
ws.Close()
}
for {
time.Sleep(50 * time.Millisecond)
}
}).ServeHTTP((*c).Response(), (*c).Request())
return nil
}

123
src/ws/reqrep.go Normal file
View File

@ -0,0 +1,123 @@
package ws
import (
"encoding/json"
"log"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"git.paulbsd.com/paulbsd/ipbl/src/models"
"github.com/labstack/echo/v4"
"golang.org/x/net/websocket"
)
// HandleWSRR handle req rep flows
func HandleWSRR(c *echo.Context, cfg *config.Config) error {
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
var welcome = wsWelcome{}
var msg []byte
err := websocket.Message.Receive(ws, &msg)
if err != nil {
log.Println(err)
}
err = json.Unmarshal(msg, &welcome)
if err == nil {
welcomeAgents(ws, welcome, "rr")
}
var lastip string
for {
var apievent = models.APIEvent{}
var event = models.Event{}
var msg []byte
err := websocket.Message.Receive(ws, &msg)
if err != nil {
log.Printf("disconnect: %s (from reqrep channel)", welcome.Hostname)
return
}
err = json.Unmarshal(msg, &apievent)
if err != nil {
log.Println(err)
}
if lastip != "" && apievent.IPData.IP == lastip {
continue
}
switch apievent.MsgType {
case "bootstrap":
log.Printf("bootstrap: %s\n", apievent.Hostname)
listeners.Range(func(index, value interface{}) bool {
if index != apievent.Hostname && value.(*connectionInfo).ConnectionPS != nil {
err = websocket.Message.Send(value.(*connectionInfo).ConnectionPS, msg)
if err != nil {
log.Println(err)
ws.Close()
gcConnOnError(ws)
}
}
return true
})
case "add":
session := cfg.Db.NewSession()
event.APIParse(session, apievent)
session.Close()
err := event.Insert(cfg)
if err != nil {
log.Println(err)
}
listeners.Range(func(index, value interface{}) bool {
if value.(*connectionInfo).ConnectionPS != nil {
err = websocket.Message.Send(value.(*connectionInfo).ConnectionPS, msg)
if err != nil {
log.Println(err)
ws.Close()
gcConnOnError(ws)
}
}
return true
})
log.Printf("ws: Inserted event")
case "init":
listeners.Range(func(index, value interface{}) bool {
if value.(*connectionInfo).ConnectionPS != nil {
err = websocket.Message.Send(value.(*connectionInfo).ConnectionPS, msg)
if err != nil {
log.Println(err)
ws.Close()
gcConnOnError(ws)
}
}
return true
})
case "ping":
listeners.Range(func(index, value interface{}) bool {
if index == apievent.Hostname && value.(*connectionInfo).ConnectionPS != nil {
err = websocket.Message.Send(value.(*connectionInfo).ConnectionPS, msg)
if err != nil {
log.Println(err)
ws.Close()
gcConnOnError(ws)
}
}
return true
})
default:
}
err = websocket.Message.Send(ws, "OK")
if err != nil {
log.Println(err)
gcConnOnError(ws)
}
}
}).ServeHTTP((*c).Response(), (*c).Request())
return nil
}

View File

@ -1,102 +0,0 @@
package zmqrouter
import (
"encoding/json"
"fmt"
"log"
"time"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"git.paulbsd.com/paulbsd/ipbl/src/models"
"gopkg.in/zeromq/goczmq.v4"
)
func Init(cfg *config.Config) (err error) {
log.Println("Initiating ZMQ sockets")
reqsock, err := InitRep()
if err != nil {
return
}
pubsock, err := InitPub()
if err != nil {
return
}
Handle(cfg, reqsock, pubsock, cfg.Options.ZMQChannel)
return
}
func Handle(cfg *config.Config, reqsock *goczmq.Sock, pubsock *goczmq.Sock, channel string) (err error) {
log.Println("Start handling zmq sockets")
var lastip string
for {
var msg = "err"
var req, err = reqsock.RecvMessage()
if err != nil {
log.Println("unable to receive message from req socket", err)
continue
}
var topub [][]byte
for _, val := range req {
var apievent = models.APIEvent{}
var event = models.Event{}
err = json.Unmarshal(val, &apievent)
if err != nil {
log.Println("unable to parse ip address", err)
time.Sleep(time.Second)
continue
}
if apievent.IPData.IP != "" && apievent.IPData.IP == lastip {
continue
}
if apievent.MsgType == "add" {
session := cfg.Db.NewSession()
event.APIParse(session, apievent)
session.Close()
err := event.Insert(cfg)
if err != nil {
log.Println(err)
}
log.Printf("zmq: Inserted event")
}
if apievent.MsgType == "file" {
apievent.MsgType = "zmq"
}
val, err = json.Marshal(apievent)
if err != nil {
fmt.Println(err)
}
tmpval := fmt.Sprintf("%s %s", channel, string(val))
ipjson := []byte(tmpval)
topub = append(topub, ipjson)
lastip = apievent.IPData.IP
}
err = pubsock.SendMessage(topub)
if err != nil {
log.Println("error sending message to pub socket")
continue
}
msg = "ok"
var resp [][]byte = [][]byte{[]byte(msg)}
err = reqsock.SendMessage(resp)
if err != nil {
log.Println("error replying message to req socket")
continue
}
}
}

View File

@ -1,14 +0,0 @@
package zmqrouter
import (
"fmt"
"gopkg.in/zeromq/goczmq.v4"
)
const pubsubport int = 9999
func InitPub() (sock *goczmq.Sock, err error) {
sock, err = goczmq.NewPub(fmt.Sprintf("tcp://*:%d", pubsubport))
return
}

View File

@ -1,14 +0,0 @@
package zmqrouter
import (
"fmt"
"gopkg.in/zeromq/goczmq.v4"
)
const repreqport int = 9998
func InitRep() (sock *goczmq.Sock, err error) {
sock, err = goczmq.NewRep(fmt.Sprintf("tcp://*:%d", repreqport))
return
}

View File

@ -56,6 +56,9 @@ linters:
- cyclop - cyclop
- containedctx - containedctx
- revive - revive
- nosnakecase
- exhaustruct
- depguard
issues: issues:
exclude-rules: exclude-rules:

View File

@ -1,3 +1,35 @@
# v0.10.2 - 2023/03/20
### New features
* Support DebugDOT option for debugging encoder ( #440 )
### Fix bugs
* Fix combination of embedding structure and omitempty option ( #442 )
# v0.10.1 - 2023/03/13
### Fix bugs
* Fix checkptr error for array decoder ( #415 )
* Fix added buffer size check when decoding key ( #430 )
* Fix handling of anonymous fields other than struct ( #431 )
* Fix to not optimize when lower conversion can't handle byte-by-byte ( #432 )
* Fix a problem that MarshalIndent does not work when UnorderedMap is specified ( #435 )
* Fix mapDecoder.DecodeStream() for empty objects containing whitespace ( #425 )
* Fix an issue that could not set the correct NextField for fields in the embedded structure ( #438 )
# v0.10.0 - 2022/11/29
### New features
* Support JSON Path ( #250 )
### Fix bugs
* Fix marshaler for map's key ( #409 )
# v0.9.11 - 2022/08/18 # v0.9.11 - 2022/08/18
### Fix bugs ### Fix bugs

View File

@ -30,7 +30,7 @@ golangci-lint: | $(BIN_DIR)
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \ GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
cd $$GOLANGCI_LINT_TMP_DIR; \ cd $$GOLANGCI_LINT_TMP_DIR; \
go mod init tmp; \ go mod init tmp; \
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0; \ GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2; \
rm -rf $$GOLANGCI_LINT_TMP_DIR; \ rm -rf $$GOLANGCI_LINT_TMP_DIR; \
} }

View File

@ -52,7 +52,7 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...
rctx.Option.Flag |= encoder.ContextOption rctx.Option.Flag |= encoder.ContextOption
rctx.Option.Context = ctx rctx.Option.Context = ctx
err := e.encodeWithOption(rctx, v, optFuncs...) err := e.encodeWithOption(rctx, v, optFuncs...) //nolint: contextcheck
encoder.ReleaseRuntimeContext(rctx) encoder.ReleaseRuntimeContext(rctx)
return err return err
@ -120,7 +120,7 @@ func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOption
optFunc(rctx.Option) optFunc(rctx.Option)
} }
buf, err := encode(rctx, v) buf, err := encode(rctx, v) //nolint: contextcheck
if err != nil { if err != nil {
encoder.ReleaseRuntimeContext(rctx) encoder.ReleaseRuntimeContext(rctx)
return nil, err return nil, err

View File

@ -19,7 +19,9 @@ type arrayDecoder struct {
} }
func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder { func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder {
zeroValue := *(*unsafe.Pointer)(unsafe_New(elemType)) // workaround to avoid checkptr errors. cannot use `*(*unsafe.Pointer)(unsafe_New(elemType))` directly.
zeroValuePtr := unsafe_New(elemType)
zeroValue := **(**unsafe.Pointer)(unsafe.Pointer(&zeroValuePtr))
return &arrayDecoder{ return &arrayDecoder{
valueDecoder: dec, valueDecoder: dec,
elemType: elemType, elemType: elemType,

View File

@ -88,7 +88,7 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
mapValue = makemap(d.mapType, 0) mapValue = makemap(d.mapType, 0)
} }
s.cursor++ s.cursor++
if s.equalChar('}') { if s.skipWhiteSpace() == '}' {
*(*unsafe.Pointer)(p) = mapValue *(*unsafe.Pointer)(p) = mapValue
s.cursor++ s.cursor++
return nil return nil

View File

@ -85,6 +85,7 @@ func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
} }
c, err := d.dec.Decode(ctx, cursor, depth, newptr) c, err := d.dec.Decode(ctx, cursor, depth, newptr)
if err != nil { if err != nil {
*(*unsafe.Pointer)(p) = nil
return 0, err return 0, err
} }
cursor = c cursor = c

View File

@ -51,6 +51,14 @@ func init() {
} }
} }
func toASCIILower(s string) string {
b := []byte(s)
for i := range b {
b[i] = largeToSmallTable[b[i]]
}
return string(b)
}
func newStructDecoder(structName, fieldName string, fieldMap map[string]*structFieldSet) *structDecoder { func newStructDecoder(structName, fieldName string, fieldMap map[string]*structFieldSet) *structDecoder {
return &structDecoder{ return &structDecoder{
fieldMap: fieldMap, fieldMap: fieldMap,
@ -91,6 +99,10 @@ func (d *structDecoder) tryOptimize() {
for k, v := range d.fieldMap { for k, v := range d.fieldMap {
key := strings.ToLower(k) key := strings.ToLower(k)
if key != k { if key != k {
if key != toASCIILower(k) {
d.isTriedOptimize = true
return
}
// already exists same key (e.g. Hello and HELLO has same lower case key // already exists same key (e.g. Hello and HELLO has same lower case key
if _, exists := conflicted[key]; exists { if _, exists := conflicted[key]; exists {
d.isTriedOptimize = true d.isTriedOptimize = true
@ -158,49 +170,53 @@ func (d *structDecoder) tryOptimize() {
} }
// decode from '\uXXXX' // decode from '\uXXXX'
func decodeKeyCharByUnicodeRune(buf []byte, cursor int64) ([]byte, int64) { func decodeKeyCharByUnicodeRune(buf []byte, cursor int64) ([]byte, int64, error) {
const defaultOffset = 4 const defaultOffset = 4
const surrogateOffset = 6 const surrogateOffset = 6
if cursor+defaultOffset >= int64(len(buf)) {
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
}
r := unicodeToRune(buf[cursor : cursor+defaultOffset]) r := unicodeToRune(buf[cursor : cursor+defaultOffset])
if utf16.IsSurrogate(r) { if utf16.IsSurrogate(r) {
cursor += defaultOffset cursor += defaultOffset
if cursor+surrogateOffset >= int64(len(buf)) || buf[cursor] != '\\' || buf[cursor+1] != 'u' { if cursor+surrogateOffset >= int64(len(buf)) || buf[cursor] != '\\' || buf[cursor+1] != 'u' {
return []byte(string(unicode.ReplacementChar)), cursor + defaultOffset - 1 return []byte(string(unicode.ReplacementChar)), cursor + defaultOffset - 1, nil
} }
cursor += 2 cursor += 2
r2 := unicodeToRune(buf[cursor : cursor+defaultOffset]) r2 := unicodeToRune(buf[cursor : cursor+defaultOffset])
if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar { if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar {
return []byte(string(r)), cursor + defaultOffset - 1 return []byte(string(r)), cursor + defaultOffset - 1, nil
} }
} }
return []byte(string(r)), cursor + defaultOffset - 1 return []byte(string(r)), cursor + defaultOffset - 1, nil
} }
func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64) { func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64, error) {
c := buf[cursor] c := buf[cursor]
cursor++ cursor++
switch c { switch c {
case '"': case '"':
return []byte{'"'}, cursor return []byte{'"'}, cursor, nil
case '\\': case '\\':
return []byte{'\\'}, cursor return []byte{'\\'}, cursor, nil
case '/': case '/':
return []byte{'/'}, cursor return []byte{'/'}, cursor, nil
case 'b': case 'b':
return []byte{'\b'}, cursor return []byte{'\b'}, cursor, nil
case 'f': case 'f':
return []byte{'\f'}, cursor return []byte{'\f'}, cursor, nil
case 'n': case 'n':
return []byte{'\n'}, cursor return []byte{'\n'}, cursor, nil
case 'r': case 'r':
return []byte{'\r'}, cursor return []byte{'\r'}, cursor, nil
case 't': case 't':
return []byte{'\t'}, cursor return []byte{'\t'}, cursor, nil
case 'u': case 'u':
return decodeKeyCharByUnicodeRune(buf, cursor) return decodeKeyCharByUnicodeRune(buf, cursor)
} }
return nil, cursor return nil, cursor, nil
} }
func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) { func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) {
@ -242,7 +258,10 @@ func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64,
return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor)
case '\\': case '\\':
cursor++ cursor++
chars, nextCursor := decodeKeyCharByEscapedChar(buf, cursor) chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor)
if err != nil {
return 0, nil, err
}
for _, c := range chars { for _, c := range chars {
curBit &= bitmap[keyIdx][largeToSmallTable[c]] curBit &= bitmap[keyIdx][largeToSmallTable[c]]
if curBit == 0 { if curBit == 0 {
@ -305,7 +324,10 @@ func decodeKeyByBitmapUint16(d *structDecoder, buf []byte, cursor int64) (int64,
return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor) return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor)
case '\\': case '\\':
cursor++ cursor++
chars, nextCursor := decodeKeyCharByEscapedChar(buf, cursor) chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor)
if err != nil {
return 0, nil, err
}
for _, c := range chars { for _, c := range chars {
curBit &= bitmap[keyIdx][largeToSmallTable[c]] curBit &= bitmap[keyIdx][largeToSmallTable[c]]
if curBit == 0 { if curBit == 0 {

View File

@ -147,7 +147,7 @@ func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int
return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path") return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
} }
func unquoteBytes(s []byte) (t []byte, ok bool) { func unquoteBytes(s []byte) (t []byte, ok bool) { //nolint: nonamedreturns
length := len(s) length := len(s)
if length < 2 || s[0] != '"' || s[length-1] != '"' { if length < 2 || s[0] != '"' || s[length-1] != '"' {
return return

View File

@ -397,7 +397,10 @@ func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *
func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode { func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode {
// firstField is special StructHead operation for anonymous structure. // firstField is special StructHead operation for anonymous structure.
// So, StructHead's next operation is truly struct head operation. // So, StructHead's next operation is truly struct head operation.
lastField := firstField.Next for firstField.Op == OpStructHead || firstField.Op == OpStructField {
firstField = firstField.Next
}
lastField := firstField
for lastField.NextField != nil { for lastField.NextField != nil {
lastField = lastField.NextField lastField = lastField.NextField
} }
@ -437,11 +440,6 @@ func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
} }
if isEndField { if isEndField {
endField := fieldCodes.Last() endField := fieldCodes.Last()
if isEmbeddedStruct(field) {
firstField.End = endField
lastField := c.lastAnonymousFieldCode(firstField)
lastField.NextField = endField
}
if len(codes) > 0 { if len(codes) > 0 {
codes.First().End = endField codes.First().End = endField
} else { } else {
@ -698,7 +696,15 @@ func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) O
Indent: ctx.indent, Indent: ctx.indent,
} }
codes.Last().Next = end codes.Last().Next = end
codes.First().NextField = end code := codes.First()
for code.Op == OpStructField || code.Op == OpStructHead {
code = code.Next
}
for code.NextField != nil {
code = code.NextField
}
code.NextField = end
codes = codes.Add(end) codes = codes.Add(end)
ctx.incOpcodeIndex() ctx.incOpcodeIndex()
return codes return codes

View File

@ -213,8 +213,8 @@ func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, e
dst = append(dst, src[start:cursor]...) dst = append(dst, src[start:cursor]...)
dst = append(dst, `\u202`...) dst = append(dst, `\u202`...)
dst = append(dst, hex[src[cursor+2]&0xF]) dst = append(dst, hex[src[cursor+2]&0xF])
cursor += 2
start = cursor + 3 start = cursor + 3
cursor += 2
} }
} }
switch c { switch c {

View File

@ -480,7 +480,7 @@ func (c *Compiler) mapCode(typ *runtime.Type) (*MapCode, error) {
func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) { func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
switch { switch {
case c.isPtrMarshalJSONType(typ): case c.implementsMarshalJSONType(typ) || c.implementsMarshalJSONType(runtime.PtrTo(typ)):
return c.marshalJSONCode(typ) return c.marshalJSONCode(typ)
case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType): case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType):
return c.marshalTextCode(typ) return c.marshalTextCode(typ)
@ -617,6 +617,13 @@ func (c *Compiler) structCode(typ *runtime.Type, isPtr bool) (*StructCode, error
return code, nil return code, nil
} }
func toElemType(t *runtime.Type) *runtime.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTag, isPtr, isOnlyOneFirstField bool) (*StructFieldCode, error) { func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTag, isPtr, isOnlyOneFirstField bool) (*StructFieldCode, error) {
field := tag.Field field := tag.Field
fieldType := runtime.Type2RType(field.Type) fieldType := runtime.Type2RType(field.Type)
@ -626,7 +633,7 @@ func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTa
key: tag.Key, key: tag.Key,
tag: tag, tag: tag,
offset: field.Offset, offset: field.Offset,
isAnonymous: field.Anonymous && !tag.IsTaggedKey, isAnonymous: field.Anonymous && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct,
isTaggedKey: tag.IsTaggedKey, isTaggedKey: tag.IsTaggedKey,
isNilableType: c.isNilableType(fieldType), isNilableType: c.isNilableType(fieldType),
isNilCheck: true, isNilCheck: true,

View File

@ -1,3 +1,27 @@
// This files's processing codes are inspired by https://github.com/segmentio/encoding.
// The license notation is as follows.
//
// # MIT License
//
// Copyright (c) 2019 Segment.io, Inc.
//
// 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.
package encoder package encoder
import ( import (

View File

@ -1,7 +1,9 @@
package encoder package encoder
import ( import (
"bytes"
"fmt" "fmt"
"sort"
"strings" "strings"
"unsafe" "unsafe"
@ -555,6 +557,87 @@ func (c *Opcode) Dump() string {
return strings.Join(codes, "\n") return strings.Join(codes, "\n")
} }
func (c *Opcode) DumpDOT() string {
type edge struct {
from, to *Opcode
label string
weight int
}
var edges []edge
b := &bytes.Buffer{}
fmt.Fprintf(b, "digraph \"%p\" {\n", c.Type)
fmt.Fprintln(b, "mclimit=1.5;\nrankdir=TD;\nordering=out;\nnode[shape=box];")
for code := c; !code.IsEnd(); {
label := code.Op.String()
fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, label)
if p := code.Next; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "Next",
weight: 10,
})
}
if p := code.NextField; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "NextField",
weight: 2,
})
}
if p := code.End; p != nil {
edges = append(edges, edge{
from: code,
to: p,
label: "End",
weight: 1,
})
}
if p := code.Jmp; p != nil {
edges = append(edges, edge{
from: code,
to: p.Code,
label: "Jmp",
weight: 1,
})
}
switch code.Op.CodeType() {
case CodeSliceHead:
code = code.Next
case CodeMapHead:
code = code.Next
case CodeArrayElem, CodeSliceElem:
code = code.End
case CodeMapKey:
code = code.End
case CodeMapValue:
code = code.Next
case CodeMapEnd:
code = code.Next
case CodeStructField:
code = code.Next
case CodeStructEnd:
code = code.Next
default:
code = code.Next
}
if code.IsEnd() {
fmt.Fprintf(b, "\"%p\" [label=%q];\n", code, code.Op.String())
}
}
sort.Slice(edges, func(i, j int) bool {
return edges[i].to.DisplayIdx < edges[j].to.DisplayIdx
})
for _, e := range edges {
fmt.Fprintf(b, "\"%p\" -> \"%p\" [label=%q][weight=%d];\n", e.from, e.to, e.label, e.weight)
}
fmt.Fprint(b, "}")
return b.String()
}
func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
idx := opcodeOffset(ctx.ptrIndex) idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex() ctx.incPtrIndex()

View File

@ -23,6 +23,7 @@ type Option struct {
ColorScheme *ColorScheme ColorScheme *ColorScheme
Context context.Context Context context.Context
DebugOut io.Writer DebugOut io.Writer
DebugDOTOut io.WriteCloser
} }
type EncodeFormat struct { type EncodeFormat struct {

View File

@ -1,3 +1,27 @@
// This files's string processing codes are inspired by https://github.com/segmentio/encoding.
// The license notation is as follows.
//
// # MIT License
//
// Copyright (c) 2019 Segment.io, Inc.
//
// 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.
package encoder package encoder
import ( import (

View File

@ -2,6 +2,7 @@ package vm
import ( import (
"fmt" "fmt"
"io"
"github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/encoder"
) )
@ -14,6 +15,11 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
} else { } else {
code = codeSet.NoescapeKeyCode code = codeSet.NoescapeKeyCode
} }
if wc := ctx.Option.DebugDOTOut; wc != nil {
_, _ = io.WriteString(wc, code.DumpDOT())
wc.Close()
ctx.Option.DebugDOTOut = nil
}
if err := recover(); err != nil { if err := recover(); err != nil {
w := ctx.Option.DebugOut w := ctx.Option.DebugOut

View File

@ -189,7 +189,7 @@ func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte {
} }
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ':', ' ') return append(b[:len(b)-2], ':', ' ')
} }
func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte { func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte {
@ -229,8 +229,9 @@ func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte {
func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte {
last := len(b) - 1 last := len(b) - 1
b[last] = '\n' // replace comma to newline
b = appendIndent(ctx, b, code.Indent-1) b[last-1] = '\n'
b = appendIndent(ctx, b[:last], code.Indent)
return append(b, '}', ',', '\n') return append(b, '}', ',', '\n')
} }

View File

@ -133,7 +133,7 @@ func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte {
} }
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte { func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ':', ' ') return append(b[:len(b)-2], ':', ' ')
} }
func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte { func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte {
@ -173,8 +173,9 @@ func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte {
func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte { func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte {
last := len(b) - 1 last := len(b) - 1
b[last] = '\n' // replace comma to newline
b = appendIndent(ctx, b, code.Indent-1) b[last-1] = '\n'
b = appendIndent(ctx, b[:last], code.Indent)
return append(b, '}', ',', '\n') return append(b, '}', ',', '\n')
} }

View File

@ -252,7 +252,6 @@ func IfaceIndir(*Type) bool
//go:noescape //go:noescape
func RType2Type(t *Type) reflect.Type func RType2Type(t *Type) reflect.Type
//go:nolint structcheck
type emptyInterface struct { type emptyInterface struct {
_ *Type _ *Type
ptr unsafe.Pointer ptr unsafe.Pointer

View File

@ -89,31 +89,31 @@ type UnmarshalerContext interface {
// //
// Examples of struct field tags and their meanings: // Examples of struct field tags and their meanings:
// //
// // Field appears in JSON as key "myName". // // Field appears in JSON as key "myName".
// Field int `json:"myName"` // Field int `json:"myName"`
// //
// // Field appears in JSON as key "myName" and // // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty, // // the field is omitted from the object if its value is empty,
// // as defined above. // // as defined above.
// Field int `json:"myName,omitempty"` // Field int `json:"myName,omitempty"`
// //
// // Field appears in JSON as key "Field" (the default), but // // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty. // // the field is skipped if empty.
// // Note the leading comma. // // Note the leading comma.
// Field int `json:",omitempty"` // Field int `json:",omitempty"`
// //
// // Field is ignored by this package. // // Field is ignored by this package.
// Field int `json:"-"` // Field int `json:"-"`
// //
// // Field appears in JSON as key "-". // // Field appears in JSON as key "-".
// Field int `json:"-,"` // Field int `json:"-,"`
// //
// The "string" option signals that a field is stored as JSON inside a // The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point, // JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used // integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs: // when communicating with JavaScript programs:
// //
// Int64String int64 `json:",string"` // Int64String int64 `json:",string"`
// //
// The key name will be used if it's a non-empty string consisting of // The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, and ASCII punctuation except quotation // only Unicode letters, digits, and ASCII punctuation except quotation
@ -166,7 +166,6 @@ type UnmarshalerContext interface {
// JSON cannot represent cyclic data structures and Marshal does not // JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in // handle them. Passing cyclic structures to Marshal will result in
// an infinite recursion. // an infinite recursion.
//
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
return MarshalWithOption(v) return MarshalWithOption(v)
} }
@ -264,14 +263,13 @@ func MarshalIndentWithOption(v interface{}, prefix, indent string, optFuncs ...E
// //
// The JSON null value unmarshals into an interface, map, pointer, or slice // The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean // by setting that Go value to nil. Because null is often used in JSON to mean
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect // “not present,” unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error. // on the value and produces no error.
// //
// When unmarshaling quoted strings, invalid UTF-8 or // When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error. // invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement // Instead, they are replaced by the Unicode replacement
// character U+FFFD. // character U+FFFD.
//
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
return unmarshal(data, v) return unmarshal(data, v)
} }
@ -299,7 +297,6 @@ func UnmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc)
// Number, for JSON numbers // Number, for JSON numbers
// string, for JSON string literals // string, for JSON string literals
// nil, for JSON null // nil, for JSON null
//
type Token = json.Token type Token = json.Token
// A Number represents a JSON number literal. // A Number represents a JSON number literal.

View File

@ -48,6 +48,13 @@ func DebugWith(w io.Writer) EncodeOptionFunc {
} }
} }
// DebugDOT sets the destination to write opcodes graph.
func DebugDOT(w io.WriteCloser) EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.DebugDOTOut = w
}
}
// Colorize add an identifier for coloring to the string of the encoded result. // Colorize add an identifier for coloring to the string of the encoded result.
func Colorize(scheme *ColorScheme) EncodeOptionFunc { func Colorize(scheme *ColorScheme) EncodeOptionFunc {
return func(opt *EncodeOption) { return func(opt *EncodeOption) {

View File

@ -1,21 +0,0 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.14.x
- 1.15.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

View File

@ -1,5 +1,137 @@
# Changelog # Changelog
## v4.12.0 - 2024-04-15
**Security**
* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625
**Enhancements**
* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554
* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579
* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581
* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584
* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568
* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588
* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574
* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461
* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603
* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596
* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595
* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604
* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605
* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606
* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550
* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607
* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608
* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611
* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616
* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624
## v4.11.4 - 2023-12-20
**Security**
* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562)
**Enhancements**
* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563)
* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543)
## v4.11.3 - 2023-11-07
**Security**
* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541)
**Enhancements**
* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540)
* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537)
* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536)
## v4.11.2 - 2023-10-11
**Security**
* Bump golang.org/x/net to prevent CVE-2023-39325 / CVE-2023-44487 HTTP/2 Rapid Reset Attack [#2527](https://github.com/labstack/echo/pull/2527)
* fix(sec): randomString bias introduced by #2490 [#2492](https://github.com/labstack/echo/pull/2492)
* CSRF/RequestID mw: switch math/random usage to crypto/random [#2490](https://github.com/labstack/echo/pull/2490)
**Enhancements**
* Delete unused context in body_limit.go [#2483](https://github.com/labstack/echo/pull/2483)
* Use Go 1.21 in CI [#2505](https://github.com/labstack/echo/pull/2505)
* Fix some typos [#2511](https://github.com/labstack/echo/pull/2511)
* Allow CORS middleware to send Access-Control-Max-Age: 0 [#2518](https://github.com/labstack/echo/pull/2518)
* Bump dependancies [#2522](https://github.com/labstack/echo/pull/2522)
## v4.11.1 - 2023-07-16
**Fixes**
* Fix `Gzip` middleware not sending response code for no content responses (404, 301/302 redirects etc) [#2481](https://github.com/labstack/echo/pull/2481)
## v4.11.0 - 2023-07-14
**Fixes**
* Fixes the proxy middleware concurrency issue of calling the Next() proxy target on Round Robin Balancer [#2409](https://github.com/labstack/echo/pull/2409)
* Fix `group.RouteNotFound` not working when group has attached middlewares [#2411](https://github.com/labstack/echo/pull/2411)
* Fix global error handler return error message when message is an error [#2456](https://github.com/labstack/echo/pull/2456)
* Do not use global timeNow variables [#2477](https://github.com/labstack/echo/pull/2477)
**Enhancements**
* Added a optional config variable to disable centralized error handler in recovery middleware [#2410](https://github.com/labstack/echo/pull/2410)
* refactor: use `strings.ReplaceAll` directly [#2424](https://github.com/labstack/echo/pull/2424)
* Add support for Go1.20 `http.rwUnwrapper` to Response struct [#2425](https://github.com/labstack/echo/pull/2425)
* Check whether is nil before invoking centralized error handling [#2429](https://github.com/labstack/echo/pull/2429)
* Proper colon support in `echo.Reverse` method [#2416](https://github.com/labstack/echo/pull/2416)
* Fix misuses of a vs an in documentation comments [#2436](https://github.com/labstack/echo/pull/2436)
* Add link to slog.Handler library for Echo logging into README.md [#2444](https://github.com/labstack/echo/pull/2444)
* In proxy middleware Support retries of failed proxy requests [#2414](https://github.com/labstack/echo/pull/2414)
* gofmt fixes to comments [#2452](https://github.com/labstack/echo/pull/2452)
* gzip response only if it exceeds a minimal length [#2267](https://github.com/labstack/echo/pull/2267)
* Upgrade packages [#2475](https://github.com/labstack/echo/pull/2475)
## v4.10.2 - 2023-02-22
**Security**
* `filepath.Clean` behaviour has changed in Go 1.20 - adapt to it [#2406](https://github.com/labstack/echo/pull/2406)
* Add `middleware.CORSConfig.UnsafeWildcardOriginWithAllowCredentials` to make UNSAFE usages of wildcard origin + allow cretentials less likely [#2405](https://github.com/labstack/echo/pull/2405)
**Enhancements**
* Add more HTTP error values [#2277](https://github.com/labstack/echo/pull/2277)
## v4.10.1 - 2023-02-19
**Security**
* Upgrade deps due to the latest golang.org/x/net vulnerability [#2402](https://github.com/labstack/echo/pull/2402)
**Enhancements**
* Add new JWT repository to the README [#2377](https://github.com/labstack/echo/pull/2377)
* Return an empty string for ctx.path if there is no registered path [#2385](https://github.com/labstack/echo/pull/2385)
* Add context timeout middleware [#2380](https://github.com/labstack/echo/pull/2380)
* Update link to jaegertracing [#2394](https://github.com/labstack/echo/pull/2394)
## v4.10.0 - 2022-12-27 ## v4.10.0 - 2022-12-27
**Security** **Security**

View File

@ -31,6 +31,6 @@ benchmark: ## Run benchmarks
help: ## Display this help screen help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.17" goversion ?= "1.19"
test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17 test_version: ## Run tests inside Docker with given version (defaults to 1.19 oldest supported). Example: make test_version goversion=1.19
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check" @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

View File

@ -3,26 +3,24 @@
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge) [![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)](https://pkg.go.dev/github.com/labstack/echo/v4) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo) [![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) [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/labstack/echo/echo.yml?style=flat-square)](https://github.com/labstack/echo/actions)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo) [![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions) [![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) [![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) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Supported Go versions ## Echo
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions. High performance, extensible, minimalist Go web framework.
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules). * [Official website](https://echo.labstack.com)
Therefore a Go version capable of understanding /vN suffixed imports is required: * [Quick start](https://echo.labstack.com/docs/quick-start)
* [Middlewares](https://echo.labstack.com/docs/category/middleware)
Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended ### Feature Overview
way of using Echo going forward.
For older versions, please use the latest v3 tag.
## Feature Overview
- Optimized HTTP router which smartly prioritize routes - Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs - Build robust and scalable RESTful APIs
@ -38,6 +36,18 @@ For older versions, please use the latest v3 tag.
- Automatic TLS via Lets Encrypt - Automatic TLS via Lets Encrypt
- HTTP/2 support - HTTP/2 support
## Sponsors
<div>
<a href="https://encore.dev" style="display: inline-flex; align-items: center; gap: 10px">
<img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" height="28px" alt="encore icon"></img>
<b>Encore the platform for building Go-based cloud backends</b>
</a>
</div>
<br/>
Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
## Benchmarks ## Benchmarks
Date: 2020/11/11<br> Date: 2020/11/11<br>
@ -57,6 +67,7 @@ The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
// go get github.com/labstack/echo/{version} // go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v4 go get github.com/labstack/echo/v4
``` ```
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
### Example ### Example
@ -90,25 +101,33 @@ func hello(c echo.Context) error {
} }
``` ```
# Third-party middlewares # Official middleware repositories
| Repository | Description | Following list of middleware is maintained by Echo team.
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares | | Repository | Description |
| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator | |------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. | | [github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt) | [JWT](https://github.com/golang-jwt/jwt) middleware |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. | | [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](https://github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. | # Third-party middleware repositories
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code | Be careful when adding 3rd party middleware. Echo teams does not have time or manpower to guarantee safety and quality
of middlewares in this list.
| Repository | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
| [github.com/samber/slog-echo](https://github.com/samber/slog-echo) | Go [slog](https://pkg.go.dev/golang.org/x/exp/slog) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code |
Please send a PR to add your own library here. Please send a PR to add your own library here.
## Help
- [Forum](https://github.com/labstack/echo/discussions)
## Contribute ## Contribute
**Use issues for everything** **Use issues for everything**

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -11,23 +14,28 @@ import (
"strings" "strings"
) )
type ( // Binder is the interface that wraps the Bind method.
// Binder is the interface that wraps the Bind method. type Binder interface {
Binder interface { Bind(i interface{}, c Context) error
Bind(i interface{}, c Context) error }
}
// DefaultBinder is the default implementation of the Binder interface. // DefaultBinder is the default implementation of the Binder interface.
DefaultBinder struct{} type DefaultBinder struct{}
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method. // BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
// Types that don't implement this, but do implement encoding.TextUnmarshaler // Types that don't implement this, but do implement encoding.TextUnmarshaler
// will use that interface instead. // will use that interface instead.
BindUnmarshaler interface { type BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param. // UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error UnmarshalParam(param string) error
} }
)
// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
// for `a` following slice `["1", "2"] will be passed to unmarshaller.
type bindMultipleUnmarshaler interface {
UnmarshalParams(params []string) error
}
// BindPathParams binds path params to bindable object // BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error { func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
@ -114,7 +122,7 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body. // Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues. // For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670) // The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
method := c.Request().Method method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead { if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err = b.BindQueryParams(c, i); err != nil { if err = b.BindQueryParams(c, i); err != nil {
return err return err
@ -131,10 +139,29 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
typ := reflect.TypeOf(destination).Elem() typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem() val := reflect.ValueOf(destination).Elem()
// Map // Support binding to limited Map destinations:
if typ.Kind() == reflect.Map { // - map[string][]string,
// - map[string]string <-- (binds first value from data slice)
// - map[string]interface{}
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
k := typ.Elem().Kind()
isElemInterface := k == reflect.Interface
isElemString := k == reflect.String
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
return nil
}
if val.IsNil() {
val.Set(reflect.MakeMap(typ))
}
for k, v := range data { for k, v := range data {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) if isElemString {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
} else {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}
} }
return nil return nil
} }
@ -161,14 +188,14 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
} }
structFieldKind := structField.Kind() structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag) inputFieldName := typeField.Tag.Get(tag)
if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" { if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error // if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field") return errors.New("query/param/form tags are not allowed with anonymous struct field")
} }
if inputFieldName == "" { if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags). // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
// structs that implement BindUnmarshaler are binded only when they have explicit tag // structs that implement BindUnmarshaler are bound only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct { if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil { if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err return err
@ -197,27 +224,46 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
continue continue
} }
// Call this first, in case we're dealing with an alias to an array type // NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int`
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok { // but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` .
// try unmarshalling first, in case we're dealing with an alias to an array type
if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
numElems := len(inputValue) if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField); ok {
if structFieldKind == reflect.Slice && numElems > 0 { if err != nil {
return err
}
continue
}
// we could be dealing with pointer to slice `*[]string` so dereference it. There are wierd OpenAPI generators
// that could create struct fields like that.
if structFieldKind == reflect.Pointer {
structFieldKind = structField.Elem().Kind()
structField = structField.Elem()
}
if structFieldKind == reflect.Slice {
sliceOf := structField.Type().Elem().Kind() sliceOf := structField.Type().Elem().Kind()
numElems := len(inputValue)
slice := reflect.MakeSlice(structField.Type(), numElems, numElems) slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ { for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil { if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err return err
} }
} }
val.Field(i).Set(slice) structField.Set(slice)
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { continue
return err }
if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
return err
} }
} }
return nil return nil
@ -225,7 +271,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { 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 // But also call it here, in case we're dealing with an array of BindUnmarshalers
if ok, err := unmarshalField(valueKind, val, structField); ok { if ok, err := unmarshalInputToField(valueKind, val, structField); ok {
return err return err
} }
@ -266,35 +312,41 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
return nil return nil
} }
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) { func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) {
switch valueKind { if valueKind == reflect.Ptr {
case reflect.Ptr: if field.IsNil() {
return unmarshalFieldPtr(val, field) field.Set(reflect.New(field.Type().Elem()))
default: }
return unmarshalFieldNonPtr(val, field) field = field.Elem()
} }
fieldIValue := field.Addr().Interface()
unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler)
if !ok {
return false, nil
}
return true, unmarshaler.UnmarshalParams(values)
} }
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
fieldIValue := field.Addr().Interface() if valueKind == reflect.Ptr {
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok { if field.IsNil() {
return true, unmarshaler.UnmarshalParam(value) field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
} }
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
return true, unmarshaler.UnmarshalText([]byte(value)) fieldIValue := field.Addr().Interface()
switch unmarshaler := fieldIValue.(type) {
case BindUnmarshaler:
return true, unmarshaler.UnmarshalParam(val)
case encoding.TextUnmarshaler:
return true, unmarshaler.UnmarshalText([]byte(val))
} }
return false, nil 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 { func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" { if value == "" {
value = "0" value = "0"

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -1236,7 +1239,7 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 // Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Second) return b.unixTime(sourceParam, dest, false, time.Second)
} }
@ -1247,7 +1250,7 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 // Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Second) return b.unixTime(sourceParam, dest, true, time.Second)
} }
@ -1257,7 +1260,7 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00 // Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Millisecond) return b.unixTime(sourceParam, dest, false, time.Millisecond)
} }
@ -1268,7 +1271,7 @@ func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueB
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00 // Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Millisecond) return b.unixTime(sourceParam, dest, true, time.Millisecond)
} }
@ -1280,8 +1283,8 @@ func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *Va
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 // Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. // - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Nanosecond) return b.unixTime(sourceParam, dest, false, time.Nanosecond)
} }
@ -1294,8 +1297,8 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 // Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
// //
// Note: // Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal // - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. // - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Nanosecond) return b.unixTime(sourceParam, dest, true, time.Nanosecond)
} }
@ -1323,7 +1326,7 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
case time.Second: case time.Second:
*dest = time.Unix(n, 0) *dest = time.Unix(n, 0)
case time.Millisecond: case time.Millisecond:
*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows *dest = time.UnixMilli(n)
case time.Nanosecond: case time.Nanosecond:
*dest = time.Unix(0, n) *dest = time.Unix(0, n)
} }

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -13,204 +16,216 @@ import (
"sync" "sync"
) )
type ( // Context represents the context of the current HTTP request. It holds request and
// Context represents the context of the current HTTP request. It holds request and // response objects, path, path parameters, data and registered handler.
// response objects, path, path parameters, data and registered handler. type Context interface {
Context interface { // Request returns `*http.Request`.
// Request returns `*http.Request`. Request() *http.Request
Request() *http.Request
// SetRequest sets `*http.Request`. // SetRequest sets `*http.Request`.
SetRequest(r *http.Request) SetRequest(r *http.Request)
// SetResponse sets `*Response`. // SetResponse sets `*Response`.
SetResponse(r *Response) SetResponse(r *Response)
// Response returns `*Response`. // Response returns `*Response`.
Response() *Response Response() *Response
// IsTLS returns true if HTTP connection is TLS otherwise false. // IsTLS returns true if HTTP connection is TLS otherwise false.
IsTLS() bool IsTLS() bool
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false. // IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
IsWebSocket() bool IsWebSocket() bool
// Scheme returns the HTTP protocol scheme, `http` or `https`. // Scheme returns the HTTP protocol scheme, `http` or `https`.
Scheme() string Scheme() string
// RealIP returns the client's network address based on `X-Forwarded-For` // RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header. // or `X-Real-IP` request header.
// The behavior can be configured using `Echo#IPExtractor`. // The behavior can be configured using `Echo#IPExtractor`.
RealIP() string RealIP() string
// Path returns the registered path for the handler. // Path returns the registered path for the handler.
Path() string Path() string
// SetPath sets the registered path for the handler. // SetPath sets the registered path for the handler.
SetPath(p string) SetPath(p string)
// Param returns path parameter by name. // Param returns path parameter by name.
Param(name string) string Param(name string) string
// ParamNames returns path parameter names. // ParamNames returns path parameter names.
ParamNames() []string ParamNames() []string
// SetParamNames sets path parameter names. // SetParamNames sets path parameter names.
SetParamNames(names ...string) SetParamNames(names ...string)
// ParamValues returns path parameter values. // ParamValues returns path parameter values.
ParamValues() []string ParamValues() []string
// SetParamValues sets path parameter values. // SetParamValues sets path parameter values.
SetParamValues(values ...string) SetParamValues(values ...string)
// QueryParam returns the query param for the provided name. // QueryParam returns the query param for the provided name.
QueryParam(name string) string QueryParam(name string) string
// QueryParams returns the query parameters as `url.Values`. // QueryParams returns the query parameters as `url.Values`.
QueryParams() url.Values QueryParams() url.Values
// QueryString returns the URL query string. // QueryString returns the URL query string.
QueryString() string QueryString() string
// FormValue returns the form field value for the provided name. // FormValue returns the form field value for the provided name.
FormValue(name string) string FormValue(name string) string
// FormParams returns the form parameters as `url.Values`. // FormParams returns the form parameters as `url.Values`.
FormParams() (url.Values, error) FormParams() (url.Values, error)
// FormFile returns the multipart form file for the provided name. // FormFile returns the multipart form file for the provided name.
FormFile(name string) (*multipart.FileHeader, error) FormFile(name string) (*multipart.FileHeader, error)
// MultipartForm returns the multipart form. // MultipartForm returns the multipart form.
MultipartForm() (*multipart.Form, error) MultipartForm() (*multipart.Form, error)
// Cookie returns the named cookie provided in the request. // Cookie returns the named cookie provided in the request.
Cookie(name string) (*http.Cookie, error) Cookie(name string) (*http.Cookie, error)
// SetCookie adds a `Set-Cookie` header in HTTP response. // SetCookie adds a `Set-Cookie` header in HTTP response.
SetCookie(cookie *http.Cookie) SetCookie(cookie *http.Cookie)
// Cookies returns the HTTP cookies sent with the request. // Cookies returns the HTTP cookies sent with the request.
Cookies() []*http.Cookie Cookies() []*http.Cookie
// Get retrieves data from the context. // Get retrieves data from the context.
Get(key string) interface{} Get(key string) interface{}
// Set saves data in the context. // Set saves data in the context.
Set(key string, val interface{}) Set(key string, val interface{})
// Bind binds the request body into provided type `i`. The default binder // Bind binds path params, query params and the request body into provided type `i`. The default binder
// does it based on Content-Type header. // binds body based on Content-Type header.
Bind(i interface{}) error Bind(i interface{}) error
// Validate validates provided `i`. It is usually called after `Context#Bind()`. // Validate validates provided `i`. It is usually called after `Context#Bind()`.
// Validator must be registered using `Echo#Validator`. // Validator must be registered using `Echo#Validator`.
Validate(i interface{}) error Validate(i interface{}) error
// Render renders a template with data and sends a text/html response with status // Render renders a template with data and sends a text/html response with status
// code. Renderer must be registered using `Echo.Renderer`. // code. Renderer must be registered using `Echo.Renderer`.
Render(code int, name string, data interface{}) error Render(code int, name string, data interface{}) error
// HTML sends an HTTP response with status code. // HTML sends an HTTP response with status code.
HTML(code int, html string) error HTML(code int, html string) error
// HTMLBlob sends an HTTP blob response with status code. // HTMLBlob sends an HTTP blob response with status code.
HTMLBlob(code int, b []byte) error HTMLBlob(code int, b []byte) error
// String sends a string response with status code. // String sends a string response with status code.
String(code int, s string) error String(code int, s string) error
// JSON sends a JSON response with status code. // JSON sends a JSON response with status code.
JSON(code int, i interface{}) error JSON(code int, i interface{}) error
// JSONPretty sends a pretty-print JSON with status code. // JSONPretty sends a pretty-print JSON with status code.
JSONPretty(code int, i interface{}, indent string) error JSONPretty(code int, i interface{}, indent string) error
// JSONBlob sends a JSON blob response with status code. // JSONBlob sends a JSON blob response with status code.
JSONBlob(code int, b []byte) error JSONBlob(code int, b []byte) error
// JSONP sends a JSONP response with status code. It uses `callback` to construct // JSONP sends a JSONP response with status code. It uses `callback` to construct
// the JSONP payload. // the JSONP payload.
JSONP(code int, callback string, i interface{}) error JSONP(code int, callback string, i interface{}) error
// JSONPBlob sends a JSONP blob response with status code. It uses `callback` // JSONPBlob sends a JSONP blob response with status code. It uses `callback`
// to construct the JSONP payload. // to construct the JSONP payload.
JSONPBlob(code int, callback string, b []byte) error JSONPBlob(code int, callback string, b []byte) error
// XML sends an XML response with status code. // XML sends an XML response with status code.
XML(code int, i interface{}) error XML(code int, i interface{}) error
// XMLPretty sends a pretty-print XML with status code. // XMLPretty sends a pretty-print XML with status code.
XMLPretty(code int, i interface{}, indent string) error XMLPretty(code int, i interface{}, indent string) error
// XMLBlob sends an XML blob response with status code. // XMLBlob sends an XML blob response with status code.
XMLBlob(code int, b []byte) error XMLBlob(code int, b []byte) error
// Blob sends a blob response with status code and content type. // Blob sends a blob response with status code and content type.
Blob(code int, contentType string, b []byte) error Blob(code int, contentType string, b []byte) error
// Stream sends a streaming response with status code and content type. // Stream sends a streaming response with status code and content type.
Stream(code int, contentType string, r io.Reader) error Stream(code int, contentType string, r io.Reader) error
// File sends a response with the content of the file. // File sends a response with the content of the file.
File(file string) error File(file string) error
// Attachment sends a response as attachment, prompting client to save the // Attachment sends a response as attachment, prompting client to save the
// file. // file.
Attachment(file string, name string) error Attachment(file string, name string) error
// Inline sends a response as inline, opening the file in the browser. // Inline sends a response as inline, opening the file in the browser.
Inline(file string, name string) error Inline(file string, name string) error
// NoContent sends a response with no body and a status code. // NoContent sends a response with no body and a status code.
NoContent(code int) error NoContent(code int) error
// Redirect redirects the request to a provided URL with status code. // Redirect redirects the request to a provided URL with status code.
Redirect(code int, url string) error Redirect(code int, url string) error
// Error invokes the registered global HTTP error handler. Generally used by middleware. // Error invokes the registered global HTTP error handler. Generally used by middleware.
// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
// middlewares up in chain can not change Response status code or Response body anymore. // middlewares up in chain can not change Response status code or Response body anymore.
// //
// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that. // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
Error(err error) Error(err error)
// Handler returns the matched handler by router. // Handler returns the matched handler by router.
Handler() HandlerFunc Handler() HandlerFunc
// SetHandler sets the matched handler by router. // SetHandler sets the matched handler by router.
SetHandler(h HandlerFunc) SetHandler(h HandlerFunc)
// Logger returns the `Logger` instance. // Logger returns the `Logger` instance.
Logger() Logger Logger() Logger
// SetLogger Set the logger // SetLogger Set the logger
SetLogger(l Logger) SetLogger(l Logger)
// Echo returns the `Echo` instance. // Echo returns the `Echo` instance.
Echo() *Echo Echo() *Echo
// Reset resets the context after request completes. It must be called along // Reset resets the context after request completes. It must be called along
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
// See `Echo#ServeHTTP()` // See `Echo#ServeHTTP()`
Reset(r *http.Request, w http.ResponseWriter) Reset(r *http.Request, w http.ResponseWriter)
} }
context struct { type context struct {
request *http.Request request *http.Request
response *Response response *Response
path string query url.Values
pnames []string echo *Echo
pvalues []string logger Logger
query url.Values
handler HandlerFunc store Map
store Map lock sync.RWMutex
echo *Echo
logger Logger // following fields are set by Router
lock sync.RWMutex
} // path is route path that Router matched. It is empty string where there is no route match.
) // Route registered with RouteNotFound is considered as a match and path therefore is not empty.
path string
// pnames length is tied to param count for the matched route
pnames []string
// Usually echo.Echo is sizing pvalues but there could be user created middlewares that decide to
// overwrite parameter by calling SetParamNames + SetParamValues.
// When echo.Echo allocated that slice it length/capacity is tied to echo.Echo.maxParam value.
//
// It is important that pvalues size is always equal or bigger to pnames length.
pvalues []string
handler HandlerFunc
}
const ( const (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain. // ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
@ -329,13 +344,9 @@ func (c *context) SetParamNames(names ...string) {
c.pnames = names c.pnames = names
l := len(names) l := len(names)
if *c.echo.maxParam < l {
*c.echo.maxParam = l
}
if len(c.pvalues) < l { if len(c.pvalues) < l {
// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them, // Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
// probably those values will be overriden in a Context#SetParamValues // probably those values will be overridden in a Context#SetParamValues
newPvalues := make([]string, l) newPvalues := make([]string, l)
copy(newPvalues, c.pvalues) copy(newPvalues, c.pvalues)
c.pvalues = newPvalues c.pvalues = newPvalues
@ -347,11 +358,11 @@ func (c *context) ParamValues() []string {
} }
func (c *context) SetParamValues(values ...string) { func (c *context) SetParamValues(values ...string) {
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam (or bigger) at all times
// It will brake the Router#Find code // It will brake the Router#Find code
limit := len(values) limit := len(values)
if limit > *c.echo.maxParam { if limit > len(c.pvalues) {
limit = *c.echo.maxParam c.pvalues = make([]string, limit)
} }
for i := 0; i < limit; i++ { for i := 0; i < limit; i++ {
c.pvalues[i] = values[i] c.pvalues[i] = values[i]
@ -489,7 +500,7 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error
} }
func (c *context) json(code int, i interface{}, indent string) error { func (c *context) json(code int, i interface{}, indent string) error {
c.writeContentType(MIMEApplicationJSONCharsetUTF8) c.writeContentType(MIMEApplicationJSON)
c.response.Status = code c.response.Status = code
return c.echo.JSONSerializer.Serialize(c, i, indent) return c.echo.JSONSerializer.Serialize(c, i, indent)
} }
@ -507,7 +518,7 @@ func (c *context) JSONPretty(code int, i interface{}, indent string) (err error)
} }
func (c *context) JSONBlob(code int, b []byte) (err error) { func (c *context) JSONBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b) return c.Blob(code, MIMEApplicationJSON, b)
} }
func (c *context) JSONP(code int, callback string, i interface{}) (err error) { func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
@ -584,8 +595,10 @@ func (c *context) Inline(file, name string) error {
return c.contentDisposition(file, name, "inline") return c.contentDisposition(file, name, "inline")
} }
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func (c *context) contentDisposition(file, name, dispositionType string) error { func (c *context) contentDisposition(file, name, dispositionType string) error {
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name)) c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name)))
return c.File(file) return c.File(file)
} }
@ -640,8 +653,8 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.path = "" c.path = ""
c.pnames = nil c.pnames = nil
c.logger = nil c.logger = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times // NOTE: Don't reset because it has to have length c.echo.maxParam (or bigger) at all times
for i := 0; i < *c.echo.maxParam; i++ { for i := 0; i < len(c.pvalues); i++ {
c.pvalues[i] = "" c.pvalues[i] = ""
} }
} }

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
/* /*
Package echo implements high performance, minimalist Go web framework. Package echo implements high performance, minimalist Go web framework.
@ -39,6 +42,7 @@ package echo
import ( import (
stdContext "context" stdContext "context"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -59,97 +63,95 @@ import (
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
) )
type ( // Echo is the top-level framework instance.
// Echo is the top-level framework instance. //
// // Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these // fields from handlers/middlewares and changing field values at the same time leads to data-races.
// fields from handlers/middlewares and changing field values at the same time leads to data-races. // Adding new routes after the server has been started is also not safe!
// Adding new routes after the server has been started is also not safe! type Echo struct {
Echo struct { filesystem
filesystem common
common // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get // listener address info (on which interface/port was listener bound) without having data races.
// listener address info (on which interface/port was listener binded) without having data races. startupMutex sync.RWMutex
startupMutex sync.RWMutex colorer *color.Color
colorer *color.Color
// premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
// an error the router is not executed and the request will end up in the global error handler. // an error the router is not executed and the request will end up in the global error handler.
premiddleware []MiddlewareFunc premiddleware []MiddlewareFunc
middleware []MiddlewareFunc middleware []MiddlewareFunc
maxParam *int maxParam *int
router *Router router *Router
routers map[string]*Router routers map[string]*Router
pool sync.Pool pool sync.Pool
StdLogger *stdLog.Logger StdLogger *stdLog.Logger
Server *http.Server Server *http.Server
TLSServer *http.Server TLSServer *http.Server
Listener net.Listener Listener net.Listener
TLSListener net.Listener TLSListener net.Listener
AutoTLSManager autocert.Manager AutoTLSManager autocert.Manager
DisableHTTP2 bool DisableHTTP2 bool
Debug bool Debug bool
HideBanner bool HideBanner bool
HidePort bool HidePort bool
HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler
Binder Binder Binder Binder
JSONSerializer JSONSerializer JSONSerializer JSONSerializer
Validator Validator Validator Validator
Renderer Renderer Renderer Renderer
Logger Logger Logger Logger
IPExtractor IPExtractor IPExtractor IPExtractor
ListenerNetwork string ListenerNetwork string
// OnAddRouteHandler is called when Echo adds new route to specific host router. // OnAddRouteHandler is called when Echo adds new route to specific host router.
OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc) OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
} }
// Route contains a handler and information for matching against requests. // Route contains a handler and information for matching against requests.
Route struct { type Route struct {
Method string `json:"method"` Method string `json:"method"`
Path string `json:"path"` Path string `json:"path"`
Name string `json:"name"` Name string `json:"name"`
} }
// HTTPError represents an error that occurred while handling a request. // HTTPError represents an error that occurred while handling a request.
HTTPError struct { type HTTPError struct {
Code int `json:"-"` Code int `json:"-"`
Message interface{} `json:"message"` Message interface{} `json:"message"`
Internal error `json:"-"` // Stores the error returned by an external dependency Internal error `json:"-"` // Stores the error returned by an external dependency
} }
// MiddlewareFunc defines a function to process middleware. // MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(next HandlerFunc) HandlerFunc type MiddlewareFunc func(next HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests. // HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(c Context) error type HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler. // HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(err error, c Context) type HTTPErrorHandler func(err error, c Context)
// Validator is the interface that wraps the Validate function. // Validator is the interface that wraps the Validate function.
Validator interface { type Validator interface {
Validate(i interface{}) error Validate(i interface{}) error
} }
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. // JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
JSONSerializer interface { type JSONSerializer interface {
Serialize(c Context, i interface{}, indent string) error Serialize(c Context, i interface{}, indent string) error
Deserialize(c Context, i interface{}) error Deserialize(c Context, i interface{}) error
} }
// Renderer is the interface that wraps the Render function. // Renderer is the interface that wraps the Render function.
Renderer interface { type Renderer interface {
Render(io.Writer, string, interface{}, Context) error Render(io.Writer, string, interface{}, Context) error
} }
// Map defines a generic map of type `map[string]interface{}`. // Map defines a generic map of type `map[string]interface{}`.
Map map[string]interface{} type Map map[string]interface{}
// Common struct for Echo & Group. // Common struct for Echo & Group.
common struct{} type common struct{}
)
// HTTP methods // HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead. // NOTE: Deprecated, please use the stdlib constants directly instead.
@ -168,7 +170,12 @@ const (
// MIME types // MIME types
const ( const (
MIMEApplicationJSON = "application/json" // MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259
MIMEApplicationJSON = "application/json"
// Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default.
// No "charset" parameter is defined for this registration.
// Adding one really has no effect on compliant recipients.
// See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8 MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript" MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8 MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
@ -258,7 +265,7 @@ const (
const ( const (
// Version of Echo // Version of Echo
Version = "4.10.0" Version = "4.12.0"
website = "https://echo.labstack.com" website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = ` banner = `
@ -273,60 +280,88 @@ ____________________________________O/_______
` `
) )
var ( var methods = [...]string{
methods = [...]string{ http.MethodConnect,
http.MethodConnect, http.MethodDelete,
http.MethodDelete, http.MethodGet,
http.MethodGet, http.MethodHead,
http.MethodHead, http.MethodOptions,
http.MethodOptions, http.MethodPatch,
http.MethodPatch, http.MethodPost,
http.MethodPost, PROPFIND,
PROPFIND, http.MethodPut,
http.MethodPut, http.MethodTrace,
http.MethodTrace, REPORT,
REPORT, }
}
)
// Errors // Errors
var ( var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) ErrBadRequest = NewHTTPError(http.StatusBadRequest) // HTTP 400 Bad Request
ErrNotFound = NewHTTPError(http.StatusNotFound) ErrUnauthorized = NewHTTPError(http.StatusUnauthorized) // HTTP 401 Unauthorized
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized) ErrPaymentRequired = NewHTTPError(http.StatusPaymentRequired) // HTTP 402 Payment Required
ErrForbidden = NewHTTPError(http.StatusForbidden) ErrForbidden = NewHTTPError(http.StatusForbidden) // HTTP 403 Forbidden
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) ErrNotFound = NewHTTPError(http.StatusNotFound) // HTTP 404 Not Found
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) // HTTP 405 Method Not Allowed
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests) ErrNotAcceptable = NewHTTPError(http.StatusNotAcceptable) // HTTP 406 Not Acceptable
ErrBadRequest = NewHTTPError(http.StatusBadRequest) ErrProxyAuthRequired = NewHTTPError(http.StatusProxyAuthRequired) // HTTP 407 Proxy AuthRequired
ErrBadGateway = NewHTTPError(http.StatusBadGateway) ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout) // HTTP 408 Request Timeout
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) ErrConflict = NewHTTPError(http.StatusConflict) // HTTP 409 Conflict
ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout) ErrGone = NewHTTPError(http.StatusGone) // HTTP 410 Gone
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable) ErrLengthRequired = NewHTTPError(http.StatusLengthRequired) // HTTP 411 Length Required
ErrValidatorNotRegistered = errors.New("validator not registered") ErrPreconditionFailed = NewHTTPError(http.StatusPreconditionFailed) // HTTP 412 Precondition Failed
ErrRendererNotRegistered = errors.New("renderer not registered") ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // HTTP 413 Payload Too Large
ErrInvalidRedirectCode = errors.New("invalid redirect status code") ErrRequestURITooLong = NewHTTPError(http.StatusRequestURITooLong) // HTTP 414 URI Too Long
ErrCookieNotFound = errors.New("cookie not found") ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) // HTTP 415 Unsupported Media Type
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte") ErrRequestedRangeNotSatisfiable = NewHTTPError(http.StatusRequestedRangeNotSatisfiable) // HTTP 416 Range Not Satisfiable
ErrInvalidListenerNetwork = errors.New("invalid listener network") ErrExpectationFailed = NewHTTPError(http.StatusExpectationFailed) // HTTP 417 Expectation Failed
ErrTeapot = NewHTTPError(http.StatusTeapot) // HTTP 418 I'm a teapot
ErrMisdirectedRequest = NewHTTPError(http.StatusMisdirectedRequest) // HTTP 421 Misdirected Request
ErrUnprocessableEntity = NewHTTPError(http.StatusUnprocessableEntity) // HTTP 422 Unprocessable Entity
ErrLocked = NewHTTPError(http.StatusLocked) // HTTP 423 Locked
ErrFailedDependency = NewHTTPError(http.StatusFailedDependency) // HTTP 424 Failed Dependency
ErrTooEarly = NewHTTPError(http.StatusTooEarly) // HTTP 425 Too Early
ErrUpgradeRequired = NewHTTPError(http.StatusUpgradeRequired) // HTTP 426 Upgrade Required
ErrPreconditionRequired = NewHTTPError(http.StatusPreconditionRequired) // HTTP 428 Precondition Required
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests) // HTTP 429 Too Many Requests
ErrRequestHeaderFieldsTooLarge = NewHTTPError(http.StatusRequestHeaderFieldsTooLarge) // HTTP 431 Request Header Fields Too Large
ErrUnavailableForLegalReasons = NewHTTPError(http.StatusUnavailableForLegalReasons) // HTTP 451 Unavailable For Legal Reasons
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // HTTP 500 Internal Server Error
ErrNotImplemented = NewHTTPError(http.StatusNotImplemented) // HTTP 501 Not Implemented
ErrBadGateway = NewHTTPError(http.StatusBadGateway) // HTTP 502 Bad Gateway
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable) // HTTP 503 Service Unavailable
ErrGatewayTimeout = NewHTTPError(http.StatusGatewayTimeout) // HTTP 504 Gateway Timeout
ErrHTTPVersionNotSupported = NewHTTPError(http.StatusHTTPVersionNotSupported) // HTTP 505 HTTP Version Not Supported
ErrVariantAlsoNegotiates = NewHTTPError(http.StatusVariantAlsoNegotiates) // HTTP 506 Variant Also Negotiates
ErrInsufficientStorage = NewHTTPError(http.StatusInsufficientStorage) // HTTP 507 Insufficient Storage
ErrLoopDetected = NewHTTPError(http.StatusLoopDetected) // HTTP 508 Loop Detected
ErrNotExtended = NewHTTPError(http.StatusNotExtended) // HTTP 510 Not Extended
ErrNetworkAuthenticationRequired = NewHTTPError(http.StatusNetworkAuthenticationRequired) // HTTP 511 Network Authentication Required
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")
ErrInvalidListenerNetwork = errors.New("invalid listener network")
) )
// Error handlers // NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results
var ( // HTTP 404 status code.
NotFoundHandler = func(c Context) error { var NotFoundHandler = func(c Context) error {
return ErrNotFound return ErrNotFound
} }
MethodNotAllowedHandler = func(c Context) error { // MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed) // another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code.
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned var MethodNotAllowedHandler = func(c Context) error {
routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string) // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
if ok && routerAllowMethods != "" { // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
c.Response().Header().Set(HeaderAllow, routerAllowMethods) routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
} if ok && routerAllowMethods != "" {
return ErrMethodNotAllowed c.Response().Header().Set(HeaderAllow, routerAllowMethods)
} }
) return ErrMethodNotAllowed
}
// New creates an instance of Echo. // New creates an instance of Echo.
func New() (e *Echo) { func New() (e *Echo) {
@ -384,7 +419,7 @@ func (e *Echo) Routers() map[string]*Router {
// //
// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error). // NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from // When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from
// handler. Then the error that global error handler received will be ignored because we have already "commited" the // handler. Then the error that global error handler received will be ignored because we have already "committed" the
// response and status code header has been sent to the client. // response and status code header has been sent to the client.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
@ -409,12 +444,18 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
// Issue #1426 // Issue #1426
code := he.Code code := he.Code
message := he.Message message := he.Message
if m, ok := he.Message.(string); ok {
switch m := he.Message.(type) {
case string:
if e.Debug { if e.Debug {
message = Map{"message": m, "error": err.Error()} message = Map{"message": m, "error": err.Error()}
} else { } else {
message = Map{"message": m} message = Map{"message": m}
} }
case json.Marshaler:
// do nothing - this type knows how to format itself to JSON
case error:
message = Map{"message": m.Error()}
} }
// Send response // Send response
@ -585,7 +626,7 @@ func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...) return e.URI(h, params...)
} }
// Reverse generates an URL from route name and provided parameters. // Reverse generates a URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string { func (e *Echo) Reverse(name string, params ...interface{}) string {
return e.router.Reverse(name, params...) return e.router.Reverse(name, params...)
} }

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (

View File

@ -1,21 +1,22 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
"net/http" "net/http"
) )
type ( // Group is a set of sub-routes for a specified route. It can be used for inner
// 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
// routes that share a common middleware or functionality that should be separate // from the parent echo instance while still inheriting from it.
// from the parent echo instance while still inheriting from it. type Group struct {
Group struct { common
common host string
host string prefix string
prefix string middleware []MiddlewareFunc
middleware []MiddlewareFunc echo *Echo
echo *Echo }
}
)
// Use implements `Echo#Use()` for sub-routes within the Group. // Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) { func (g *Group) Use(middleware ...MiddlewareFunc) {
@ -23,10 +24,12 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
if len(g.middleware) == 0 { if len(g.middleware) == 0 {
return return
} }
// Allow all requests to reach the group as they might get dropped if router // group level middlewares are different from Echo `Pre` and `Use` middlewares (those are global). Group level middlewares
// doesn't find a match, making none of the group middleware process. // are only executed if they are added to the Router with route.
g.Any("", NotFoundHandler) // So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the
g.Any("/*", NotFoundHandler) // Router would find route to match our request path and therefore guarantee the middleware(s) will get executed.
g.RouteNotFound("", NotFoundHandler)
g.RouteNotFound("/*", NotFoundHandler)
} }
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. // CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -64,7 +67,7 @@ XFF: "x" "x, a" "x, a, b"
``` ```
In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre". configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure".
In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`. In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`. In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
@ -225,15 +228,21 @@ func extractIP(req *http.Request) string {
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor { func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options) checker := newIPChecker(options)
return func(req *http.Request) string { return func(req *http.Request) string {
directIP := extractIP(req)
realIP := req.Header.Get(HeaderXRealIP) realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" { if realIP == "" {
return directIP
}
if checker.trust(net.ParseIP(directIP)) {
realIP = strings.TrimPrefix(realIP, "[") realIP = strings.TrimPrefix(realIP, "[")
realIP = strings.TrimSuffix(realIP, "]") realIP = strings.TrimSuffix(realIP, "]")
if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) { if rIP := net.ParseIP(realIP); rIP != nil {
return realIP return realIP
} }
} }
return extractIP(req)
return directIP
} }
} }

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (

View File

@ -1,41 +1,41 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
"io"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"
"io"
) )
type ( // Logger defines the logging interface.
// Logger defines the logging interface. type Logger interface {
Logger interface { Output() io.Writer
Output() io.Writer SetOutput(w io.Writer)
SetOutput(w io.Writer) Prefix() string
Prefix() string SetPrefix(p string)
SetPrefix(p string) Level() log.Lvl
Level() log.Lvl SetLevel(v log.Lvl)
SetLevel(v log.Lvl) SetHeader(h string)
SetHeader(h string) Print(i ...interface{})
Print(i ...interface{}) Printf(format string, args ...interface{})
Printf(format string, args ...interface{}) Printj(j log.JSON)
Printj(j log.JSON) Debug(i ...interface{})
Debug(i ...interface{}) Debugf(format string, args ...interface{})
Debugf(format string, args ...interface{}) Debugj(j log.JSON)
Debugj(j log.JSON) Info(i ...interface{})
Info(i ...interface{}) Infof(format string, args ...interface{})
Infof(format string, args ...interface{}) Infoj(j log.JSON)
Infoj(j log.JSON) Warn(i ...interface{})
Warn(i ...interface{}) Warnf(format string, args ...interface{})
Warnf(format string, args ...interface{}) Warnj(j log.JSON)
Warnj(j log.JSON) Error(i ...interface{})
Error(i ...interface{}) Errorf(format string, args ...interface{})
Errorf(format string, args ...interface{}) Errorj(j log.JSON)
Errorj(j log.JSON) Fatal(i ...interface{})
Fatal(i ...interface{}) Fatalj(j log.JSON)
Fatalj(j log.JSON) Fatalf(format string, args ...interface{})
Fatalf(format string, args ...interface{}) Panic(i ...interface{})
Panic(i ...interface{}) Panicj(j log.JSON)
Panicj(j log.JSON) Panicf(format string, args ...interface{})
Panicf(format string, args ...interface{}) }
}
)

View File

@ -1,25 +1,27 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
"bufio" "bufio"
"errors"
"net" "net"
"net/http" "net/http"
) )
type ( // Response wraps an http.ResponseWriter and implements its interface to be used
// Response wraps an http.ResponseWriter and implements its interface to be used // by an HTTP handler to construct an HTTP response.
// by an HTTP handler to construct an HTTP response. // See: https://golang.org/pkg/net/http/#ResponseWriter
// See: https://golang.org/pkg/net/http/#ResponseWriter type Response struct {
Response struct { echo *Echo
echo *Echo beforeFuncs []func()
beforeFuncs []func() afterFuncs []func()
afterFuncs []func() Writer http.ResponseWriter
Writer http.ResponseWriter Status int
Status int Size int64
Size int64 Committed bool
Committed bool }
}
)
// NewResponse creates a new instance of Response. // NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) { func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
@ -84,14 +86,24 @@ func (r *Response) Write(b []byte) (n int, err error) {
// buffered data to the client. // buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher) // See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() { func (r *Response) Flush() {
r.Writer.(http.Flusher).Flush() err := responseControllerFlush(r.Writer)
if err != nil && errors.Is(err, http.ErrNotSupported) {
panic(errors.New("response writer flushing is not supported"))
}
} }
// Hijack implements the http.Hijacker interface to allow an HTTP handler to // Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection. // take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker) // See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack() return responseControllerHijack(r.Writer)
}
// Unwrap returns the original http.ResponseWriter.
// ResponseController can be used to access the original http.ResponseWriter.
// See [https://go.dev/blog/go1.20]
func (r *Response) Unwrap() http.ResponseWriter {
return r.Writer
} }
func (r *Response) reset(w http.ResponseWriter) { func (r *Response) reset(w http.ResponseWriter) {

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build !go1.20
package echo
import (
"bufio"
"fmt"
"net"
"net/http"
)
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerFlush(rw http.ResponseWriter) error {
for {
switch t := rw.(type) {
case interface{ FlushError() error }:
return t.FlushError()
case http.Flusher:
t.Flush()
return nil
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return fmt.Errorf("%w", http.ErrNotSupported)
}
}
}
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
for {
switch t := rw.(type) {
case http.Hijacker:
return t.Hijack()
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
}
}
}

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build go1.20
package echo
import (
"bufio"
"net"
"net/http"
)
func responseControllerFlush(rw http.ResponseWriter) error {
return http.NewResponseController(rw).Flush()
}
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
return http.NewResponseController(rw).Hijack()
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -6,56 +9,58 @@ import (
"net/http" "net/http"
) )
type ( // Router is the registry of all registered routes for an `Echo` instance for
// Router is the registry of all registered routes for an `Echo` instance for // request matching and URL path parameter parsing.
// request matching and URL path parameter parsing. type Router struct {
Router struct { tree *node
tree *node routes map[string]*Route
routes map[string]*Route echo *Echo
echo *Echo }
}
node struct {
kind kind
label byte
prefix string
parent *node
staticChildren children
originalPath string
methods *routeMethods
paramChild *node
anyChild *node
paramsCount int
// isLeaf indicates that node does not have child routes
isLeaf bool
// isHandler indicates that node has at least one handler registered to it
isHandler bool
// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases type node struct {
notFoundHandler *routeMethod kind kind
} label byte
kind uint8 prefix string
children []*node parent *node
routeMethod struct { staticChildren children
ppath string originalPath string
pnames []string methods *routeMethods
handler HandlerFunc paramChild *node
} anyChild *node
routeMethods struct { paramsCount int
connect *routeMethod // isLeaf indicates that node does not have child routes
delete *routeMethod isLeaf bool
get *routeMethod // isHandler indicates that node has at least one handler registered to it
head *routeMethod isHandler bool
options *routeMethod
patch *routeMethod // notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
post *routeMethod notFoundHandler *routeMethod
propfind *routeMethod }
put *routeMethod
trace *routeMethod type kind uint8
report *routeMethod type children []*node
anyOther map[string]*routeMethod
allowHeader string type routeMethod struct {
} ppath string
) pnames []string
handler HandlerFunc
}
type routeMethods struct {
connect *routeMethod
delete *routeMethod
get *routeMethod
head *routeMethod
options *routeMethod
patch *routeMethod
post *routeMethod
propfind *routeMethod
put *routeMethod
trace *routeMethod
report *routeMethod
anyOther map[string]*routeMethod
allowHeader string
}
const ( const (
staticKind kind = iota staticKind kind = iota
@ -151,7 +156,7 @@ func (r *Router) Routes() []*Route {
return routes return routes
} }
// Reverse generates an URL from route name and provided parameters. // Reverse generates a URL from route name and provided parameters.
func (r *Router) Reverse(name string, params ...interface{}) string { func (r *Router) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer) uri := new(bytes.Buffer)
ln := len(params) ln := len(params)
@ -159,7 +164,12 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
for _, route := range r.routes { for _, route := range r.routes {
if route.Name == name { if route.Name == name {
for i, l := 0, len(route.Path); i < l; i++ { for i, l := 0, len(route.Path); i < l; i++ {
if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln { hasBackslash := route.Path[i] == '\\'
if hasBackslash && i+1 < l && route.Path[i+1] == ':' {
i++ // backslash before colon escapes that colon. in that case skip backslash
}
if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) {
// in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path
for ; i < l && route.Path[i] != '/'; i++ { for ; i < l && route.Path[i] != '/'; i++ {
} }
uri.WriteString(fmt.Sprintf("%v", params[n])) uri.WriteString(fmt.Sprintf("%v", params[n]))
@ -175,8 +185,18 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
return uri.String() return uri.String()
} }
func normalizePathSlash(path string) string {
if path == "" {
path = "/"
} else if path[0] != '/' {
path = "/" + path
}
return path
}
func (r *Router) add(method, path, name string, h HandlerFunc) *Route { func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
r.Add(method, path, h) path = normalizePathSlash(path)
r.insert(method, path, h)
route := &Route{ route := &Route{
Method: method, Method: method,
@ -189,13 +209,11 @@ func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
// Add registers a new route for method and path with matching handler. // Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) { func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path r.insert(method, normalizePathSlash(path), h)
if path == "" { }
path = "/"
} func (r *Router) insert(method, path string, h HandlerFunc) {
if path[0] != '/' { path = normalizePathSlash(path)
path = "/" + path
}
pnames := []string{} // Param names pnames := []string{} // Param names
ppath := path // Pristine path ppath := path // Pristine path
@ -214,7 +232,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
} }
j := i + 1 j := i + 1
r.insert(method, path[:i], staticKind, routeMethod{}) r.insertNode(method, path[:i], staticKind, routeMethod{})
for ; i < lcpIndex && path[i] != '/'; i++ { for ; i < lcpIndex && path[i] != '/'; i++ {
} }
@ -224,21 +242,21 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
if i == lcpIndex { if i == lcpIndex {
// path node is last fragment of route path. ie. `/users/:id` // path node is last fragment of route path. ie. `/users/:id`
r.insert(method, path[:i], paramKind, routeMethod{ppath, pnames, h}) r.insertNode(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
} else { } else {
r.insert(method, path[:i], paramKind, routeMethod{}) r.insertNode(method, path[:i], paramKind, routeMethod{})
} }
} else if path[i] == '*' { } else if path[i] == '*' {
r.insert(method, path[:i], staticKind, routeMethod{}) r.insertNode(method, path[:i], staticKind, routeMethod{})
pnames = append(pnames, "*") pnames = append(pnames, "*")
r.insert(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h}) r.insertNode(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
} }
} }
r.insert(method, path, staticKind, routeMethod{ppath, pnames, h}) r.insertNode(method, path, staticKind, routeMethod{ppath, pnames, h})
} }
func (r *Router) insert(method, path string, t kind, rm routeMethod) { func (r *Router) insertNode(method, path string, t kind, rm routeMethod) {
// Adjust max param // Adjust max param
paramLen := len(rm.pnames) paramLen := len(rm.pnames)
if *r.echo.maxParam < paramLen { if *r.echo.maxParam < paramLen {
@ -524,7 +542,6 @@ func optionsMethodHandler(allowMethods string) func(c Context) error {
// - Return it `Echo#ReleaseContext()`. // - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, c Context) { func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context) ctx := c.(*context)
ctx.path = path
currentNode := r.tree // Current node as root currentNode := r.tree // Current node as root
var ( var (

136
vendor/github.com/lib/pq/conn.go generated vendored
View File

@ -2,6 +2,7 @@ package pq
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"crypto/md5" "crypto/md5"
"crypto/sha256" "crypto/sha256"
@ -112,7 +113,9 @@ type defaultDialer struct {
func (d defaultDialer) Dial(network, address string) (net.Conn, error) { func (d defaultDialer) Dial(network, address string) (net.Conn, error) {
return d.d.Dial(network, address) return d.d.Dial(network, address)
} }
func (d defaultDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { func (d defaultDialer) DialTimeout(
network, address string, timeout time.Duration,
) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
@ -260,47 +263,56 @@ func (cn *conn) handlePgpass(o values) {
} }
defer file.Close() defer file.Close()
scanner := bufio.NewScanner(io.Reader(file)) scanner := bufio.NewScanner(io.Reader(file))
// From: https://github.com/tg/pgpass/blob/master/reader.go
for scanner.Scan() {
if scanText(scanner.Text(), o) {
break
}
}
}
// GetFields is a helper function for scanText.
func getFields(s string) []string {
fs := make([]string, 0, 5)
f := make([]rune, 0, len(s))
var esc bool
for _, c := range s {
switch {
case esc:
f = append(f, c)
esc = false
case c == '\\':
esc = true
case c == ':':
fs = append(fs, string(f))
f = f[:0]
default:
f = append(f, c)
}
}
return append(fs, string(f))
}
// ScanText assists HandlePgpass in it's objective.
func scanText(line string, o values) bool {
hostname := o["host"] hostname := o["host"]
ntw, _ := network(o) ntw, _ := network(o)
port := o["port"] port := o["port"]
db := o["dbname"] db := o["dbname"]
username := o["user"] username := o["user"]
// From: https://github.com/tg/pgpass/blob/master/reader.go if len(line) == 0 || line[0] == '#' {
getFields := func(s string) []string { return false
fs := make([]string, 0, 5)
f := make([]rune, 0, len(s))
var esc bool
for _, c := range s {
switch {
case esc:
f = append(f, c)
esc = false
case c == '\\':
esc = true
case c == ':':
fs = append(fs, string(f))
f = f[:0]
default:
f = append(f, c)
}
}
return append(fs, string(f))
} }
for scanner.Scan() { split := getFields(line)
line := scanner.Text() if len(split) != 5 {
if len(line) == 0 || line[0] == '#' { return false
continue
}
split := getFields(line)
if len(split) != 5 {
continue
}
if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) {
o["password"] = split[4]
return
}
} }
if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) {
o["password"] = split[4]
return true
}
return false
} }
func (cn *conn) writeBuf(b byte) *writeBuf { func (cn *conn) writeBuf(b byte) *writeBuf {
@ -765,7 +777,9 @@ func (noRows) RowsAffected() (int64, error) {
// Decides which column formats to use for a prepared statement. The input is // Decides which column formats to use for a prepared statement. The input is
// an array of type oids, one element per result column. // an array of type oids, one element per result column.
func decideColumnFormats(colTyps []fieldDesc, forceText bool) (colFmts []format, colFmtData []byte) { func decideColumnFormats(
colTyps []fieldDesc, forceText bool,
) (colFmts []format, colFmtData []byte) {
if len(colTyps) == 0 { if len(colTyps) == 0 {
return nil, colFmtDataAllText return nil, colFmtDataAllText
} }
@ -1631,10 +1645,10 @@ func (rs *rows) NextResultSet() error {
// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be // QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be
// used as part of an SQL statement. For example: // used as part of an SQL statement. For example:
// //
// tblname := "my_table" // tblname := "my_table"
// data := "my_data" // data := "my_data"
// quoted := pq.QuoteIdentifier(tblname) // quoted := pq.QuoteIdentifier(tblname)
// err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data) // err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data)
// //
// Any double quotes in name will be escaped. The quoted identifier will be // Any double quotes in name will be escaped. The quoted identifier will be
// case sensitive when used in a query. If the input string contains a zero // case sensitive when used in a query. If the input string contains a zero
@ -1647,12 +1661,24 @@ func QuoteIdentifier(name string) string {
return `"` + strings.Replace(name, `"`, `""`, -1) + `"` return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
} }
// BufferQuoteIdentifier satisfies the same purpose as QuoteIdentifier, but backed by a
// byte buffer.
func BufferQuoteIdentifier(name string, buffer *bytes.Buffer) {
end := strings.IndexRune(name, 0)
if end > -1 {
name = name[:end]
}
buffer.WriteRune('"')
buffer.WriteString(strings.Replace(name, `"`, `""`, -1))
buffer.WriteRune('"')
}
// QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal // QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal
// to DDL and other statements that do not accept parameters) to be used as part // to DDL and other statements that do not accept parameters) to be used as part
// of an SQL statement. For example: // of an SQL statement. For example:
// //
// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z") // exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z")
// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date)) // err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date))
// //
// Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be // Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be
// replaced by two backslashes (i.e. "\\") and the C-style escape identifier // replaced by two backslashes (i.e. "\\") and the C-style escape identifier
@ -1808,7 +1834,11 @@ func (cn *conn) readParseResponse() {
} }
} }
func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []fieldDesc) { func (cn *conn) readStatementDescribeResponse() (
paramTyps []oid.Oid,
colNames []string,
colTyps []fieldDesc,
) {
for { for {
t, r := cn.recv1() t, r := cn.recv1()
switch t { switch t {
@ -1896,7 +1926,9 @@ func (cn *conn) postExecuteWorkaround() {
} }
// Only for Exec(), since we ignore the returned data // Only for Exec(), since we ignore the returned data
func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, commandTag string, err error) { func (cn *conn) readExecuteResponse(
protocolState string,
) (res driver.Result, commandTag string, err error) {
for { for {
t, r := cn.recv1() t, r := cn.recv1()
switch t { switch t {
@ -2062,3 +2094,19 @@ func alnumLowerASCII(ch rune) rune {
} }
return -1 // discard return -1 // discard
} }
// The database/sql/driver package says:
// All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator.
var _ driver.Pinger = &conn{}
var _ driver.SessionResetter = &conn{}
func (cn *conn) ResetSession(ctx context.Context) error {
// Ensure bad connections are reported: From database/sql/driver:
// If a connection is never returned to the connection pool but immediately reused, then
// ResetSession is called prior to reuse but IsValid is not called.
return cn.err.get()
}
func (cn *conn) IsValid() bool {
return cn.err.get() == nil
}

8
vendor/github.com/lib/pq/conn_go115.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
//go:build go1.15
// +build go1.15
package pq
import "database/sql/driver"
var _ driver.Validator = &conn{}

35
vendor/github.com/lib/pq/copy.go generated vendored
View File

@ -1,6 +1,7 @@
package pq package pq
import ( import (
"bytes"
"context" "context"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
@ -20,29 +21,35 @@ var (
// CopyIn creates a COPY FROM statement which can be prepared with // CopyIn creates a COPY FROM statement which can be prepared with
// Tx.Prepare(). The target table should be visible in search_path. // Tx.Prepare(). The target table should be visible in search_path.
func CopyIn(table string, columns ...string) string { func CopyIn(table string, columns ...string) string {
stmt := "COPY " + QuoteIdentifier(table) + " (" buffer := bytes.NewBufferString("COPY ")
BufferQuoteIdentifier(table, buffer)
buffer.WriteString(" (")
makeStmt(buffer, columns...)
return buffer.String()
}
// MakeStmt makes the stmt string for CopyIn and CopyInSchema.
func makeStmt(buffer *bytes.Buffer, columns ...string) {
//s := bytes.NewBufferString()
for i, col := range columns { for i, col := range columns {
if i != 0 { if i != 0 {
stmt += ", " buffer.WriteString(", ")
} }
stmt += QuoteIdentifier(col) BufferQuoteIdentifier(col, buffer)
} }
stmt += ") FROM STDIN" buffer.WriteString(") FROM STDIN")
return stmt
} }
// CopyInSchema creates a COPY FROM statement which can be prepared with // CopyInSchema creates a COPY FROM statement which can be prepared with
// Tx.Prepare(). // Tx.Prepare().
func CopyInSchema(schema, table string, columns ...string) string { func CopyInSchema(schema, table string, columns ...string) string {
stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " (" buffer := bytes.NewBufferString("COPY ")
for i, col := range columns { BufferQuoteIdentifier(schema, buffer)
if i != 0 { buffer.WriteRune('.')
stmt += ", " BufferQuoteIdentifier(table, buffer)
} buffer.WriteString(" (")
stmt += QuoteIdentifier(col) makeStmt(buffer, columns...)
} return buffer.String()
stmt += ") FROM STDIN"
return stmt
} }
type copyin struct { type copyin struct {

View File

@ -1,6 +1,7 @@
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine //go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
// +build darwin freebsd openbsd netbsd dragonfly hurd // +build darwin freebsd openbsd netbsd dragonfly hurd
// +build !appengine // +build !appengine
// +build !tinygo
package isatty package isatty

View File

@ -1,5 +1,6 @@
//go:build appengine || js || nacl || wasm //go:build (appengine || js || nacl || tinygo || wasm) && !windows
// +build appengine js nacl wasm // +build appengine js nacl tinygo wasm
// +build !windows
package isatty package isatty

View File

@ -1,6 +1,7 @@
//go:build (linux || aix || zos) && !appengine //go:build (linux || aix || zos) && !appengine && !tinygo
// +build linux aix zos // +build linux aix zos
// +build !appengine // +build !appengine
// +build !tinygo
package isatty package isatty

4
vendor/golang.org/x/crypto/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

View File

@ -15,6 +15,7 @@ import (
"io" "io"
"math/big" "math/big"
"net/http" "net/http"
"runtime/debug"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -271,9 +272,27 @@ func (c *Client) httpClient() *http.Client {
} }
// packageVersion is the version of the module that contains this package, for // 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. // sending as part of the User-Agent header.
var packageVersion string var packageVersion string
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
}
}
// userAgent returns the User-Agent header value. It includes the package name, // userAgent returns the User-Agent header value. It includes the package name,
// the module version (if available), and the c.UserAgent value (if set). // the module version (if available), and the c.UserAgent value (if set).
func (c *Client) userAgent() string { func (c *Client) userAgent() string {

View File

@ -117,7 +117,7 @@ func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error)
return responseAccount(res) return responseAccount(res)
} }
// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555. // getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
// It expects c.Discover to have already been called. // It expects c.Discover to have already been called.
func (c *Client) getRegRFC(ctx context.Context) (*Account, error) { func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
req := json.RawMessage(`{"onlyReturnExisting": true}`) req := json.RawMessage(`{"onlyReturnExisting": true}`)

View File

@ -1,28 +0,0 @@
// 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.
//go:build go1.12
// +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
}
}

4
vendor/golang.org/x/net/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

View File

@ -12,7 +12,7 @@ import (
"golang.org/x/net/idna" "golang.org/x/net/idna"
) )
var isTokenTable = [127]bool{ var isTokenTable = [256]bool{
'!': true, '!': true,
'#': true, '#': true,
'$': true, '$': true,
@ -93,12 +93,7 @@ var isTokenTable = [127]bool{
} }
func IsTokenRune(r rune) bool { func IsTokenRune(r rune) bool {
i := int(r) return r < utf8.RuneSelf && isTokenTable[byte(r)]
return i < len(isTokenTable) && isTokenTable[i]
}
func isNotToken(r rune) bool {
return !IsTokenRune(r)
} }
// HeaderValuesContainsToken reports whether any string in values // HeaderValuesContainsToken reports whether any string in values
@ -202,8 +197,8 @@ func ValidHeaderFieldName(v string) bool {
if len(v) == 0 { if len(v) == 0 {
return false return false
} }
for _, r := range v { for i := 0; i < len(v); i++ {
if !IsTokenRune(r) { if !isTokenTable[v[i]] {
return false return false
} }
} }

View File

@ -1,51 +0,0 @@
#
# This Dockerfile builds a recent curl with HTTP/2 client support, using
# a recent nghttp2 build.
#
# See the Makefile for how to tag it. If Docker and that image is found, the
# Go tests use this curl binary for integration tests.
#
FROM ubuntu:trusty
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y git-core build-essential wget
RUN apt-get install -y --no-install-recommends \
autotools-dev libtool pkg-config zlib1g-dev \
libcunit1-dev libssl-dev libxml2-dev libevent-dev \
automake autoconf
# The list of packages nghttp2 recommends for h2load:
RUN apt-get install -y --no-install-recommends make binutils \
autoconf automake autotools-dev \
libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \
libev-dev libevent-dev libjansson-dev libjemalloc-dev \
cython python3.4-dev python-setuptools
# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
ENV NGHTTP2_VER 895da9a
RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
WORKDIR /root/nghttp2
RUN git reset --hard $NGHTTP2_VER
RUN autoreconf -i
RUN automake
RUN autoconf
RUN ./configure
RUN make
RUN make install
WORKDIR /root
RUN wget https://curl.se/download/curl-7.45.0.tar.gz
RUN tar -zxvf curl-7.45.0.tar.gz
WORKDIR /root/curl-7.45.0
RUN ./configure --with-ssl --with-nghttp2=/usr/local
RUN make
RUN make install
RUN ldconfig
CMD ["-h"]
ENTRYPOINT ["/usr/local/bin/curl"]

View File

@ -1,3 +0,0 @@
curlimage:
docker build -t gohttp2/curl .

View File

@ -20,41 +20,44 @@ import (
// TODO: Benchmark to determine if the pools are necessary. The GC may have // TODO: Benchmark to determine if the pools are necessary. The GC may have
// improved enough that we can instead allocate chunks like this: // improved enough that we can instead allocate chunks like this:
// make([]byte, max(16<<10, expectedBytesRemaining)) // make([]byte, max(16<<10, expectedBytesRemaining))
var ( var dataChunkPools = [...]sync.Pool{
dataChunkSizeClasses = []int{ {New: func() interface{} { return new([1 << 10]byte) }},
1 << 10, {New: func() interface{} { return new([2 << 10]byte) }},
2 << 10, {New: func() interface{} { return new([4 << 10]byte) }},
4 << 10, {New: func() interface{} { return new([8 << 10]byte) }},
8 << 10, {New: func() interface{} { return new([16 << 10]byte) }},
16 << 10, }
}
dataChunkPools = [...]sync.Pool{
{New: func() interface{} { return make([]byte, 1<<10) }},
{New: func() interface{} { return make([]byte, 2<<10) }},
{New: func() interface{} { return make([]byte, 4<<10) }},
{New: func() interface{} { return make([]byte, 8<<10) }},
{New: func() interface{} { return make([]byte, 16<<10) }},
}
)
func getDataBufferChunk(size int64) []byte { func getDataBufferChunk(size int64) []byte {
i := 0 switch {
for ; i < len(dataChunkSizeClasses)-1; i++ { case size <= 1<<10:
if size <= int64(dataChunkSizeClasses[i]) { return dataChunkPools[0].Get().(*[1 << 10]byte)[:]
break case size <= 2<<10:
} return dataChunkPools[1].Get().(*[2 << 10]byte)[:]
case size <= 4<<10:
return dataChunkPools[2].Get().(*[4 << 10]byte)[:]
case size <= 8<<10:
return dataChunkPools[3].Get().(*[8 << 10]byte)[:]
default:
return dataChunkPools[4].Get().(*[16 << 10]byte)[:]
} }
return dataChunkPools[i].Get().([]byte)
} }
func putDataBufferChunk(p []byte) { func putDataBufferChunk(p []byte) {
for i, n := range dataChunkSizeClasses { switch len(p) {
if len(p) == n { case 1 << 10:
dataChunkPools[i].Put(p) dataChunkPools[0].Put((*[1 << 10]byte)(p))
return case 2 << 10:
} dataChunkPools[1].Put((*[2 << 10]byte)(p))
case 4 << 10:
dataChunkPools[2].Put((*[4 << 10]byte)(p))
case 8 << 10:
dataChunkPools[3].Put((*[8 << 10]byte)(p))
case 16 << 10:
dataChunkPools[4].Put((*[16 << 10]byte)(p))
default:
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
} }
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
} }
// dataBuffer is an io.ReadWriter backed by a list of data chunks. // dataBuffer is an io.ReadWriter backed by a list of data chunks.

View File

@ -18,7 +18,7 @@ type inflow struct {
unsent int32 unsent int32
} }
// set sets the initial window. // init sets the initial window.
func (f *inflow) init(n int32) { func (f *inflow) init(n int32) {
f.avail = n f.avail = n
} }

View File

@ -490,6 +490,9 @@ func terminalReadFrameError(err error) bool {
// returned error is ErrFrameTooLarge. Other errors may be of type // returned error is ErrFrameTooLarge. Other errors may be of type
// ConnectionError, StreamError, or anything else from the underlying // ConnectionError, StreamError, or anything else from the underlying
// reader. // reader.
//
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
// indicates the stream responsible for the error.
func (fr *Framer) ReadFrame() (Frame, error) { func (fr *Framer) ReadFrame() (Frame, error) {
fr.errDetail = nil fr.errDetail = nil
if fr.lastFrame != nil { if fr.lastFrame != nil {
@ -662,6 +665,15 @@ func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error {
// It is the caller's responsibility not to violate the maximum frame size // It is the caller's responsibility not to violate the maximum frame size
// and to not call other Write methods concurrently. // and to not call other Write methods concurrently.
func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error { func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
if err := f.startWriteDataPadded(streamID, endStream, data, pad); err != nil {
return err
}
return f.endWrite()
}
// startWriteDataPadded is WriteDataPadded, but only writes the frame to the Framer's internal buffer.
// The caller should call endWrite to flush the frame to the underlying writer.
func (f *Framer) startWriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
if !validStreamID(streamID) && !f.AllowIllegalWrites { if !validStreamID(streamID) && !f.AllowIllegalWrites {
return errStreamID return errStreamID
} }
@ -691,7 +703,7 @@ func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []by
} }
f.wbuf = append(f.wbuf, data...) f.wbuf = append(f.wbuf, data...)
f.wbuf = append(f.wbuf, pad...) f.wbuf = append(f.wbuf, pad...)
return f.endWrite() return nil
} }
// A SettingsFrame conveys configuration parameters that affect how // A SettingsFrame conveys configuration parameters that affect how
@ -1501,19 +1513,18 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
} }
func (fr *Framer) maxHeaderStringLen() int { func (fr *Framer) maxHeaderStringLen() int {
v := fr.maxHeaderListSize() v := int(fr.maxHeaderListSize())
if uint32(int(v)) == v { if v < 0 {
return int(v) // If maxHeaderListSize overflows an int, use no limit (0).
return 0
} }
// They had a crazy big number for MaxHeaderBytes anyway, return v
// so give them unlimited header lengths:
return 0
} }
// readMetaFrame returns 0 or more CONTINUATION frames from fr and // readMetaFrame returns 0 or more CONTINUATION frames from fr and
// merge them into the provided hf and returns a MetaHeadersFrame // merge them into the provided hf and returns a MetaHeadersFrame
// with the decoded hpack values. // with the decoded hpack values.
func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) { func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) {
if fr.AllowIllegalReads { if fr.AllowIllegalReads {
return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders") return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders")
} }
@ -1556,6 +1567,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
if size > remainSize { if size > remainSize {
hdec.SetEmitEnabled(false) hdec.SetEmitEnabled(false)
mh.Truncated = true mh.Truncated = true
remainSize = 0
return return
} }
remainSize -= size remainSize -= size
@ -1568,8 +1580,38 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
var hc headersOrContinuation = hf var hc headersOrContinuation = hf
for { for {
frag := hc.HeaderBlockFragment() frag := hc.HeaderBlockFragment()
// Avoid parsing large amounts of headers that we will then discard.
// If the sender exceeds the max header list size by too much,
// skip parsing the fragment and close the connection.
//
// "Too much" is either any CONTINUATION frame after we've already
// exceeded the max header list size (in which case remainSize is 0),
// or a frame whose encoded size is more than twice the remaining
// header list bytes we're willing to accept.
if int64(len(frag)) > int64(2*remainSize) {
if VerboseLogs {
log.Printf("http2: header list too large")
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return mh, ConnectionError(ErrCodeProtocol)
}
// Also close the connection after any CONTINUATION frame following an
// invalid header, since we stop tracking the size of the headers after
// an invalid one.
if invalid != nil {
if VerboseLogs {
log.Printf("http2: invalid header: %v", invalid)
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return mh, ConnectionError(ErrCodeProtocol)
}
if _, err := hdec.Write(frag); err != nil { if _, err := hdec.Write(frag); err != nil {
return nil, ConnectionError(ErrCodeCompression) return mh, ConnectionError(ErrCodeCompression)
} }
if hc.HeadersEnded() { if hc.HeadersEnded() {
@ -1586,7 +1628,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
mh.HeadersFrame.invalidate() mh.HeadersFrame.invalidate()
if err := hdec.Close(); err != nil { if err := hdec.Close(); err != nil {
return nil, ConnectionError(ErrCodeCompression) return mh, ConnectionError(ErrCodeCompression)
} }
if invalid != nil { if invalid != nil {
fr.errDetail = invalid fr.errDetail = invalid

View File

@ -1,30 +0,0 @@
// 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.
//go:build go1.11
// +build go1.11
package http2
import (
"net/http/httptrace"
"net/textproto"
)
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
return trace != nil && trace.WroteHeaderField != nil
}
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(k, []string{v})
}
}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
}
return nil
}

View File

@ -1,27 +0,0 @@
// Copyright 2021 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.
//go:build go1.15
// +build go1.15
package http2
import (
"context"
"crypto/tls"
)
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
// connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
dialer := &tls.Dialer{
Config: cfg,
}
cn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
return tlsCn, nil
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 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.
//go:build go1.18
// +build go1.18
package http2
import (
"crypto/tls"
"net"
)
func tlsUnderlyingConn(tc *tls.Conn) net.Conn {
return tc.NetConn()
}

View File

@ -44,7 +44,7 @@ func init() {
// HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
// h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
// h2c. When either of those situations occur we hijack the HTTP/1 connection, // h2c. When either of those situations occur we hijack the HTTP/1 connection,
// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. // convert it to an HTTP/2 connection and pass the net.Conn to http2.ServeConn.
type h2cHandler struct { type h2cHandler struct {
Handler http.Handler Handler http.Handler
s *http2.Server s *http2.Server

View File

@ -211,7 +211,7 @@ func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
return dt.ents[dt.len()-(int(i)-staticTable.len())], true return dt.ents[dt.len()-(int(i)-staticTable.len())], true
} }
// Decode decodes an entire block. // DecodeFull decodes an entire block.
// //
// TODO: remove this method and make it incremental later? This is // TODO: remove this method and make it incremental later? This is
// easier for debugging now. // easier for debugging now.
@ -359,6 +359,7 @@ func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
var hf HeaderField var hf HeaderField
wantStr := d.emitEnabled || it.indexed() wantStr := d.emitEnabled || it.indexed()
var undecodedName undecodedString
if nameIdx > 0 { if nameIdx > 0 {
ihf, ok := d.at(nameIdx) ihf, ok := d.at(nameIdx)
if !ok { if !ok {
@ -366,15 +367,27 @@ func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
} }
hf.Name = ihf.Name hf.Name = ihf.Name
} else { } else {
hf.Name, buf, err = d.readString(buf, wantStr) undecodedName, buf, err = d.readString(buf)
if err != nil { if err != nil {
return err return err
} }
} }
hf.Value, buf, err = d.readString(buf, wantStr) undecodedValue, buf, err := d.readString(buf)
if err != nil { if err != nil {
return err return err
} }
if wantStr {
if nameIdx <= 0 {
hf.Name, err = d.decodeString(undecodedName)
if err != nil {
return err
}
}
hf.Value, err = d.decodeString(undecodedValue)
if err != nil {
return err
}
}
d.buf = buf d.buf = buf
if it.indexed() { if it.indexed() {
d.dynTab.add(hf) d.dynTab.add(hf)
@ -459,46 +472,52 @@ func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) {
return 0, origP, errNeedMore return 0, origP, errNeedMore
} }
// readString decodes an hpack string from p. // readString reads an hpack string from p.
// //
// wantStr is whether s will be used. If false, decompression and // It returns a reference to the encoded string data to permit deferring decode costs
// []byte->string garbage are skipped if s will be ignored // until after the caller verifies all data is present.
// anyway. This does mean that huffman decoding errors for non-indexed func (d *Decoder) readString(p []byte) (u undecodedString, remain []byte, err error) {
// strings past the MAX_HEADER_LIST_SIZE are ignored, but the server
// is returning an error anyway, and because they're not indexed, the error
// won't affect the decoding state.
func (d *Decoder) readString(p []byte, wantStr bool) (s string, remain []byte, err error) {
if len(p) == 0 { if len(p) == 0 {
return "", p, errNeedMore return u, p, errNeedMore
} }
isHuff := p[0]&128 != 0 isHuff := p[0]&128 != 0
strLen, p, err := readVarInt(7, p) strLen, p, err := readVarInt(7, p)
if err != nil { if err != nil {
return "", p, err return u, p, err
} }
if d.maxStrLen != 0 && strLen > uint64(d.maxStrLen) { if d.maxStrLen != 0 && strLen > uint64(d.maxStrLen) {
return "", nil, ErrStringLength // Returning an error here means Huffman decoding errors
// for non-indexed strings past the maximum string length
// are ignored, but the server is returning an error anyway
// and because the string is not indexed the error will not
// affect the decoding state.
return u, nil, ErrStringLength
} }
if uint64(len(p)) < strLen { if uint64(len(p)) < strLen {
return "", p, errNeedMore return u, p, errNeedMore
} }
if !isHuff { u.isHuff = isHuff
if wantStr { u.b = p[:strLen]
s = string(p[:strLen]) return u, p[strLen:], nil
} }
return s, p[strLen:], nil
} type undecodedString struct {
isHuff bool
if wantStr { b []byte
buf := bufPool.Get().(*bytes.Buffer) }
buf.Reset() // don't trust others
defer bufPool.Put(buf) func (d *Decoder) decodeString(u undecodedString) (string, error) {
if err := huffmanDecode(buf, d.maxStrLen, p[:strLen]); err != nil { if !u.isHuff {
buf.Reset() return string(u.b), nil
return "", nil, err }
} buf := bufPool.Get().(*bytes.Buffer)
s = buf.String() buf.Reset() // don't trust others
buf.Reset() // be nice to GC var s string
} err := huffmanDecode(buf, d.maxStrLen, u.b)
return s, p[strLen:], nil if err == nil {
s = buf.String()
}
buf.Reset() // be nice to GC
bufPool.Put(buf)
return s, err
} }

View File

@ -17,6 +17,7 @@ package http2 // import "golang.org/x/net/http2"
import ( import (
"bufio" "bufio"
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
@ -26,6 +27,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
@ -210,12 +212,6 @@ type stringWriter interface {
WriteString(s string) (n int, err error) WriteString(s string) (n int, err error)
} }
// A gate lets two goroutines coordinate their activities.
type gate chan struct{}
func (g gate) Done() { g <- struct{}{} }
func (g gate) Wait() { <-g }
// A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed). // A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed).
type closeWaiter chan struct{} type closeWaiter chan struct{}
@ -383,3 +379,14 @@ func validPseudoPath(v string) bool {
// makes that struct also non-comparable, and generally doesn't add // makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first). // any size (as long as it's first).
type incomparable [0]func() type incomparable [0]func()
// synctestGroupInterface is the methods of synctestGroup used by Server and Transport.
// It's defined as an interface here to let us keep synctestGroup entirely test-only
// and not a part of non-test builds.
type synctestGroupInterface interface {
Join()
Now() time.Time
NewTimer(d time.Duration) timer
AfterFunc(d time.Duration, f func()) timer
ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc)
}

View File

@ -1,21 +0,0 @@
// 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.
//go:build !go1.11
// +build !go1.11
package http2
import (
"net/http/httptrace"
"net/textproto"
)
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { return false }
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
return nil
}

View File

@ -1,31 +0,0 @@
// Copyright 2021 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.
//go:build !go1.15
// +build !go1.15
package http2
import (
"context"
"crypto/tls"
)
// dialTLSWithContext opens a TLS connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
cn, err := tls.Dial(network, addr, cfg)
if err != nil {
return nil, err
}
if err := cn.Handshake(); err != nil {
return nil, err
}
if cfg.InsecureSkipVerify {
return cn, nil
}
if err := cn.VerifyHostname(cfg.ServerName); err != nil {
return nil, err
}
return cn, nil
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 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.
//go:build !go1.18
// +build !go1.18
package http2
import (
"crypto/tls"
"net"
)
func tlsUnderlyingConn(tc *tls.Conn) net.Conn {
return nil
}

View File

@ -77,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
} }
} }
var errClosedPipeWrite = errors.New("write on closed buffer") var (
errClosedPipeWrite = errors.New("write on closed buffer")
errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
)
// Write copies bytes from p into the buffer and wakes a reader. // Write copies bytes from p into the buffer and wakes a reader.
// It is an error to write more data than the buffer can hold. // It is an error to write more data than the buffer can hold.
@ -88,12 +91,14 @@ func (p *pipe) Write(d []byte) (n int, err error) {
p.c.L = &p.mu p.c.L = &p.mu
} }
defer p.c.Signal() defer p.c.Signal()
if p.err != nil { if p.err != nil || p.breakErr != nil {
return 0, errClosedPipeWrite return 0, errClosedPipeWrite
} }
if p.breakErr != nil { // pipe.setBuffer is never invoked, leaving the buffer uninitialized.
p.unread += len(d) // We shouldn't try to write to an uninitialized pipe,
return len(d), nil // discard when there is no reader // but returning an error is better than panicking.
if p.b == nil {
return 0, errUninitializedPipeWrite
} }
return p.b.Write(d) return p.b.Write(d)
} }

View File

@ -124,6 +124,7 @@ type Server struct {
// IdleTimeout specifies how long until idle clients should be // IdleTimeout specifies how long until idle clients should be
// closed with a GOAWAY frame. PING frames are not considered // closed with a GOAWAY frame. PING frames are not considered
// activity for the purposes of IdleTimeout. // activity for the purposes of IdleTimeout.
// If zero or negative, there is no timeout.
IdleTimeout time.Duration IdleTimeout time.Duration
// MaxUploadBufferPerConnection is the size of the initial flow // MaxUploadBufferPerConnection is the size of the initial flow
@ -153,6 +154,39 @@ type Server struct {
// so that we don't embed a Mutex in this struct, which will make the // so that we don't embed a Mutex in this struct, which will make the
// struct non-copyable, which might break some callers. // struct non-copyable, which might break some callers.
state *serverInternalState state *serverInternalState
// Synchronization group used for testing.
// Outside of tests, this is nil.
group synctestGroupInterface
}
func (s *Server) markNewGoroutine() {
if s.group != nil {
s.group.Join()
}
}
func (s *Server) now() time.Time {
if s.group != nil {
return s.group.Now()
}
return time.Now()
}
// newTimer creates a new time.Timer, or a synthetic timer in tests.
func (s *Server) newTimer(d time.Duration) timer {
if s.group != nil {
return s.group.NewTimer(d)
}
return timeTimer{time.NewTimer(d)}
}
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
func (s *Server) afterFunc(d time.Duration, f func()) timer {
if s.group != nil {
return s.group.AfterFunc(d, f)
}
return timeTimer{time.AfterFunc(d, f)}
} }
func (s *Server) initialConnRecvWindowSize() int32 { func (s *Server) initialConnRecvWindowSize() int32 {
@ -399,6 +433,10 @@ func (o *ServeConnOpts) handler() http.Handler {
// //
// The opts parameter is optional. If nil, default values are used. // The opts parameter is optional. If nil, default values are used.
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
s.serveConn(c, opts, nil)
}
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
baseCtx, cancel := serverConnBaseContext(c, opts) baseCtx, cancel := serverConnBaseContext(c, opts)
defer cancel() defer cancel()
@ -425,6 +463,9 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
pushEnabled: true, pushEnabled: true,
sawClientPreface: opts.SawClientPreface, sawClientPreface: opts.SawClientPreface,
} }
if newf != nil {
newf(sc)
}
s.state.registerConn(sc) s.state.registerConn(sc)
defer s.state.unregisterConn(sc) defer s.state.unregisterConn(sc)
@ -434,14 +475,14 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
// passes the connection off to us with the deadline already set. // passes the connection off to us with the deadline already set.
// Write deadlines are set per stream in serverConn.newStream. // Write deadlines are set per stream in serverConn.newStream.
// Disarm the net.Conn write deadline here. // Disarm the net.Conn write deadline here.
if sc.hs.WriteTimeout != 0 { if sc.hs.WriteTimeout > 0 {
sc.conn.SetWriteDeadline(time.Time{}) sc.conn.SetWriteDeadline(time.Time{})
} }
if s.NewWriteScheduler != nil { if s.NewWriteScheduler != nil {
sc.writeSched = s.NewWriteScheduler() sc.writeSched = s.NewWriteScheduler()
} else { } else {
sc.writeSched = NewPriorityWriteScheduler(nil) sc.writeSched = newRoundRobinWriteScheduler()
} }
// These start at the RFC-specified defaults. If there is a higher // These start at the RFC-specified defaults. If there is a higher
@ -581,9 +622,11 @@ type serverConn struct {
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
curClientStreams uint32 // number of open streams initiated by the client curClientStreams uint32 // number of open streams initiated by the client
curPushedStreams uint32 // number of open streams initiated by server push curPushedStreams uint32 // number of open streams initiated by server push
curHandlers uint32 // number of running handler goroutines
maxClientStreamID uint32 // max ever seen from client (odd), or 0 if there have been no client requests maxClientStreamID uint32 // max ever seen from client (odd), or 0 if there have been no client requests
maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes
streams map[uint32]*stream streams map[uint32]*stream
unstartedHandlers []unstartedHandler
initialStreamSendWindowSize int32 initialStreamSendWindowSize int32
maxFrameSize int32 maxFrameSize int32
peerMaxHeaderListSize uint32 // zero means unknown (default) peerMaxHeaderListSize uint32 // zero means unknown (default)
@ -596,8 +639,8 @@ type serverConn struct {
inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop
needToSendGoAway bool // we need to schedule a GOAWAY frame write needToSendGoAway bool // we need to schedule a GOAWAY frame write
goAwayCode ErrCode goAwayCode ErrCode
shutdownTimer *time.Timer // nil until used shutdownTimer timer // nil until used
idleTimer *time.Timer // nil if unused idleTimer timer // nil if unused
// Owned by the writeFrameAsync goroutine: // Owned by the writeFrameAsync goroutine:
headerWriteBuf bytes.Buffer headerWriteBuf bytes.Buffer
@ -646,12 +689,12 @@ type stream struct {
flow outflow // limits writing from Handler to client flow outflow // limits writing from Handler to client
inflow inflow // what the client is allowed to POST/etc to us inflow inflow // what the client is allowed to POST/etc to us
state streamState state streamState
resetQueued bool // RST_STREAM queued for write; set by sc.resetStream resetQueued bool // RST_STREAM queued for write; set by sc.resetStream
gotTrailerHeader bool // HEADER frame for trailers was seen gotTrailerHeader bool // HEADER frame for trailers was seen
wroteHeaders bool // whether we wrote headers (not status 100) wroteHeaders bool // whether we wrote headers (not status 100)
readDeadline *time.Timer // nil if unused readDeadline timer // nil if unused
writeDeadline *time.Timer // nil if unused writeDeadline timer // nil if unused
closeErr error // set before cw is closed closeErr error // set before cw is closed
trailer http.Header // accumulated trailers trailer http.Header // accumulated trailers
reqTrailer http.Header // handler's Request.Trailer reqTrailer http.Header // handler's Request.Trailer
@ -729,11 +772,7 @@ func isClosedConnError(err error) bool {
return false return false
} }
// TODO: remove this string search and be more like the Windows if errors.Is(err, net.ErrClosed) {
// case below. That might involve modifying the standard library
// to return better error types.
str := err.Error()
if strings.Contains(str, "use of closed network connection") {
return true return true
} }
@ -812,8 +851,9 @@ type readFrameResult struct {
// consumer is done with the frame. // consumer is done with the frame.
// It's run on its own goroutine. // It's run on its own goroutine.
func (sc *serverConn) readFrames() { func (sc *serverConn) readFrames() {
gate := make(gate) sc.srv.markNewGoroutine()
gateDone := gate.Done gate := make(chan struct{})
gateDone := func() { gate <- struct{}{} }
for { for {
f, err := sc.framer.ReadFrame() f, err := sc.framer.ReadFrame()
select { select {
@ -843,8 +883,14 @@ type frameWriteResult struct {
// and then reports when it's done. // and then reports when it's done.
// At most one goroutine can be running writeFrameAsync at a time per // At most one goroutine can be running writeFrameAsync at a time per
// serverConn. // serverConn.
func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest) { func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest, wd *writeData) {
err := wr.write.writeFrame(sc) sc.srv.markNewGoroutine()
var err error
if wd == nil {
err = wr.write.writeFrame(sc)
} else {
err = sc.framer.endWrite()
}
sc.wroteFrameCh <- frameWriteResult{wr: wr, err: err} sc.wroteFrameCh <- frameWriteResult{wr: wr, err: err}
} }
@ -917,14 +963,14 @@ func (sc *serverConn) serve() {
sc.setConnState(http.StateActive) sc.setConnState(http.StateActive)
sc.setConnState(http.StateIdle) sc.setConnState(http.StateIdle)
if sc.srv.IdleTimeout != 0 { if sc.srv.IdleTimeout > 0 {
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer) sc.idleTimer = sc.srv.afterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
defer sc.idleTimer.Stop() defer sc.idleTimer.Stop()
} }
go sc.readFrames() // closed by defer sc.conn.Close above go sc.readFrames() // closed by defer sc.conn.Close above
settingsTimer := time.AfterFunc(firstSettingsTimeout, sc.onSettingsTimer) settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer)
defer settingsTimer.Stop() defer settingsTimer.Stop()
loopNum := 0 loopNum := 0
@ -976,6 +1022,8 @@ func (sc *serverConn) serve() {
return return
case gracefulShutdownMsg: case gracefulShutdownMsg:
sc.startGracefulShutdownInternal() sc.startGracefulShutdownInternal()
case handlerDoneMsg:
sc.handlerDone()
default: default:
panic("unknown timer") panic("unknown timer")
} }
@ -1007,14 +1055,6 @@ func (sc *serverConn) serve() {
} }
} }
func (sc *serverConn) awaitGracefulShutdown(sharedCh <-chan struct{}, privateCh chan struct{}) {
select {
case <-sc.doneServing:
case <-sharedCh:
close(privateCh)
}
}
type serverMessage int type serverMessage int
// Message values sent to serveMsgCh. // Message values sent to serveMsgCh.
@ -1023,6 +1063,7 @@ var (
idleTimerMsg = new(serverMessage) idleTimerMsg = new(serverMessage)
shutdownTimerMsg = new(serverMessage) shutdownTimerMsg = new(serverMessage)
gracefulShutdownMsg = new(serverMessage) gracefulShutdownMsg = new(serverMessage)
handlerDoneMsg = new(serverMessage)
) )
func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) } func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) }
@ -1058,10 +1099,10 @@ func (sc *serverConn) readPreface() error {
errc <- nil errc <- nil
} }
}() }()
timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server? timer := sc.srv.newTimer(prefaceTimeout) // TODO: configurable on *Server?
defer timer.Stop() defer timer.Stop()
select { select {
case <-timer.C: case <-timer.C():
return errPrefaceTimeout return errPrefaceTimeout
case err := <-errc: case err := <-errc:
if err == nil { if err == nil {
@ -1251,9 +1292,16 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) {
sc.writingFrameAsync = false sc.writingFrameAsync = false
err := wr.write.writeFrame(sc) err := wr.write.writeFrame(sc)
sc.wroteFrame(frameWriteResult{wr: wr, err: err}) sc.wroteFrame(frameWriteResult{wr: wr, err: err})
} else if wd, ok := wr.write.(*writeData); ok {
// Encode the frame in the serve goroutine, to ensure we don't have
// any lingering asynchronous references to data passed to Write.
// See https://go.dev/issue/58446.
sc.framer.startWriteDataPadded(wd.streamID, wd.endStream, wd.p, nil)
sc.writingFrameAsync = true
go sc.writeFrameAsync(wr, wd)
} else { } else {
sc.writingFrameAsync = true sc.writingFrameAsync = true
go sc.writeFrameAsync(wr) go sc.writeFrameAsync(wr, nil)
} }
} }
@ -1419,7 +1467,7 @@ func (sc *serverConn) goAway(code ErrCode) {
func (sc *serverConn) shutDownIn(d time.Duration) { func (sc *serverConn) shutDownIn(d time.Duration) {
sc.serveG.check() sc.serveG.check()
sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer) sc.shutdownTimer = sc.srv.afterFunc(d, sc.onShutdownTimer)
} }
func (sc *serverConn) resetStream(se StreamError) { func (sc *serverConn) resetStream(se StreamError) {
@ -1472,6 +1520,11 @@ func (sc *serverConn) processFrameFromReader(res readFrameResult) bool {
sc.goAway(ErrCodeFlowControl) sc.goAway(ErrCodeFlowControl)
return true return true
case ConnectionError: case ConnectionError:
if res.f != nil {
if id := res.f.Header().StreamID; id > sc.maxClientStreamID {
sc.maxClientStreamID = id
}
}
sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev) sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev)
sc.goAway(ErrCode(ev)) sc.goAway(ErrCode(ev))
return true // goAway will handle shutdown return true // goAway will handle shutdown
@ -1628,7 +1681,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
delete(sc.streams, st.id) delete(sc.streams, st.id)
if len(sc.streams) == 0 { if len(sc.streams) == 0 {
sc.setConnState(http.StateIdle) sc.setConnState(http.StateIdle)
if sc.srv.IdleTimeout != 0 { if sc.srv.IdleTimeout > 0 && sc.idleTimer != nil {
sc.idleTimer.Reset(sc.srv.IdleTimeout) sc.idleTimer.Reset(sc.srv.IdleTimeout)
} }
if h1ServerKeepAlivesDisabled(sc.hs) { if h1ServerKeepAlivesDisabled(sc.hs) {
@ -1650,6 +1703,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
} }
} }
st.closeErr = err st.closeErr = err
st.cancelCtx()
st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc
sc.writeSched.CloseStream(st.id) sc.writeSched.CloseStream(st.id)
} }
@ -1810,15 +1864,18 @@ func (sc *serverConn) processData(f *DataFrame) error {
} }
if len(data) > 0 { if len(data) > 0 {
st.bodyBytes += int64(len(data))
wrote, err := st.body.Write(data) wrote, err := st.body.Write(data)
if err != nil { if err != nil {
// The handler has closed the request body.
// Return the connection-level flow control for the discarded data,
// but not the stream-level flow control.
sc.sendWindowUpdate(nil, int(f.Length)-wrote) sc.sendWindowUpdate(nil, int(f.Length)-wrote)
return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed)) return nil
} }
if wrote != len(data) { if wrote != len(data) {
panic("internal error: bad Writer") panic("internal error: bad Writer")
} }
st.bodyBytes += int64(len(data))
} }
// Return any padded flow control now, since we won't // Return any padded flow control now, since we won't
@ -1885,9 +1942,11 @@ func (st *stream) copyTrailersToHandlerRequest() {
// onReadTimeout is run on its own goroutine (from time.AfterFunc) // onReadTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's ReadTimeout has fired. // when the stream's ReadTimeout has fired.
func (st *stream) onReadTimeout() { func (st *stream) onReadTimeout() {
// Wrap the ErrDeadlineExceeded to avoid callers depending on us if st.body != nil {
// returning the bare error. // Wrap the ErrDeadlineExceeded to avoid callers depending on us
st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded)) // returning the bare error.
st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded))
}
} }
// onWriteTimeout is run on its own goroutine (from time.AfterFunc) // onWriteTimeout is run on its own goroutine (from time.AfterFunc)
@ -2003,15 +2062,12 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
// similar to how the http1 server works. Here it's // similar to how the http1 server works. Here it's
// technically more like the http1 Server's ReadHeaderTimeout // technically more like the http1 Server's ReadHeaderTimeout
// (in Go 1.8), though. That's a more sane option anyway. // (in Go 1.8), though. That's a more sane option anyway.
if sc.hs.ReadTimeout != 0 { if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{}) sc.conn.SetReadDeadline(time.Time{})
if st.body != nil { st.readDeadline = sc.srv.afterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
}
} }
go sc.runHandler(rw, req, handler) return sc.scheduleHandler(id, rw, req, handler)
return nil
} }
func (sc *serverConn) upgradeRequest(req *http.Request) { func (sc *serverConn) upgradeRequest(req *http.Request) {
@ -2027,10 +2083,14 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
// Disable any read deadline set by the net/http package // Disable any read deadline set by the net/http package
// prior to the upgrade. // prior to the upgrade.
if sc.hs.ReadTimeout != 0 { if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{}) sc.conn.SetReadDeadline(time.Time{})
} }
// This is the first request on the connection,
// so start the handler directly rather than going
// through scheduleHandler.
sc.curHandlers++
go sc.runHandler(rw, req, sc.handler.ServeHTTP) go sc.runHandler(rw, req, sc.handler.ServeHTTP)
} }
@ -2101,8 +2161,8 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
st.flow.conn = &sc.flow // link to conn-level counter st.flow.conn = &sc.flow // link to conn-level counter
st.flow.add(sc.initialStreamSendWindowSize) st.flow.add(sc.initialStreamSendWindowSize)
st.inflow.init(sc.srv.initialStreamRecvWindowSize()) st.inflow.init(sc.srv.initialStreamRecvWindowSize())
if sc.hs.WriteTimeout != 0 { if sc.hs.WriteTimeout > 0 {
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
} }
sc.streams[id] = st sc.streams[id] = st
@ -2192,7 +2252,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
tlsState = sc.tlsState tlsState = sc.tlsState
} }
needsContinue := rp.header.Get("Expect") == "100-continue" needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
if needsContinue { if needsContinue {
rp.header.Del("Expect") rp.header.Del("Expect")
} }
@ -2271,8 +2331,63 @@ func (sc *serverConn) newResponseWriter(st *stream, req *http.Request) *response
return &responseWriter{rws: rws} return &responseWriter{rws: rws}
} }
type unstartedHandler struct {
streamID uint32
rw *responseWriter
req *http.Request
handler func(http.ResponseWriter, *http.Request)
}
// scheduleHandler starts a handler goroutine,
// or schedules one to start as soon as an existing handler finishes.
func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error {
sc.serveG.check()
maxHandlers := sc.advMaxStreams
if sc.curHandlers < maxHandlers {
sc.curHandlers++
go sc.runHandler(rw, req, handler)
return nil
}
if len(sc.unstartedHandlers) > int(4*sc.advMaxStreams) {
return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm))
}
sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{
streamID: streamID,
rw: rw,
req: req,
handler: handler,
})
return nil
}
func (sc *serverConn) handlerDone() {
sc.serveG.check()
sc.curHandlers--
i := 0
maxHandlers := sc.advMaxStreams
for ; i < len(sc.unstartedHandlers); i++ {
u := sc.unstartedHandlers[i]
if sc.streams[u.streamID] == nil {
// This stream was reset before its goroutine had a chance to start.
continue
}
if sc.curHandlers >= maxHandlers {
break
}
sc.curHandlers++
go sc.runHandler(u.rw, u.req, u.handler)
sc.unstartedHandlers[i] = unstartedHandler{} // don't retain references
}
sc.unstartedHandlers = sc.unstartedHandlers[i:]
if len(sc.unstartedHandlers) == 0 {
sc.unstartedHandlers = nil
}
}
// Run on its own goroutine. // Run on its own goroutine.
func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) { func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) {
sc.srv.markNewGoroutine()
defer sc.sendServeMsg(handlerDoneMsg)
didPanic := true didPanic := true
defer func() { defer func() {
rw.rws.stream.cancelCtx() rw.rws.stream.cancelCtx()
@ -2414,7 +2529,7 @@ type requestBody struct {
conn *serverConn conn *serverConn
closeOnce sync.Once // for use by Close only closeOnce sync.Once // for use by Close only
sawEOF bool // for use by Read only sawEOF bool // for use by Read only
pipe *pipe // non-nil if we have a HTTP entity message body pipe *pipe // non-nil if we have an HTTP entity message body
needsContinue bool // need to send a 100-continue needsContinue bool // need to send a 100-continue
} }
@ -2480,7 +2595,6 @@ type responseWriterState struct {
wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet. wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
sentHeader bool // have we sent the header frame? sentHeader bool // have we sent the header frame?
handlerDone bool // handler has finished handlerDone bool // handler has finished
dirty bool // a Write failed; don't reuse this responseWriterState
sentContentLen int64 // non-zero if handler set a Content-Length header sentContentLen int64 // non-zero if handler set a Content-Length header
wroteBytes int64 wroteBytes int64
@ -2554,7 +2668,8 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
clen = "" clen = ""
} }
} }
if clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { _, hasContentLength := rws.snapHeader["Content-Length"]
if !hasContentLength && clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) {
clen = strconv.Itoa(len(p)) clen = strconv.Itoa(len(p))
} }
_, hasContentType := rws.snapHeader["Content-Type"] _, hasContentType := rws.snapHeader["Content-Type"]
@ -2568,7 +2683,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
var date string var date string
if _, ok := rws.snapHeader["Date"]; !ok { if _, ok := rws.snapHeader["Date"]; !ok {
// TODO(bradfitz): be faster here, like net/http? measure. // TODO(bradfitz): be faster here, like net/http? measure.
date = time.Now().UTC().Format(http.TimeFormat) date = rws.conn.srv.now().UTC().Format(http.TimeFormat)
} }
for _, v := range rws.snapHeader["Trailer"] { for _, v := range rws.snapHeader["Trailer"] {
@ -2599,7 +2714,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
date: date, date: date,
}) })
if err != nil { if err != nil {
rws.dirty = true
return 0, err return 0, err
} }
if endStream { if endStream {
@ -2620,7 +2734,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
if len(p) > 0 || endStream { if len(p) > 0 || endStream {
// only send a 0 byte DATA frame if we're ending the stream. // only send a 0 byte DATA frame if we're ending the stream.
if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil { if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
rws.dirty = true
return 0, err return 0, err
} }
} }
@ -2632,9 +2745,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
trailers: rws.trailers, trailers: rws.trailers,
endStream: true, endStream: true,
}) })
if err != nil {
rws.dirty = true
}
return len(p), err return len(p), err
} }
return len(p), nil return len(p), nil
@ -2695,7 +2805,7 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
func (w *responseWriter) SetReadDeadline(deadline time.Time) error { func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
st := w.rws.stream st := w.rws.stream
if !deadline.IsZero() && deadline.Before(time.Now()) { if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
// If we're setting a deadline in the past, reset the stream immediately // If we're setting a deadline in the past, reset the stream immediately
// so writes after SetWriteDeadline returns will fail. // so writes after SetWriteDeadline returns will fail.
st.onReadTimeout() st.onReadTimeout()
@ -2711,9 +2821,9 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
if deadline.IsZero() { if deadline.IsZero() {
st.readDeadline = nil st.readDeadline = nil
} else if st.readDeadline == nil { } else if st.readDeadline == nil {
st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout) st.readDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onReadTimeout)
} else { } else {
st.readDeadline.Reset(deadline.Sub(time.Now())) st.readDeadline.Reset(deadline.Sub(sc.srv.now()))
} }
}) })
return nil return nil
@ -2721,7 +2831,7 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
func (w *responseWriter) SetWriteDeadline(deadline time.Time) error { func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
st := w.rws.stream st := w.rws.stream
if !deadline.IsZero() && deadline.Before(time.Now()) { if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
// If we're setting a deadline in the past, reset the stream immediately // If we're setting a deadline in the past, reset the stream immediately
// so writes after SetWriteDeadline returns will fail. // so writes after SetWriteDeadline returns will fail.
st.onWriteTimeout() st.onWriteTimeout()
@ -2737,9 +2847,9 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
if deadline.IsZero() { if deadline.IsZero() {
st.writeDeadline = nil st.writeDeadline = nil
} else if st.writeDeadline == nil { } else if st.writeDeadline == nil {
st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout) st.writeDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onWriteTimeout)
} else { } else {
st.writeDeadline.Reset(deadline.Sub(time.Now())) st.writeDeadline.Reset(deadline.Sub(sc.srv.now()))
} }
}) })
return nil return nil
@ -2759,7 +2869,7 @@ func (w *responseWriter) FlushError() error {
err = rws.bw.Flush() err = rws.bw.Flush()
} else { } else {
// The bufio.Writer won't call chunkWriter.Write // The bufio.Writer won't call chunkWriter.Write
// (writeChunk with zero bytes, so we have to do it // (writeChunk with zero bytes), so we have to do it
// ourselves to force the HTTP response header and/or // ourselves to force the HTTP response header and/or
// final DATA frame (with END_STREAM) to be sent. // final DATA frame (with END_STREAM) to be sent.
_, err = chunkWriter{rws}.Write(nil) _, err = chunkWriter{rws}.Write(nil)
@ -2850,14 +2960,12 @@ func (rws *responseWriterState) writeHeader(code int) {
h.Del("Transfer-Encoding") h.Del("Transfer-Encoding")
} }
if rws.conn.writeHeaders(rws.stream, &writeResHeaders{ rws.conn.writeHeaders(rws.stream, &writeResHeaders{
streamID: rws.stream.id, streamID: rws.stream.id,
httpResCode: code, httpResCode: code,
h: h, h: h,
endStream: rws.handlerDone && !rws.hasTrailers(), endStream: rws.handlerDone && !rws.hasTrailers(),
}) != nil { })
rws.dirty = true
}
return return
} }
@ -2922,19 +3030,10 @@ func (w *responseWriter) write(lenData int, dataB []byte, dataS string) (n int,
func (w *responseWriter) handlerDone() { func (w *responseWriter) handlerDone() {
rws := w.rws rws := w.rws
dirty := rws.dirty
rws.handlerDone = true rws.handlerDone = true
w.Flush() w.Flush()
w.rws = nil w.rws = nil
if !dirty { responseWriterStatePool.Put(rws)
// Only recycle the pool if all prior Write calls to
// the serverConn goroutine completed successfully. If
// they returned earlier due to resets from the peer
// there might still be write goroutines outstanding
// from the serverConn referencing the rws memory. See
// issue 20704.
responseWriterStatePool.Put(rws)
}
} }
// Push errors. // Push errors.
@ -3117,6 +3216,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
panic(fmt.Sprintf("newWriterAndRequestNoBody(%+v): %v", msg.url, err)) panic(fmt.Sprintf("newWriterAndRequestNoBody(%+v): %v", msg.url, err))
} }
sc.curHandlers++
go sc.runHandler(rw, req, sc.handler.ServeHTTP) go sc.runHandler(rw, req, sc.handler.ServeHTTP)
return promisedID, nil return promisedID, nil
} }

20
vendor/golang.org/x/net/http2/timer.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import "time"
// A timer is a time.Timer, as an interface which can be replaced in tests.
type timer = interface {
C() <-chan time.Time
Reset(d time.Duration) bool
Stop() bool
}
// timeTimer adapts a time.Timer to the timer interface.
type timeTimer struct {
*time.Timer
}
func (t timeTimer) C() <-chan time.Time { return t.Timer.C }

View File

@ -19,6 +19,7 @@ import (
"io/fs" "io/fs"
"log" "log"
"math" "math"
"math/bits"
mathrand "math/rand" mathrand "math/rand"
"net" "net"
"net/http" "net/http"
@ -146,6 +147,12 @@ type Transport struct {
// waiting for their turn. // waiting for their turn.
StrictMaxConcurrentStreams bool StrictMaxConcurrentStreams bool
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration
// ReadIdleTimeout is the timeout after which a health check using ping // ReadIdleTimeout is the timeout after which a health check using ping
// frame will be carried out if no frame is received on the connection. // frame will be carried out if no frame is received on the connection.
// Note that a ping response will is considered a received frame, so if // Note that a ping response will is considered a received frame, so if
@ -177,6 +184,46 @@ type Transport struct {
connPoolOnce sync.Once connPoolOnce sync.Once
connPoolOrDef ClientConnPool // non-nil version of ConnPool connPoolOrDef ClientConnPool // non-nil version of ConnPool
*transportTestHooks
}
// Hook points used for testing.
// Outside of tests, t.transportTestHooks is nil and these all have minimal implementations.
// Inside tests, see the testSyncHooks function docs.
type transportTestHooks struct {
newclientconn func(*ClientConn)
group synctestGroupInterface
}
func (t *Transport) markNewGoroutine() {
if t != nil && t.transportTestHooks != nil {
t.transportTestHooks.group.Join()
}
}
// newTimer creates a new time.Timer, or a synthetic timer in tests.
func (t *Transport) newTimer(d time.Duration) timer {
if t.transportTestHooks != nil {
return t.transportTestHooks.group.NewTimer(d)
}
return timeTimer{time.NewTimer(d)}
}
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
func (t *Transport) afterFunc(d time.Duration, f func()) timer {
if t.transportTestHooks != nil {
return t.transportTestHooks.group.AfterFunc(d, f)
}
return timeTimer{time.AfterFunc(d, f)}
}
func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
if t.transportTestHooks != nil {
return t.transportTestHooks.group.ContextWithTimeout(ctx, d)
}
return context.WithTimeout(ctx, d)
} }
func (t *Transport) maxHeaderListSize() uint32 { func (t *Transport) maxHeaderListSize() uint32 {
@ -290,8 +337,7 @@ func (t *Transport) initConnPool() {
// HTTP/2 server. // HTTP/2 server.
type ClientConn struct { type ClientConn struct {
t *Transport t *Transport
tconn net.Conn // usually *tls.Conn, except specialized impls tconn net.Conn // usually *tls.Conn, except specialized impls
tconnClosed bool
tlsState *tls.ConnectionState // nil only for specialized impls tlsState *tls.ConnectionState // nil only for specialized impls
reused uint32 // whether conn is being reused; atomic reused uint32 // whether conn is being reused; atomic
singleUse bool // whether being used for a single http.Request singleUse bool // whether being used for a single http.Request
@ -302,7 +348,7 @@ type ClientConn struct {
readerErr error // set before readerDone is closed readerErr error // set before readerDone is closed
idleTimeout time.Duration // or 0 for never idleTimeout time.Duration // or 0 for never
idleTimer *time.Timer idleTimer timer
mu sync.Mutex // guards following mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes cond *sync.Cond // hold mu; broadcast on flow/closed changes
@ -446,6 +492,7 @@ func (cs *clientStream) closeReqBodyLocked() {
cs.reqBodyClosed = make(chan struct{}) cs.reqBodyClosed = make(chan struct{})
reqBodyClosed := cs.reqBodyClosed reqBodyClosed := cs.reqBodyClosed
go func() { go func() {
cs.cc.t.markNewGoroutine()
cs.reqBody.Close() cs.reqBody.Close()
close(reqBodyClosed) close(reqBodyClosed)
}() }()
@ -518,11 +565,14 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
func authorityAddr(scheme string, authority string) (addr string) { func authorityAddr(scheme string, authority string) (addr string) {
host, port, err := net.SplitHostPort(authority) host, port, err := net.SplitHostPort(authority)
if err != nil { // authority didn't have a port if err != nil { // authority didn't have a port
host = authority
port = ""
}
if port == "" { // authority's port was empty
port = "443" port = "443"
if scheme == "http" { if scheme == "http" {
port = "80" port = "80"
} }
host = authority
} }
if a, err := idna.ToASCII(host); err == nil { if a, err := idna.ToASCII(host); err == nil {
host = a host = a
@ -534,15 +584,6 @@ func authorityAddr(scheme string, authority string) (addr string) {
return net.JoinHostPort(host, port) return net.JoinHostPort(host, port)
} }
var retryBackoffHook func(time.Duration) *time.Timer
func backoffNewTimer(d time.Duration) *time.Timer {
if retryBackoffHook != nil {
return retryBackoffHook(d)
}
return time.NewTimer(d)
}
// RoundTripOpt is like RoundTrip, but takes options. // RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) { if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@ -560,22 +601,23 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
traceGotConn(req, cc, reused) traceGotConn(req, cc, reused)
res, err := cc.RoundTrip(req) res, err := cc.RoundTrip(req)
if err != nil && retry <= 6 { if err != nil && retry <= 6 {
roundTripErr := err
if req, err = shouldRetryRequest(req, err); err == nil { if req, err = shouldRetryRequest(req, err); err == nil {
// After the first retry, do exponential backoff with 10% jitter. // After the first retry, do exponential backoff with 10% jitter.
if retry == 0 { if retry == 0 {
t.vlogf("RoundTrip retrying after failure: %v", err) t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
continue continue
} }
backoff := float64(uint(1) << (uint(retry) - 1)) backoff := float64(uint(1) << (uint(retry) - 1))
backoff += backoff * (0.1 * mathrand.Float64()) backoff += backoff * (0.1 * mathrand.Float64())
d := time.Second * time.Duration(backoff) d := time.Second * time.Duration(backoff)
timer := backoffNewTimer(d) tm := t.newTimer(d)
select { select {
case <-timer.C: case <-tm.C():
t.vlogf("RoundTrip retrying after failure: %v", err) t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
continue continue
case <-req.Context().Done(): case <-req.Context().Done():
timer.Stop() tm.Stop()
err = req.Context().Err() err = req.Context().Err()
} }
} }
@ -654,6 +696,9 @@ func canRetryError(err error) bool {
} }
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) { func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
if t.transportTestHooks != nil {
return t.newClientConn(nil, singleUse)
}
host, _, err := net.SplitHostPort(addr) host, _, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -747,9 +792,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
pings: make(map[[8]byte]chan struct{}), pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1), reqHeaderMu: make(chan struct{}, 1),
} }
if d := t.idleConnTimeout(); d != 0 { if t.transportTestHooks != nil {
cc.idleTimeout = d t.markNewGoroutine()
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout) t.transportTestHooks.newclientconn(cc)
c = cc.tconn
} }
if VerboseLogs { if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
@ -781,10 +827,6 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize()) cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
cc.peerMaxHeaderTableSize = initialHeaderTableSize cc.peerMaxHeaderTableSize = initialHeaderTableSize
if t.AllowHTTP {
cc.nextStreamID = 3
}
if cs, ok := c.(connectionStater); ok { if cs, ok := c.(connectionStater); ok {
state := cs.ConnectionState() state := cs.ConnectionState()
cc.tlsState = &state cc.tlsState = &state
@ -814,6 +856,12 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
return nil, cc.werr return nil, cc.werr
} }
// Start the idle timer after the connection is fully initialized.
if d := t.idleConnTimeout(); d != 0 {
cc.idleTimeout = d
cc.idleTimer = t.afterFunc(d, cc.onIdleTimeout)
}
go cc.readLoop() go cc.readLoop()
return cc, nil return cc, nil
} }
@ -822,7 +870,7 @@ func (cc *ClientConn) healthCheck() {
pingTimeout := cc.t.pingTimeout() pingTimeout := cc.t.pingTimeout()
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will // We don't need to periodically ping in the health check, because the readLoop of ClientConn will
// trigger the healthCheck again if there is no frame received. // trigger the healthCheck again if there is no frame received.
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout) ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout)
defer cancel() defer cancel()
cc.vlogf("http2: Transport sending health check") cc.vlogf("http2: Transport sending health check")
err := cc.Ping(ctx) err := cc.Ping(ctx)
@ -857,7 +905,20 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
} }
last := f.LastStreamID last := f.LastStreamID
for streamID, cs := range cc.streams { for streamID, cs := range cc.streams {
if streamID > last { if streamID <= last {
// The server's GOAWAY indicates that it received this stream.
// It will either finish processing it, or close the connection
// without doing so. Either way, leave the stream alone for now.
continue
}
if streamID == 1 && cc.goAway.ErrCode != ErrCodeNo {
// Don't retry the first stream on a connection if we get a non-NO error.
// If the server is sending an error on a new connection,
// retrying the request on a new one probably isn't going to work.
cs.abortStreamLocked(fmt.Errorf("http2: Transport received GOAWAY from server ErrCode:%v", cc.goAway.ErrCode))
} else {
// Aborting the stream with errClentConnGotGoAway indicates that
// the request should be retried on a new connection.
cs.abortStreamLocked(errClientConnGotGoAway) cs.abortStreamLocked(errClientConnGotGoAway)
} }
} }
@ -1014,7 +1075,7 @@ func (cc *ClientConn) forceCloseConn() {
if !ok { if !ok {
return return
} }
if nc := tlsUnderlyingConn(tc); nc != nil { if nc := tc.NetConn(); nc != nil {
nc.Close() nc.Close()
} }
} }
@ -1053,6 +1114,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
done := make(chan struct{}) done := make(chan struct{})
cancelled := false // guarded by cc.mu cancelled := false // guarded by cc.mu
go func() { go func() {
cc.t.markNewGoroutine()
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() defer cc.mu.Unlock()
for { for {
@ -1211,6 +1273,10 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
} }
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return cc.roundTrip(req, nil)
}
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
ctx := req.Context() ctx := req.Context()
cs := &clientStream{ cs := &clientStream{
cc: cc, cc: cc,
@ -1225,7 +1291,28 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
respHeaderRecv: make(chan struct{}), respHeaderRecv: make(chan struct{}),
donec: make(chan struct{}), donec: make(chan struct{}),
} }
go cs.doRequest(req)
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
if !cc.t.disableCompression() &&
req.Header.Get("Accept-Encoding") == "" &&
req.Header.Get("Range") == "" &&
!cs.isHead {
// Request gzip only, not deflate. Deflate is ambiguous and
// not as universally supported anyway.
// See: https://zlib.net/zlib_faq.html#faq39
//
// Note that we don't request this for HEAD requests,
// due to a bug in nginx:
// http://trac.nginx.org/nginx/ticket/358
// https://golang.org/issue/5522
//
// We don't request gzip if the request is for a range, since
// auto-decoding a portion of a gzipped document will just fail
// anyway. See https://golang.org/issue/8923
cs.requestedGzip = true
}
go cs.doRequest(req, streamf)
waitDone := func() error { waitDone := func() error {
select { select {
@ -1265,6 +1352,29 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return res, nil return res, nil
} }
cancelRequest := func(cs *clientStream, err error) error {
cs.cc.mu.Lock()
bodyClosed := cs.reqBodyClosed
cs.cc.mu.Unlock()
// Wait for the request body to be closed.
//
// If nothing closed the body before now, abortStreamLocked
// will have started a goroutine to close it.
//
// Closing the body before returning avoids a race condition
// with net/http checking its readTrackingBody to see if the
// body was read from or closed. See golang/go#60041.
//
// The body is closed in a separate goroutine without the
// connection mutex held, but dropping the mutex before waiting
// will keep us from holding it indefinitely if the body
// close is slow for some reason.
if bodyClosed != nil {
<-bodyClosed
}
return err
}
for { for {
select { select {
case <-cs.respHeaderRecv: case <-cs.respHeaderRecv:
@ -1284,10 +1394,10 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
case <-ctx.Done(): case <-ctx.Done():
err := ctx.Err() err := ctx.Err()
cs.abortStream(err) cs.abortStream(err)
return nil, err return nil, cancelRequest(cs, err)
case <-cs.reqCancel: case <-cs.reqCancel:
cs.abortStream(errRequestCanceled) cs.abortStream(errRequestCanceled)
return nil, errRequestCanceled return nil, cancelRequest(cs, errRequestCanceled)
} }
} }
} }
@ -1295,8 +1405,9 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
// doRequest runs for the duration of the request lifetime. // doRequest runs for the duration of the request lifetime.
// //
// It sends the request and performs post-request cleanup (closing Request.Body, etc.). // It sends the request and performs post-request cleanup (closing Request.Body, etc.).
func (cs *clientStream) doRequest(req *http.Request) { func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)) {
err := cs.writeRequest(req) cs.cc.t.markNewGoroutine()
err := cs.writeRequest(req, streamf)
cs.cleanupWriteRequest(err) cs.cleanupWriteRequest(err)
} }
@ -1307,7 +1418,7 @@ func (cs *clientStream) doRequest(req *http.Request) {
// //
// It returns non-nil if the request ends otherwise. // It returns non-nil if the request ends otherwise.
// If the returned error is StreamError, the error Code may be used in resetting the stream. // If the returned error is StreamError, the error Code may be used in resetting the stream.
func (cs *clientStream) writeRequest(req *http.Request) (err error) { func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStream)) (err error) {
cc := cs.cc cc := cs.cc
ctx := cs.ctx ctx := cs.ctx
@ -1345,24 +1456,8 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
} }
cc.mu.Unlock() cc.mu.Unlock()
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? if streamf != nil {
if !cc.t.disableCompression() && streamf(cs)
req.Header.Get("Accept-Encoding") == "" &&
req.Header.Get("Range") == "" &&
!cs.isHead {
// Request gzip only, not deflate. Deflate is ambiguous and
// not as universally supported anyway.
// See: https://zlib.net/zlib_faq.html#faq39
//
// Note that we don't request this for HEAD requests,
// due to a bug in nginx:
// http://trac.nginx.org/nginx/ticket/358
// https://golang.org/issue/5522
//
// We don't request gzip if the request is for a range, since
// auto-decoding a portion of a gzipped document will just fail
// anyway. See https://golang.org/issue/8923
cs.requestedGzip = true
} }
continueTimeout := cc.t.expectContinueTimeout() continueTimeout := cc.t.expectContinueTimeout()
@ -1425,9 +1520,9 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
var respHeaderTimer <-chan time.Time var respHeaderTimer <-chan time.Time
var respHeaderRecv chan struct{} var respHeaderRecv chan struct{}
if d := cc.responseHeaderTimeout(); d != 0 { if d := cc.responseHeaderTimeout(); d != 0 {
timer := time.NewTimer(d) timer := cc.t.newTimer(d)
defer timer.Stop() defer timer.Stop()
respHeaderTimer = timer.C respHeaderTimer = timer.C()
respHeaderRecv = cs.respHeaderRecv respHeaderRecv = cs.respHeaderRecv
} }
// Wait until the peer half-closes its end of the stream, // Wait until the peer half-closes its end of the stream,
@ -1569,7 +1664,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
close(cs.donec) close(cs.donec)
} }
// awaitOpenSlotForStream waits until len(streams) < maxConcurrentStreams. // awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
// Must hold cc.mu. // Must hold cc.mu.
func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error { func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
for { for {
@ -1653,7 +1748,27 @@ func (cs *clientStream) frameScratchBufferLen(maxFrameSize int) int {
return int(n) // doesn't truncate; max is 512K return int(n) // doesn't truncate; max is 512K
} }
var bufPool sync.Pool // of *[]byte // Seven bufPools manage different frame sizes. This helps to avoid scenarios where long-running
// streaming requests using small frame sizes occupy large buffers initially allocated for prior
// requests needing big buffers. The size ranges are as follows:
// {0 KB, 16 KB], {16 KB, 32 KB], {32 KB, 64 KB], {64 KB, 128 KB], {128 KB, 256 KB],
// {256 KB, 512 KB], {512 KB, infinity}
// In practice, the maximum scratch buffer size should not exceed 512 KB due to
// frameScratchBufferLen(maxFrameSize), thus the "infinity pool" should never be used.
// It exists mainly as a safety measure, for potential future increases in max buffer size.
var bufPools [7]sync.Pool // of *[]byte
func bufPoolIndex(size int) int {
if size <= 16384 {
return 0
}
size -= 1
bits := bits.Len(uint(size))
index := bits - 14
if index >= len(bufPools) {
return len(bufPools) - 1
}
return index
}
func (cs *clientStream) writeRequestBody(req *http.Request) (err error) { func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
cc := cs.cc cc := cs.cc
@ -1671,12 +1786,13 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
// Scratch buffer for reading into & writing from. // Scratch buffer for reading into & writing from.
scratchLen := cs.frameScratchBufferLen(maxFrameSize) scratchLen := cs.frameScratchBufferLen(maxFrameSize)
var buf []byte var buf []byte
if bp, ok := bufPool.Get().(*[]byte); ok && len(*bp) >= scratchLen { index := bufPoolIndex(scratchLen)
defer bufPool.Put(bp) if bp, ok := bufPools[index].Get().(*[]byte); ok && len(*bp) >= scratchLen {
defer bufPools[index].Put(bp)
buf = *bp buf = *bp
} else { } else {
buf = make([]byte, scratchLen) buf = make([]byte, scratchLen)
defer bufPool.Put(&buf) defer bufPools[index].Put(&buf)
} }
var sawEOF bool var sawEOF bool
@ -1827,6 +1943,22 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
} }
} }
func validateHeaders(hdrs http.Header) string {
for k, vv := range hdrs {
if !httpguts.ValidHeaderFieldName(k) {
return fmt.Sprintf("name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
// Don't include the value in the error,
// because it may be sensitive.
return fmt.Sprintf("value for header %q", k)
}
}
}
return ""
}
var errNilRequestURL = errors.New("http2: Request.URI is nil") var errNilRequestURL = errors.New("http2: Request.URI is nil")
// requires cc.wmu be held. // requires cc.wmu be held.
@ -1844,6 +1976,9 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !httpguts.ValidHostHeader(host) {
return nil, errors.New("http2: invalid Host header")
}
var path string var path string
if req.Method != "CONNECT" { if req.Method != "CONNECT" {
@ -1861,26 +1996,21 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
} }
} }
// Check for any invalid headers and return an error before we // Check for any invalid headers+trailers and return an error before we
// potentially pollute our hpack state. (We want to be able to // potentially pollute our hpack state. (We want to be able to
// continue to reuse the hpack encoder for future requests) // continue to reuse the hpack encoder for future requests)
for k, vv := range req.Header { if err := validateHeaders(req.Header); err != "" {
if !httpguts.ValidHeaderFieldName(k) { return nil, fmt.Errorf("invalid HTTP header %s", err)
return nil, fmt.Errorf("invalid HTTP header name %q", k) }
} if err := validateHeaders(req.Trailer); err != "" {
for _, v := range vv { return nil, fmt.Errorf("invalid HTTP trailer %s", err)
if !httpguts.ValidHeaderFieldValue(v) {
// Don't include the value in the error, because it may be sensitive.
return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
}
}
} }
enumerateHeaders := func(f func(name, value string)) { enumerateHeaders := func(f func(name, value string)) {
// 8.1.2.3 Request Pseudo-Header Fields // 8.1.2.3 Request Pseudo-Header Fields
// The :path pseudo-header field includes the path and query parts of the // The :path pseudo-header field includes the path and query parts of the
// target URI (the path-absolute production and optionally a '?' character // target URI (the path-absolute production and optionally a '?' character
// followed by the query production (see Sections 3.3 and 3.4 of // followed by the query production, see Sections 3.3 and 3.4 of
// [RFC3986]). // [RFC3986]).
f(":authority", host) f(":authority", host)
m := req.Method m := req.Method
@ -2114,6 +2244,7 @@ type clientConnReadLoop struct {
// readLoop runs in its own goroutine and reads and dispatches frames. // readLoop runs in its own goroutine and reads and dispatches frames.
func (cc *ClientConn) readLoop() { func (cc *ClientConn) readLoop() {
cc.t.markNewGoroutine()
rl := &clientConnReadLoop{cc: cc} rl := &clientConnReadLoop{cc: cc}
defer rl.cleanup() defer rl.cleanup()
cc.readerErr = rl.run() cc.readerErr = rl.run()
@ -2215,10 +2346,9 @@ func (rl *clientConnReadLoop) run() error {
cc := rl.cc cc := rl.cc
gotSettings := false gotSettings := false
readIdleTimeout := cc.t.ReadIdleTimeout readIdleTimeout := cc.t.ReadIdleTimeout
var t *time.Timer var t timer
if readIdleTimeout != 0 { if readIdleTimeout != 0 {
t = time.AfterFunc(readIdleTimeout, cc.healthCheck) t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck)
defer t.Stop()
} }
for { for {
f, err := cc.fr.ReadFrame() f, err := cc.fr.ReadFrame()
@ -2555,6 +2685,9 @@ func (b transportResponseBody) Close() error {
cs := b.cs cs := b.cs
cc := cs.cc cc := cs.cc
cs.bufPipe.BreakWithError(errClosedResponseBody)
cs.abortStream(errClosedResponseBody)
unread := cs.bufPipe.Len() unread := cs.bufPipe.Len()
if unread > 0 { if unread > 0 {
cc.mu.Lock() cc.mu.Lock()
@ -2573,9 +2706,6 @@ func (b transportResponseBody) Close() error {
cc.wmu.Unlock() cc.wmu.Unlock()
} }
cs.bufPipe.BreakWithError(errClosedResponseBody)
cs.abortStream(errClosedResponseBody)
select { select {
case <-cs.donec: case <-cs.donec:
case <-cs.ctx.Done(): case <-cs.ctx.Done():
@ -2633,7 +2763,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
}) })
return nil return nil
} }
if !cs.firstByte { if !cs.pastHeaders {
cc.logf("protocol error: received DATA before a HEADERS frame") cc.logf("protocol error: received DATA before a HEADERS frame")
rl.endStreamError(cs, StreamError{ rl.endStreamError(cs, StreamError{
StreamID: f.StreamID, StreamID: f.StreamID,
@ -2860,6 +2990,15 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
fl = &cs.flow fl = &cs.flow
} }
if !fl.add(int32(f.Increment)) { if !fl.add(int32(f.Increment)) {
// For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR
if cs != nil {
rl.endStreamError(cs, StreamError{
StreamID: f.StreamID,
Code: ErrCodeFlowControl,
})
return nil
}
return ConnectionError(ErrCodeFlowControl) return ConnectionError(ErrCodeFlowControl)
} }
cc.cond.Broadcast() cc.cond.Broadcast()
@ -2904,24 +3043,26 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
} }
cc.mu.Unlock() cc.mu.Unlock()
} }
errc := make(chan error, 1) var pingError error
errc := make(chan struct{})
go func() { go func() {
cc.t.markNewGoroutine()
cc.wmu.Lock() cc.wmu.Lock()
defer cc.wmu.Unlock() defer cc.wmu.Unlock()
if err := cc.fr.WritePing(false, p); err != nil { if pingError = cc.fr.WritePing(false, p); pingError != nil {
errc <- err close(errc)
return return
} }
if err := cc.bw.Flush(); err != nil { if pingError = cc.bw.Flush(); pingError != nil {
errc <- err close(errc)
return return
} }
}() }()
select { select {
case <-c: case <-c:
return nil return nil
case err := <-errc: case <-errc:
return err return pingError
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
case <-cc.readerDone: case <-cc.readerDone:
@ -3090,9 +3231,17 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
} }
func (t *Transport) idleConnTimeout() time.Duration { func (t *Transport) idleConnTimeout() time.Duration {
// to keep things backwards compatible, we use non-zero values of
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
// http1 transport, followed by 0
if t.IdleConnTimeout != 0 {
return t.IdleConnTimeout
}
if t.t1 != nil { if t.t1 != nil {
return t.t1.IdleConnTimeout return t.t1.IdleConnTimeout
} }
return 0 return 0
} }
@ -3150,3 +3299,34 @@ func traceFirstResponseByte(trace *httptrace.ClientTrace) {
trace.GotFirstResponseByte() trace.GotFirstResponseByte()
} }
} }
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
return trace != nil && trace.WroteHeaderField != nil
}
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(k, []string{v})
}
}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
}
return nil
}
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
// connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
dialer := &tls.Dialer{
Config: cfg,
}
cn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
return tlsCn, nil
}

View File

@ -184,7 +184,8 @@ func (wr *FrameWriteRequest) replyToWriter(err error) {
// writeQueue is used by implementations of WriteScheduler. // writeQueue is used by implementations of WriteScheduler.
type writeQueue struct { type writeQueue struct {
s []FrameWriteRequest s []FrameWriteRequest
prev, next *writeQueue
} }
func (q *writeQueue) empty() bool { return len(q.s) == 0 } func (q *writeQueue) empty() bool { return len(q.s) == 0 }

View File

@ -443,8 +443,8 @@ func (ws *priorityWriteScheduler) addClosedOrIdleNode(list *[]*priorityNode, max
} }
func (ws *priorityWriteScheduler) removeNode(n *priorityNode) { func (ws *priorityWriteScheduler) removeNode(n *priorityNode) {
for k := n.kids; k != nil; k = k.next { for n.kids != nil {
k.setParent(n.parent) n.kids.setParent(n.parent)
} }
n.setParent(nil) n.setParent(nil)
delete(ws.nodes, n.id) delete(ws.nodes, n.id)

119
vendor/golang.org/x/net/http2/writesched_roundrobin.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"fmt"
"math"
)
type roundRobinWriteScheduler struct {
// control contains control frames (SETTINGS, PING, etc.).
control writeQueue
// streams maps stream ID to a queue.
streams map[uint32]*writeQueue
// stream queues are stored in a circular linked list.
// head is the next stream to write, or nil if there are no streams open.
head *writeQueue
// pool of empty queues for reuse.
queuePool writeQueuePool
}
// newRoundRobinWriteScheduler constructs a new write scheduler.
// The round robin scheduler priorizes control frames
// like SETTINGS and PING over DATA frames.
// When there are no control frames to send, it performs a round-robin
// selection from the ready streams.
func newRoundRobinWriteScheduler() WriteScheduler {
ws := &roundRobinWriteScheduler{
streams: make(map[uint32]*writeQueue),
}
return ws
}
func (ws *roundRobinWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {
if ws.streams[streamID] != nil {
panic(fmt.Errorf("stream %d already opened", streamID))
}
q := ws.queuePool.get()
ws.streams[streamID] = q
if ws.head == nil {
ws.head = q
q.next = q
q.prev = q
} else {
// Queues are stored in a ring.
// Insert the new stream before ws.head, putting it at the end of the list.
q.prev = ws.head.prev
q.next = ws.head
q.prev.next = q
q.next.prev = q
}
}
func (ws *roundRobinWriteScheduler) CloseStream(streamID uint32) {
q := ws.streams[streamID]
if q == nil {
return
}
if q.next == q {
// This was the only open stream.
ws.head = nil
} else {
q.prev.next = q.next
q.next.prev = q.prev
if ws.head == q {
ws.head = q.next
}
}
delete(ws.streams, streamID)
ws.queuePool.put(q)
}
func (ws *roundRobinWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {}
func (ws *roundRobinWriteScheduler) Push(wr FrameWriteRequest) {
if wr.isControl() {
ws.control.push(wr)
return
}
q := ws.streams[wr.StreamID()]
if q == nil {
// This is a closed stream.
// wr should not be a HEADERS or DATA frame.
// We push the request onto the control queue.
if wr.DataSize() > 0 {
panic("add DATA on non-open stream")
}
ws.control.push(wr)
return
}
q.push(wr)
}
func (ws *roundRobinWriteScheduler) Pop() (FrameWriteRequest, bool) {
// Control and RST_STREAM frames first.
if !ws.control.empty() {
return ws.control.shift(), true
}
if ws.head == nil {
return FrameWriteRequest{}, false
}
q := ws.head
for {
if wr, ok := q.consume(math.MaxInt32); ok {
ws.head = q.next
return wr, true
}
q = q.next
if q == ws.head {
break
}
}
return FrameWriteRequest{}, false
}

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build go1.18 //go:build go1.18
// +build go1.18
package idna package idna

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build go1.10 //go:build go1.10
// +build go1.10
// Package idna implements IDNA2008 using the compatibility processing // Package idna implements IDNA2008 using the compatibility processing
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to // defined by UTS (Unicode Technical Standard) #46, which defines a standard to

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !go1.10 //go:build !go1.10
// +build !go1.10
// Package idna implements IDNA2008 using the compatibility processing // Package idna implements IDNA2008 using the compatibility processing
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to // defined by UTS (Unicode Technical Standard) #46, which defines a standard to
@ -121,7 +120,7 @@ func CheckJoiners(enable bool) Option {
} }
} }
// StrictDomainName limits the set of permissable ASCII characters to those // StrictDomainName limits the set of permissible ASCII characters to those
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the // allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
// hyphen). This is set by default for MapForLookup and ValidateForRegistration, // hyphen). This is set by default for MapForLookup and ValidateForRegistration,
// but is only useful if ValidateLabels is set. // but is only useful if ValidateLabels is set.

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !go1.18 //go:build !go1.18
// +build !go1.18
package idna package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. // Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.10 && !go1.13 //go:build go1.10 && !go1.13
// +build go1.10,!go1.13
package idna package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. // Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.13 && !go1.14 //go:build go1.13 && !go1.14
// +build go1.13,!go1.14
package idna package idna

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