Compare commits

..

118 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
90b7f68004 Merge branch 'develop-xorm'
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
2023-01-21 09:59:32 +01:00
8439926037 added arch arm64 in ci
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-21 09:39:07 +01:00
e1d5d7bdfe added arch arm64 in ci
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-21 09:37:27 +01:00
c81d89d417 updated dependencies
All checks were successful
continuous-integration/drone/tag Build is passing
2023-01-19 08:51:51 +01:00
53f78abaf3 stabilized zmq loop 2023-01-19 08:51:03 +01:00
1cfe9e83bb updated dependencies 2023-01-19 08:50:42 +01:00
5b95e817de updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
* removed unused columns in tables
* added multithreaded support for ip scans
* added health endpoint
2023-01-17 18:37:31 +01:00
698510b19a updated messaging system, removed cache
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-16 01:01:14 +01:00
4bada1c166 updated messaging system
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-15 16:04:49 +01:00
9244e05fc3 updated ScanIP function
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-14 20:58:32 +01:00
1868bb2ea4 misc update for develop-xorm
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-14 17:30:42 +01:00
46095f778c updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-14 17:13:25 +01:00
589c6a5861 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-14 17:11:15 +01:00
467ce67fa6 updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-23 18:16:43 +02:00
2a2199e3eb updated models
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-16 23:27:57 +02:00
7e8437c2cd updated dependencies 2022-10-16 23:27:39 +02:00
a3d1f84271 updated ipbl 2022-09-18 10:24:55 +02:00
b81adb4f95 updated ipbl 2022-09-18 10:24:44 +02:00
77e57b4f7d updated ipbl 2022-09-18 10:23:24 +02:00
e92d332269 updated ipbl to handle init of ipblc
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-11 23:34:51 +02:00
3f000cbf8f updated ipbl, bug fixes, change models
All checks were successful
continuous-integration/drone Build is passing
continuous-integration/drone/tag Build is passing
2022-08-28 16:23:21 +02:00
a1329fc544 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-16 11:42:03 +02:00
be9446abab updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-01 12:47:01 +02:00
0518d4445c added hostinfo collect on ipbl
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-07-01 10:25:48 +02:00
3fd0689366 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-05 23:07:32 +02:00
459c74bbf0 misc fixes
Some checks failed
continuous-integration/drone/push Build is failing
2022-06-04 17:45:37 +02:00
f854a3b219 added per set blocktime
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-06-04 11:32:56 +02:00
9d70341f41 added transactions, cleaned code
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-10 13:00:22 +02:00
632c4ba101 updated ipbl limit on ScanIP
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-04-10 19:11:26 +02:00
c362158dfb updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-04-10 12:46:39 +02:00
2c4af80b3d Merge branch 'master' of ssh://git.paulbsd.com:2222/paulbsd/ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2022-04-10 12:43:32 +02:00
60943adc66 updated ipbl api endpoints 2022-04-10 12:43:28 +02:00
bdaf6c49a3 fix bug when inserting~ ips
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-29 10:14:40 +02:00
9803adfbb9 updated ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-27 18:32:58 +02:00
9677593085 Merge branch 'master' of https://git.paulbsd.com/paulbsd/ipbl
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-26 19:46:13 +01:00
d8ab04aedf updated ipbl with some functions reliability 2022-03-26 19:46:04 +01:00
a6d2de6f69 Update 'README.md'
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-25 11:56:28 +01:00
551b3311a6 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-19 16:21:31 +01:00
cbce240dc2 updated dependencies 2022-03-19 16:21:19 +01:00
1ef4b1789a fix bug in InsertIPBulk 2022-03-19 16:21:08 +01:00
b1b7302c7a updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-19 15:32:49 +01:00
83995c26a5 changed tables schemas
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-19 14:48:50 +01:00
881969f81e updated ipbl 2022-03-14 18:49:28 +01:00
32784ff442 misc updates 2022-03-13 12:08:44 +01:00
c41747e146 updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-11 23:35:24 +01:00
90136fe906 added zeromq handling
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-11 23:34:09 +01:00
e828164392 merge conflict
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-10 19:57:32 +01:00
b93cd8306e added zmq configs 2022-03-10 19:56:03 +01:00
1df597e1d6 Merge branch 'master' of https://git.paulbsd.com/paulbsd/ipbl
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build is passing
2022-02-27 01:17:35 +01:00
8cb9c51bde misc changes 2022-02-27 01:17:21 +01:00
2bf11fdb75 updated misc ipbl functions
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-25 13:39:19 +01:00
52e767b941 added folders config update
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-25 12:12:57 +01:00
de8717dc99 changed archive filename
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build is passing
2022-02-24 19:53:12 +01:00
14f8cbebb0 added go generate
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-02-24 19:50:05 +01:00
561b5be477 changed .drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2022-02-24 19:48:23 +01:00
ee33ee3fd6 updated .drone.yml
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-02-24 18:46:12 +01:00
29a5c5927b added folders config in database
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build encountered an error
2022-02-24 17:28:29 +01:00
ef228e3750 updates on rdns resolve messages
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-02-23 11:17:21 +01:00
636 changed files with 78217 additions and 20454 deletions

View File

@ -1,89 +1,52 @@
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: cleanup-before name: build-linux-amd64
steps: environment:
- name: clean
image: alpine
commands:
- rm -rf /build/*
volumes:
- name: build
path: /build
when:
event: tag
volumes:
- name: build
host:
path: /tmp/ipbl/build
---
kind: pipeline
type: docker
name: default-linux-amd64
steps:
- name: build
image: golang
commands:
- ./ci-build.sh build
environment:
GOOS: linux GOOS: linux
GOARCH: amd64 GOARCH: amd64
volumes: GOOPTIONS: -mod=vendor
- name: build SRCFILES: cmd/ipbl/*.go
path: /build PROJECTNAME: ipbl
volumes: steps:
- name: build - name: build
host: image: golang
path: /tmp/ipbl/build environment:
VERSION: dev
depends_on: commands:
- cleanup-before - go generate $SRCFILES
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
when:
event:
exclude:
- tag
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: default-linux-arm64 name: gitea-release-linux-amd64
environment:
GOOS: linux
GOARCH: amd64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/ipbl/*.go
PROJECTNAME: ipbl
steps: steps:
- name: build - name: build
image: golang image: golang
commands: commands:
- ./ci-build.sh build - export VERSION=$DRONE_TAG
environment: - go generate $SRCFILES
GOOS: linux - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
GOARCH: arm64 - tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
volumes: - echo $PROJECTNAME $DRONE_TAG > VERSION
- name: build
path: /build
volumes:
- name: build
host:
path: /tmp/ipbl/build
depends_on:
- cleanup-before
---
kind: pipeline
type: docker
name: gitea-release
steps:
- name: move
image: alpine
commands:
- mv build/* ./
volumes:
- name: build
path: /drone/src/build
when: when:
event: tag event:
- tag
- name: release - name: release
image: plugins/gitea-release image: plugins/gitea-release
settings: settings:
@ -95,50 +58,70 @@ steps:
- sha256 - sha256
- sha512 - sha512
title: VERSION title: VERSION
volumes:
- name: build
path: /drone/src/build
when: when:
event: tag event:
- name: ls - tag
image: alpine
commands:
- find .
volumes:
- name: build
path: /drone/src/build
when:
event: tag
volumes:
- name: build
host:
path: /tmp/ipbl/build
depends_on:
- default-linux-amd64
- default-linux-arm64
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: cleanup-after name: build-linux-arm64
environment:
GOOS: linux
GOARCH: arm64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/ipbl/*.go
PROJECTNAME: ipbl
steps: steps:
- name: clean - name: build
image: alpine image: golang
environment:
VERSION: dev
commands: commands:
- rm -rf /build/* - go generate $SRCFILES
volumes: - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- name: build
path: /build
when: when:
event: tag event:
exclude:
- tag
volumes: ---
kind: pipeline
type: docker
name: gitea-release-linux-arm64
environment:
GOOS: linux
GOARCH: arm64
GOOPTIONS: -mod=vendor
SRCFILES: cmd/ipbl/*.go
PROJECTNAME: ipbl
steps:
- name: build - name: build
host: image: golang
path: /tmp/ipbl/build commands:
- export VERSION=$DRONE_TAG
depends_on: - go generate $SRCFILES
- gitea-release - go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
- echo $PROJECTNAME $DRONE_TAG > VERSION
when:
event:
- tag
- name: release
image: plugins/gitea-release
settings:
base_url: https://git.paulbsd.com
api_key:
from_secret: gitea_token
files: "*.tar.gz"
checksum:
- sha256
- sha512
title: VERSION
when:
event:
- tag

1
.gitignore vendored
View File

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

View File

@ -1,18 +0,0 @@
# ipbl Makefile
GOCMD=go
GOBUILDCMD=${GOCMD} build
GOOPTIONS=-mod=vendor -ldflags="-s -w"
RMCMD=rm
BINNAME=ipbl
SRCFILES=cmd/ipbl/*.go
all: build
build:
${GOBUILDCMD} ${GOOPTIONS} ${SRCFILES}
clean:
${RMCMD} -f ${BINNAME}

View File

@ -11,7 +11,8 @@ ipbl is a webservice collecting / storing / distributing abuse IP addresses
### Build ### Build
```bash ```bash
make go generate cmd/ipbl/*.go
go build cmd/ipbl/*.go
``` ```
### Sample config in ipbl.ini ### Sample config in ipbl.ini
@ -22,19 +23,19 @@ db_hostname="hostname"
db_name="database" db_name="database"
db_username="username" db_username="username"
db_password="password" db_password="password"
db_table="ipbl_test" db_table="ipbl"
``` ```
### Run ### Run
```bash ```bash
./ipbl -configfile ipbl.ini -port 8080 ./ipbl -configfile ipbl.ini -port 8099
``` ```
## License ## License
```text ```text
Copyright (c) 2020, 2021 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

@ -1,63 +0,0 @@
#!/bin/bash
set -e
PROJECTNAME=ipbl
RELEASENAME=${PROJECTNAME}
VERSION="0"
GOOPTIONS="-mod=vendor"
SRCFILES=cmd/${PROJECTNAME}/*.go
build() {
echo "Begin of build"
if [[ ! -z ${DRONE_TAG} ]]
then
echo "Drone tag set, let's do a release"
export VERSION=${DRONE_TAG}
echo "${PROJECTNAME} ${VERSION}" > /build/VERSION
elif [[ ! -z ${DRONE_COMMIT} ]]
then
echo "Drone not set, let's only do a build"
export VERSION=${DRONE_COMMIT}
fi
if [[ ! -z ${VERSION} && ! -z ${GOOS} && ! -z ${GOARCH} ]]
then
echo "Let's set a release name"
RELEASENAME=${PROJECTNAME}-${VERSION}-${GOOS}-${GOARCH}
fi
echo "Building project"
go generate ${SRCFILES}
go build -o ${PROJECTNAME} ${GOOPTIONS} ${SRCFILES}
if [[ ! -z ${DRONE_TAG} ]]
then
echo "Let's make archives"
mkdir -p /build
tar -czvf /build/${RELEASENAME}.tar.gz ${PROJECTNAME}
fi
echo "Removing binary file"
rm ${PROJECTNAME}
echo "End of build"
}
clean() {
rm -rf ${RELEASEDIR}
}
case $1 in
"build")
build
;;
"clean")
clean
;;
*)
echo "No options choosen"
exit 1
;;
esac

View File

@ -6,10 +6,15 @@ 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/ws"
"git.paulbsd.com/paulbsd/ipbl/utils"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -25,26 +30,26 @@ func main() {
os.Exit(0) os.Exit(0)
} }
// Initialize database app context
err := database.Initialize(&ctx, &cfg) err := database.Initialize(&ctx, &cfg)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
defer cfg.Db.Close() defer cfg.Db.Close()
// Handles IP with no reverse DNS if cfg.Options.ScanIP {
go models.ScanIP(&cfg) go models.ScanIP(&cfg)
}
// Add cron task to handle them if cfg.Options.Debug {
//cr := cron.New() go func() {
//cr.AddFunc("0 * * * * *", func() { log.Println(http.ListenAndServe("localhost:6060", nil))
// models.ScanIP(&cfg) }()
//}) }
//cr.Start()
// Run the ipbl web service ws.Init(&cfg)
err = routers.RunServer(&ctx, &cfg) go func() { err = routers.RunServer(&ctx, &cfg) }()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
utils.Server()
} }

35
go.mod
View File

@ -1,31 +1,32 @@
module git.paulbsd.com/paulbsd/ipbl module git.paulbsd.com/paulbsd/ipbl
go 1.17 go 1.23
require ( require (
github.com/labstack/echo/v4 v4.6.3 github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.4 github.com/lib/pq v1.10.9
gopkg.in/ini.v1 v1.66.4 golang.org/x/net v0.28.0
xorm.io/xorm v1.2.5 gopkg.in/ini.v1 v1.67.0
xorm.io/xorm v1.3.9
) )
require ( require (
github.com/goccy/go-json v0.9.4 // 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/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/labstack/gommon v0.3.1 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.14 // 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.4 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.14.0 // indirect github.com/onsi/gomega v1.22.1 // indirect
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.1 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/sys v0.24.0 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/text v0.17.0 // indirect
golang.org/x/text v0.3.7 // indirect xorm.io/builder v0.3.13 // indirect
xorm.io/builder v0.3.9 // indirect
) )

601
go.sum
View File

@ -1,619 +1,180 @@
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=
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.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.9.4/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-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
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=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.0.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.11/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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/labstack/echo/v4 v4.6.3 h1:VhPuIZYxsbPmo4m9KAkMU/el2442eB7EBFFhNTTT9ac= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/labstack/echo/v4 v4.6.3/go.mod h1:Hk5OiHj0kDqmFq7aHe7eDqI7CUhuCrfpupQtLGGLm7A= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
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.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/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.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/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=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
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.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.1/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.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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 h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
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/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w=
modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A=
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.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
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.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc=
xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.2.5 h1:tqN7OhN8P9xi52qBb76I8m5maAJMz/SSbgK2RGPCPbo=
xorm.io/xorm v1.2.5/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0=

View File

@ -1,9 +0,0 @@
package api
// IP describe IP objects via API calls
type IP struct {
ID int `json:"id"`
IP string `json:"ip"`
Rdns string `json:"rdns"`
Src string `json:"src"`
}

View File

@ -8,31 +8,30 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// GetConfig fetch configuration
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 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(&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.Migrate = migrate
var inicfg, err = ini.Load(configfile) var inicfg, err = ini.Load(configfile)
if err != nil { if err != nil {
@ -45,30 +44,36 @@ func (cfg *Config) GetConfig() error {
cfg.DbParams.DbUsername = ipblsection.Key("username").MustString("username") cfg.DbParams.DbUsername = ipblsection.Key("username").MustString("username")
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.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
} }
// Config is the global config
type Config struct { type Config struct {
Db *xorm.Engine `json:"-"` Db *xorm.Engine
DbParams struct { DbParams struct {
DbHostname string `json:"dbhostname"` DbHostname string
DbUsername string `json:"dbusername"` DbUsername string
DbPassword string `json:"dbpassword"` DbPassword string
DbDatabase string `json:"dbdatabase"` DbDatabase string
} `json:"dbparams"` }
Options struct { Options struct {
Version string `json:"version"` Version string
HideBanner bool `json:"hidebanner"` HideBanner bool
} `json:"-"` SocketChannel string
Debug bool
ScanIP bool
}
Switchs struct { Switchs struct {
Port int `json:"port"` Port int
NoFeed bool `json:"nofeed"` NoFeed bool
Debug bool `json:"debug"` Drop bool
Drop bool `json:"drop"` Init bool
Init bool `json:"init"` Version bool
Version bool `json:"version"` Migrate bool
} `json:"-"` }
} }

View File

@ -5,18 +5,19 @@ 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"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/dialects"
"xorm.io/xorm/names" "xorm.io/xorm/names"
) )
// Init creates connection to database and exec Schema
func Initialize(ctx *context.Context, cfg *config.Config) (err error) { func Initialize(ctx *context.Context, cfg *config.Config) (err error) {
var databaseEngine = "postgres" var databaseEngine = "postgres"
var tables = []interface{}{models.IP{}, models.Cfg{}, models.Src{}} //cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 10)
cfg.Db, err = xorm.NewEngine(databaseEngine, cfg.Db, err = xorm.NewEngine(databaseEngine,
fmt.Sprintf("%s://%s:%s@%s/%s", fmt.Sprintf("%s://%s:%s@%s/%s",
@ -29,18 +30,48 @@ func Initialize(ctx *context.Context, cfg *config.Config) (err error) {
log.Fatalln(err) log.Fatalln(err)
} }
cfg.Db.SetMapper(names.GonicMapper{}) cfg.Db.SetMapper(names.GonicMapper{})
cfg.Db.ShowSQL(cfg.Switchs.Debug) cfg.Db.SetQuotePolicy(dialects.QuotePolicyReserved)
cfg.Db.ShowSQL(cfg.Options.Debug)
//cfg.Db.SetDefaultCacher(cacher)
if cfg.Switchs.Drop { if cfg.Switchs.Drop {
log.Println("Dropping tables") log.Println("Dropping tables")
cfg.Db.DropTables(tables) models.DropTables(ctx, cfg)
os.Exit(0) os.Exit(0)
} }
log.Println("Syncing tables") log.Println("Syncing tables")
for _, table := range tables { err = models.NewEngine(ctx, cfg)
cfg.Db.CreateTables(table)
err = cfg.Db.Sync2(table) 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
} }

62
src/models/as.go Normal file
View File

@ -0,0 +1,62 @@
package models
import (
"time"
"xorm.io/xorm"
)
func (as *AutonomousSystem) GetOrCreate(session *xorm.Session) (apias *APIAutonomousSystem, err error) {
has, err := session.Get(as)
if err != nil {
return
}
if !has {
session.Insert(as)
} else {
session.ID(as.ID).Update(as)
}
as.Get(session)
apias = as.APIFormat()
return
}
func (as *AutonomousSystem) Get(session *xorm.Session) (apias *APIAutonomousSystem, err error) {
has, err := session.Get(as)
if !has || err != nil {
return
}
apias = as.APIFormat()
return
}
func (as *AutonomousSystem) APIFormat() *APIAutonomousSystem {
if as == nil {
return &APIAutonomousSystem{}
}
return &APIAutonomousSystem{
ASID: as.ASID,
ASName: as.ASName,
}
}
func (as *AutonomousSystem) APIParse(apias APIAutonomousSystem) (err error) {
*as = AutonomousSystem{
ASID: apias.ASID,
ASName: apias.ASName}
return
}
type AutonomousSystem struct {
ID int `xorm:"pk autoincr"`
ASID int `xorm:"integer unique(asindex) as_id"`
ASName string `xorm:"text unique(asindex) as_name"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APIAutonomousSystem struct {
ID int `json:"-"`
ASID int `json:"as_id"`
ASName string `json:"as_name"`
}

View File

@ -1,71 +1,218 @@
package models package models
import ( import (
"encoding/json"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"github.com/labstack/echo/v4"
) )
//var 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])?$)`
var ipv4_cidr_regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|)){4}\/([1-3])?([0-9])?$)`
// GetTrustlists ... func GetTrustlists(cfg *config.Config) (res []string, err error) {
func GetTrustlists(cfg config.Config) (res []string, err error) { var ctl []CfgTrustlist
var w = Cfg{Key: "trustlist"} err = cfg.Db.Find(&ctl)
if exists, _ := cfg.Db.Get(&w); exists {
res = strings.Split(w.Value, ",") if len(ctl) > 0 {
for _, a := range ctl {
res = append(res, a.IP)
}
} }
return return
} }
func (wl Trustlist) 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 Trustlist) Delete(cfg config.Config, ip string) (err error) { func (tl CfgTrustlist) Delete(cfg *config.Config, ip string) (affected int64, err error) {
var w = Cfg{Key: "trustlist"} var w = CfgTrustlist{IP: ip}
exists, _ := cfg.Db.Get(&w) exists, _ := cfg.Db.Get(&w)
var updated []string
if exists { if exists {
existing, _ := GetTrustlists(cfg) affected, err = cfg.Db.ID(w.ID).Update(&w)
for _, sip := range existing { return
if sip != ip {
updated = append(updated, sip)
} }
} return
w.Value = strings.Join(updated, ",")
cfg.Db.ID(w.ID).Update(&w)
}
return fmt.Errorf("no trustlist updated")
} }
func (wl Trustlist) 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)
} }
type Trustlist struct { func (cfgset *CfgSet) BuildRegex(cfg *config.Config) {
IP string `json:"ip"` 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{}
if err := cfg.Db.Where("enabled").Find(&w); err == nil {
return w, err
}
return
}
func GetGlobalConfig(cfg *config.Config) (res []Cfg, err error) {
var w = []Cfg{}
if err := cfg.Db.Find(&w); err == nil {
return w, err
}
return
}
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"}
if exists, _ := cfg.Db.Get(&w); exists {
resbytes, err := json.Marshal(folders)
if err != nil {
return "", err
}
w.Value = string(resbytes)
_, err = cfg.Db.ID(w.ID).Update(&w)
if err != nil {
return "", err
}
}
return
}
func GetWS(cfg *config.Config) (res []CfgWS, err error) {
var w = []CfgWS{}
if err = cfg.Db.Find(&w); err == nil {
return w, err
}
return
}
func DiscoverURLS(cfg *config.Config, routes []*echo.Route) (Discovery, error) {
var disc Discovery
var urls = make(map[string]Url)
for _, j := range routes {
if strings.Contains(j.Path, ":") || j.Path == "/" || j.Path == "/discovery" {
continue
}
names := strings.Split(j.Path, "/")
name := names[len(names)-1]
urls[name] = Url{Key: name, Path: j.Path}
}
disc = Discovery{Version: "1.0", URLs: urls}
return disc, nil
}
type CfgAllv2 struct {
Cfg map[string]string `json:"cfg"`
Sets []CfgSet `json:"sets"`
Trustlists []string `json:"trustlists"`
WS []CfgWS `json:"ws"`
} }
// Cfg is ipbl config
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"`
Value string `xorm:"text default" json:"value"` Value string `xorm:"text default" json:"value"`
} }
type CfgSet struct {
ID int `xorm:"pk autoincr" json:"-"`
Path string `xorm:"text notnull default" json:"path"`
Src string `xorm:"text notnull" json:"src"`
Filename string `xorm:"text notnull" json:"filename"`
Regex string `xorm:"-" json:"regex"`
Blocktime int64 `xorm:"notnull default 60" json:"blocktime"`
TryFail int64 `xorm:"notnull default 5" json:"tryfail"`
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 {
ID int `xorm:"pk autoincr" json:"-"`
IP string `xorm:"text notnull" json:"ip"`
}
type CfgWS struct {
ID int `xorm:"pk autoincr" json:"-"`
Type string `xorm:"text notnull" json:"type"`
Endpoint string `xorm:"text notnull" json:"endpoint"`
Subscription string `json:"subscription"`
}
type Discovery struct {
Version string `json:"version"`
URLs map[string]Url `json:"urls"`
}
type Url struct {
Key string `json:"key"`
Path string `json:"path"`
}

58
src/models/city.go Normal file
View File

@ -0,0 +1,58 @@
package models
import (
"time"
"xorm.io/xorm"
)
func (city *City) GetOrCreate(session *xorm.Session) (apicity *APICity, err error) {
has, err := session.Get(city)
if err != nil {
return
}
if !has {
session.Insert(city)
} else {
session.ID(city.ID).Update(city)
}
city.Get(session)
apicity = city.APIFormat()
return
}
func (city *City) Get(session *xorm.Session) (apicity *APICity, err error) {
has, err := session.Get(city)
if !has || err != nil {
return
}
apicity = city.APIFormat()
return
}
func (city *City) APIFormat() *APICity {
if city == nil {
return &APICity{}
}
return &APICity{
CityName: city.CityName,
}
}
func (city *City) APIParse(apicity APICity) (err error) {
*city = City{
CityName: apicity.CityName}
return
}
type City struct {
ID int `xorm:"pk autoincr"`
CityName string `xorm:"text unique(cityindex) city_name" json:"city_name"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APICity struct {
ID int `json:"-"`
CityName string `json:"city_name"`
}

58
src/models/country.go Normal file
View File

@ -0,0 +1,58 @@
package models
import (
"time"
"xorm.io/xorm"
)
func (country *Country) GetOrCreate(session *xorm.Session) (apicountry *APICountry, err error) {
has, err := session.Get(country)
if err != nil {
return
}
if !has {
session.Insert(country)
} else {
session.ID(country.ID).Update(country)
}
apicountry = country.APIFormat()
country.Get(session)
return
}
func (country *Country) Get(session *xorm.Session) (apicountry *APICountry, err error) {
has, err := session.Get(country)
if !has || err != nil {
return
}
apicountry = country.APIFormat()
return
}
func (country *Country) APIFormat() *APICountry {
if country == nil {
return &APICountry{}
}
return &APICountry{
CountryName: country.CountryName,
}
}
func (country *Country) APIParse(apicountry APICountry) (err error) {
*country = Country{
CountryName: apicountry.CountryName}
return
}
type Country struct {
ID int `xorm:"pk autoincr"`
CountryName string `xorm:"text unique(countryindex) country_name" json:"country_name"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APICountry struct {
ID int `json:"-"`
CountryName string `json:"country_name"`
}

50
src/models/event.go Normal file
View File

@ -0,0 +1,50 @@
package models
import (
"time"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"xorm.io/xorm"
)
func (event *Event) Insert(cfg *config.Config) (err error) {
session := cfg.Db.NewSession()
defer session.Close()
_, err = session.Insert(event)
err = session.Commit()
return
}
func (event *Event) APIParse(session *xorm.Session, apievent APIEvent) (err error) {
*event = Event{
IP: &IP{IP: apievent.IPData.IP},
Host: &Host{Host: apievent.IPData.Hostname},
Src: &Src{Src: apievent.IPData.Src},
}
event.IP.GetOrCreate(session)
event.Host.GetOrCreate(session)
event.Src.GetOrCreate(session)
return
}
type Event struct {
ID int `xorm:"pk autoincr"`
Src *Src `xorm:"int src_id index"`
Host *Host `xorm:"int host_id index"`
IP *IP `xorm:"int ip_id index"`
Created time.Time `xorm:"created notnull index"`
}
type APIEvent struct {
MsgType string `json:"msgtype"`
Mode string `json:"mode"`
Hostname string `json:"hostname"`
IPData struct {
IP string `json:"ip"`
Type int `json:"t"`
Src string `json:"src"`
Hostname string `json:"hostname"`
Date string `json:"date"`
Created string `json:"created"`
} `json:"ipdata"`
}

58
src/models/host.go Normal file
View File

@ -0,0 +1,58 @@
package models
import (
"time"
"xorm.io/xorm"
)
func (host *Host) GetOrCreate(session *xorm.Session) (apihost *APIHost, err error) {
has, err := session.Get(host)
if err != nil {
return
}
if !has {
session.Insert(host)
} else {
session.ID(host.ID).Update(host)
}
host.Get(session)
apihost = host.APIFormat()
return
}
func (host *Host) Get(session *xorm.Session) (apihost *APIHost, err error) {
has, err := session.Get(host)
if !has || err != nil {
return
}
apihost = host.APIFormat()
return
}
func (host *Host) APIFormat() *APIHost {
if host == nil {
return &APIHost{}
}
return &APIHost{
Host: host.Host,
}
}
func (host *Host) APIParse(apihost APIHost) (err error) {
*host = Host{
Host: apihost.Host}
return
}
type Host struct {
ID int `xorm:"pk autoincr"`
Host string `xorm:"text unique host" json:"host"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APIHost struct {
ID int `json:"-"`
Host string `json:"host"`
}

55
src/models/hostinfo.go Normal file
View File

@ -0,0 +1,55 @@
package models
import (
"context"
"database/sql"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"xorm.io/xorm"
)
func GetIPsWithoutHostInfo(ctx *context.Context, config *config.Config) (apiips []string, err error) {
res, err := config.Db.Query(`
SELECT ip
FROM ip
WHERE ip NOT IN (
SELECT ip FROM host_info)
ORDER BY RANDOM()
LIMIT 10;`)
for _, r := range res {
apiips = append(apiips, string(r["ip"]))
}
return
}
func ProcessHostInfo(cfg *config.Config, hostinfos []HostInfo) (err error) {
session := cfg.Db.NewSession()
defer session.Close()
for _, hi := range hostinfos {
var i IP = HostInfoToIP(session, &hi)
i.GetOrCreate(session)
}
session.Commit()
return
}
func HostInfoToIP(session *xorm.Session, hi *HostInfo) (ip IP) {
ip = IP{
IP: hi.IP,
Rdns: sql.NullString{
String: hi.Rdns,
Valid: true},
City: &City{CityName: hi.City},
Country: &Country{CountryName: hi.Country},
AutonomousSystem: &AutonomousSystem{ID: 0},
}
return
}
type HostInfo struct {
IP string `json:"ip"`
Whois string `json:"whois"`
Rdns string `json:"rdns"`
City string `json:"city"`
Country string `json:"country"`
}

View File

@ -3,135 +3,291 @@ package models
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"io"
"log" "log"
"net" "net/http"
"runtime"
"sync"
"time" "time"
"git.paulbsd.com/paulbsd/ipbl/src/api"
"git.paulbsd.com/paulbsd/ipbl/src/config" "git.paulbsd.com/paulbsd/ipbl/src/config"
"xorm.io/xorm"
) )
const SCANLIMIT = 100
const IPINFO_WS = "https://ip.paulbsd.com"
var lastday = time.Now().Add(-(time.Hour * 24)) var lastday = time.Now().Add(-(time.Hour * 24))
// GetIPs ... func GetIPs(ctx *context.Context, config *config.Config, limit int) (apiips []*APIIP, err error) {
func GetIPs(ctx *context.Context, config *config.Config, limit int) (apimailboxes []*api.IP, err error) {
var ips []IP var ips []IP
err = config.Db.Limit(limit).Desc("created").Find(&ips) err = config.Db.Limit(limit).Desc("created").Find(&ips)
for _, ml := range ips { for _, ml := range ips {
apimailboxes = append(apimailboxes, ml.APIFormat()) apiips = append(apiips, ml.APIFormat())
} }
return return
} }
// GetIPs ... func GetIPsLast(ctx *context.Context, config *config.Config, interval string) (apiips []string, err error) {
func GetIPsLast(ctx *context.Context, config *config.Config, interval string) (apimailboxes []*api.IP, err error) {
var ips []IP var ips []IP
err = config.Db.Where(fmt.Sprintf("updated >= (now()-'%s'::interval)", interval)).GroupBy("ip").Find(&ips) err = config.Db.Where("updated >= (now()-?::interval)", interval).GroupBy("ip").Find(&ips)
for _, ml := range ips { for _, ml := range ips {
apimailboxes = append(apimailboxes, ml.APIFormat()) apiips = append(apiips, ml.IP)
} }
return return
} }
// GetIP ... func (ip *IP) GetOrCreate(session *xorm.Session) (apiip *APIIP, err error) {
func GetIP(ctx *context.Context, config *config.Config, ipquery interface{}) (apiip *api.IP, err error) { ip.City.GetOrCreate(session)
var ip IP ip.Country.GetOrCreate(session)
has, err := config.Db.Where("ip = ?", ipquery).Get(&ip) ip.AutonomousSystem.GetOrCreate(session)
session.Commit()
var tmpip *IP
if ip.ID != 0 {
tmpip = &IP{ID: ip.ID, IP: ip.IP}
} else {
tmpip = &IP{IP: ip.IP}
}
has, err := session.Get(tmpip)
if err != nil {
log.Println(err)
}
if !has { if !has {
session.Insert(ip)
} else {
ip.ID = tmpip.ID
session.ID(ip.ID).AllCols().Update(ip)
session.ID(ip.ID).Cols("city_id", "country_id", "as_id").Update(ip)
}
session.Commit()
ip.Get(session)
apiip = ip.APIFormat()
return
}
func (ip *IP) Get(session *xorm.Session) (apiip *APIIP, err error) {
has, err := session.Get(ip)
if !has || err != nil {
err = fmt.Errorf("not found") err = fmt.Errorf("not found")
return nil, err return nil, err
} }
if err != nil {
return
}
apiip = ip.APIFormat() apiip = ip.APIFormat()
return return
} }
// UpdateRDNS ... func (ip *IP) InsertOrUpdate(session *xorm.Session) (numinsert int64, numupdate int64, err error) {
func (i *IP) UpdateRDNS() (result string, err error) { has, err := session.Get(ip)
res, err := net.LookupAddr(i.IP) if has {
if err != nil { session.ID(ip.ID).Update(&IP{})
result = ""
} else {
result = res[0]
} }
session.Commit()
return return
} }
// InsertIP ... func InsertIPBulk(session *xorm.Session, ips *[]IP) (numinsert int64, numupdate int64, err error) {
func (i *IP) InsertIP(cfg *config.Config) (num int64, err error) {
num, err = cfg.Db.Insert(i)
return
}
// InsertIPBulk ...
func InsertIPBulk(cfg *config.Config, ips *[]IP) (numinserts int64, numupdates int64, numfail int64, err error) {
var iplist []string
for _, ip := range *ips { for _, ip := range *ips {
iplist = append(iplist, ip.IP) numinsert, numupdate, err = ip.InsertOrUpdate(session)
} }
Cleanup(session)
var searchips []IP
cfg.Db.In("ip", iplist).Find(&searchips)
var toupdateips []string
for _, ip := range searchips {
toupdateips = append(toupdateips, ip.IP)
}
cfg.Db.In("ip", toupdateips).Cols("updated").Update(&IP{})
var toinsertip, _ = differ(*ips, searchips)
numinserts, err = cfg.Db.Insert(toinsertip)
return return
} }
// ScanIP ...
func ScanIP(cfg *config.Config) (err error) { func ScanIP(cfg *config.Config) (err error) {
var numthreads int = runtime.NumCPU() / 2
for { for {
session := cfg.Db.NewSession()
orphans := []IP{} orphans := []IP{}
if cfg.Db.Where("rdns IS NULL").Asc("ip").Find(&orphans); len(orphans) > 0 { err = session.Where(`
for _, i := range orphans { (
reverse, _ := i.UpdateRDNS() (
if reverse == "" { as_id IS NULL
log.Printf("Set \"none\" rdns to IP %s\n", i.IP) OR country_id IS NULL
i.Rdns.String = "none" OR city_id IS NULL
} else { OR rdns = ''
log.Printf("%s %s\n", i.IP, reverse) OR rdns IS NULL
i.Rdns.String = reverse )
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()
if err == nil && len(orphans) > 0 {
orphanchan := make(chan IP)
var wg sync.WaitGroup
for thr := range make([]int, numthreads) {
go ScanOrphan(&wg, orphanchan, thr, cfg)
} }
i.Rdns.Valid = true
_, err = cfg.Db.ID(i.ID).Cols("rdns").Update(&i) for _, orphan := range orphans {
orphanchan <- orphan
}
close(orphanchan)
wg.Wait()
} else {
time.Sleep(2 * time.Second)
}
}
}
func ScanOrphan(wg *sync.WaitGroup, orphans chan IP, thr int, cfg *config.Config) (err error) {
wg.Add(1)
session := cfg.Db.NewSession()
defer session.Close()
queryclient := http.Client{}
for {
orphan, more := <-orphans
if more {
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
var query QueryIP
query, err := QueryInfo(cfg, &queryclient, orphan.IP)
if err != nil {
log.Println(err)
time.Sleep(1 * time.Minute)
continue
} }
var as = AutonomousSystem{ASID: query.APIAS.Number, ASName: query.APIAS.Org}
orphan.AutonomousSystem = &as
if query.APICity != "" {
var city = City{CityName: query.APICity}
orphan.City = &city
}
if query.APICountry != "" {
var country = Country{CountryName: query.APICountry}
orphan.Country = &country
}
orphan.Rdns = sql.NullString{String: query.Rdns, Valid: true}
if cfg.Options.Debug {
log.Printf("t%d: %s -> \"%s\"\n", thr, orphan.IP, query.Rdns)
}
_, err = orphan.GetOrCreate(session)
if err != nil {
continue
}
} else { } else {
time.Sleep(60 * time.Second) log.Printf("All orphan migrated on thread num %d\n", thr)
wg.Done()
break
} }
} }
err = session.Commit()
if err != nil {
log.Println(err)
}
return nil
} }
// APIFormat returns a JSON formatted object of IP func QueryInfo(cfg *config.Config, client *http.Client, ip string) (query QueryIP, err error) {
func (ip *IP) APIFormat() *api.IP { var url = fmt.Sprintf("%s/%s", IPINFO_WS, ip)
if ip == nil { req, err := http.NewRequest("GET", url, nil)
return nil if err != nil {
log.Println(err)
} }
return &api.IP{ req.Header.Add("Accept", "*/*")
req.Header.Add("User-Agent", fmt.Sprintf("ipbl %s", cfg.Options.Version))
res, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
data, err := io.ReadAll(res.Body)
if err != nil {
log.Println(err)
}
err = json.Unmarshal(data, &query)
if err != nil {
log.Println(err)
}
return
}
func Cleanup(session *xorm.Session) (err error) {
return
}
func (ip *IP) APIFormat() *APIIP {
if ip == nil {
return &APIIP{}
}
return &APIIP{
IP: ip.IP, IP: ip.IP,
Rdns: ip.Rdns.String, Rdns: ip.Rdns.String,
Src: ip.Src, APIAS: *ip.AutonomousSystem.APIFormat(),
APICity: ip.City.APIFormat().CityName,
APICountry: ip.Country.APIFormat().CountryName,
} }
} }
// IP describe IP objects func (ip *IP) APIParse(apiip APIIP) (err error) {
type IP struct { *ip = IP{
ID int `xorm:"pk autoincr" json:"-"` IP: apiip.IP,
IP string `xorm:"text notnull unique(ipsrc)" json:"ip"` Rdns: sql.NullString{
Rdns sql.NullString `xorm:"text default" json:"rdns"` String: apiip.Rdns,
Src string `xorm:"text notnull unique(ipsrc)" json:"src"` Valid: true},
Created time.Time `xorm:"created notnull" json:"-"` AutonomousSystem: &AutonomousSystem{
Updated time.Time `xorm:"updated notnull" json:"-"` ASID: apiip.APIAS.ASID,
ASName: apiip.APIAS.ASName,
},
}
return
}
func (ip *APIIP) BeforeInsert() (err error) {
return
}
type IP struct {
ID int `xorm:"pk autoincr"`
IP string `xorm:"text notnull unique"`
Rdns sql.NullString `xorm:"text index default ''"`
AutonomousSystem *AutonomousSystem `xorm:"as_id int index default null"`
City *City `xorm:"city_id int index default null"`
Country *Country `xorm:"country_id int index default null"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated index notnull"`
}
type APIIP struct {
IP string `json:"ip"`
Rdns string `json:"rdns"`
APIAS APIAutonomousSystem `json:"as"`
APICity string `json:"city"`
APICountry string `json:"country"`
APIWhois string `json:"whois"`
}
type QueryIP struct {
IP string `json:"ip"`
Rdns string `json:"hostname"`
APIAS QueryAutonomousSystem `json:"as"`
APICity string `json:"city"`
APICountry string `json:"country"`
}
type QueryAutonomousSystem struct {
Number int `json:"number"`
Org string `json:"org"`
} }

64
src/models/models.go Normal file
View File

@ -0,0 +1,64 @@
package models
import (
"context"
"fmt"
"git.paulbsd.com/paulbsd/ipbl/src/config"
"xorm.io/xorm/names"
)
var (
tables []interface{}
HasEngine bool
)
func init() {
tables = append(tables,
new(AutonomousSystem),
new(Cfg),
new(CfgSet),
new(CfgExpr),
new(CfgTrustlist),
new(CfgWS),
new(City),
new(Country),
new(Event),
new(IP),
new(Src),
new(Host),
)
for _, name := range []string{"SSL", "UID"} {
names.LintGonicMapper[name] = true
}
}
// NewEngine initializes a new xorm.Engine
func NewEngine(ctx *context.Context, config *config.Config) (err error) {
var x = config.Db
if err = x.Ping(); err != nil {
return err
}
if err = x.Sync2(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v", err)
}
return nil
}
// DropTables initializes a new xorm.Engine
func DropTables(ctx *context.Context, config *config.Config) (err error) {
var x = config.Db
if err = x.Ping(); err != nil {
return err
}
if err = x.Sync2(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v", err)
}
return nil
}

65
src/models/scanresult.go Normal file
View File

@ -0,0 +1,65 @@
package models
import (
"time"
"git.paulbsd.com/paulbsd/ipbl/src/config"
)
func (sr *ScanResult) GetOrCreate(cfg *config.Config) (apisr *APIScanResult, err error) {
has, err := cfg.Db.Get(sr)
if !has {
cfg.Db.Insert(sr)
} else {
cfg.Db.ID(sr.ID).Update(sr)
}
sr.Get(cfg)
apisr = sr.APIFormat()
return
}
func (sr *ScanResult) Get(cfg *config.Config) (apisr *APIScanResult, err error) {
has, err := cfg.Db.Get(sr)
if !has || err != nil {
return
}
apisr = sr.APIFormat()
return
}
func (sr *ScanResult) APIFormat() *APIScanResult {
if sr == nil {
return &APIScanResult{}
}
return &APIScanResult{
Protocol: sr.Protocol,
PortID: sr.PortID,
State: sr.State,
ServiceName: sr.ServiceName,
}
}
func (sr *ScanResult) APIParse(apisr APIScanResult) (err error) {
*sr = ScanResult{
Protocol: apisr.Protocol,
PortID: apisr.PortID}
return
}
type ScanResult struct {
ID int `xorm:"pk autoincr"`
Protocol string `xorm:"text default ''"`
PortID int `xorm:"default ''"`
State string `xorm:"text default ''"`
ServiceName string `xorm:"text default ''"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APIScanResult struct {
ID int `json:"id"`
Protocol string `json:"proto"`
PortID int `json:"port_id"`
State string `json:"state"`
ServiceName string `json:"service"`
}

View File

@ -1,7 +1,58 @@
package models package models
// Src is src types import (
type Src struct { "time"
ID int `xorm:"pk autoincr" json:"-"`
Src string `xorm:"text notnull unique" json:"src"` "xorm.io/xorm"
)
func (src *Src) GetOrCreate(session *xorm.Session) (apisrc *APISrc, err error) {
has, err := session.Get(src)
if err != nil {
return
}
if !has {
session.Insert(src)
} else {
session.ID(src.ID).Update(src)
}
src.Get(session)
apisrc = src.APIFormat()
return
}
func (src *Src) Get(session *xorm.Session) (apisrc *APISrc, err error) {
has, err := session.Get(src)
if !has || err != nil {
return
}
apisrc = src.APIFormat()
return
}
func (src *Src) APIFormat() *APISrc {
if src == nil {
return &APISrc{}
}
return &APISrc{
Src: src.Src,
}
}
func (src *Src) APIParse(apisrc APISrc) (err error) {
*src = Src{
Src: apisrc.Src}
return
}
type Src struct {
ID int `xorm:"pk autoincr"`
Src string `xorm:"text unique(srcindex) src" json:"src"`
Created time.Time `xorm:"created notnull"`
Updated time.Time `xorm:"updated notnull"`
}
type APISrc struct {
ID int `json:"-"`
Src string `json:"src"`
} }

View File

@ -2,9 +2,7 @@ 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...)
@ -13,7 +11,7 @@ func differ(sl1 []IP, sl2 []IP) (toinsert []IP, err error) {
m[v2.IP] = IPDiffer{IP: v2, Num: 1} m[v2.IP] = IPDiffer{IP: v2, Num: 1}
} else { } else {
if this, ok := m[v2.IP]; ok { if this, ok := m[v2.IP]; ok {
this.Num += 1 this.Num++
m[v2.IP] = this m[v2.IP] = this
} }
} }

View File

@ -3,45 +3,53 @@ 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"
) )
// RegisterRoutes runs the main echo HTTP server
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 style="background-color: black"> <html>
<p style="color:trust">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 {
return c.HTML(http.StatusOK, `OK`)
}) })
e.GET("/ip/:ip", func(c echo.Context) (err error) { e.GET("/ip/:ip", func(c echo.Context) (err error) {
ret, err := models.GetIP(ctx, cfg, c.Param("ip")) session := cfg.Db.NewSession()
defer session.Close()
var ip = models.IP{IP: c.Param("ip")}
ret, err := ip.Get(session)
return Result(c, err, ret) return Result(c, err, ret)
}) })
e.POST("/ip", func(c echo.Context) (err error) { e.POST("/ip", func(c echo.Context) (err error) {
session := cfg.Db.NewSession()
var apiip = new(models.APIIP)
var ip = new(models.IP) var ip = new(models.IP)
var msg = "No IP inserted" var msg = "No IP inserted"
err = c.Bind(ip) err = c.Bind(apiip)
if err != nil { if err != nil {
return Result(c, fmt.Errorf("error when parsing body"), "") return Result(c, fmt.Errorf("error when parsing body"), "")
} }
num, err := ip.InsertIP(cfg) ip.APIParse(*apiip)
fmt.Println(err) _, err = ip.GetOrCreate(session)
if err != nil { if err != nil {
return Result(c, err, "") return Result(c, err, "")
} }
if num > 0 {
msg = fmt.Sprintf("Inserted %d IP", num)
}
return Result(c, err, msg) return Result(c, err, msg)
}) })
e.GET("/ips", func(c echo.Context) (err error) { e.GET("/ips", func(c echo.Context) (err error) {
@ -52,65 +60,104 @@ func RegisterRoutes(e *echo.Echo, ctx *context.Context, cfg *config.Config) {
ret, err := models.GetIPs(ctx, cfg, limit) ret, err := models.GetIPs(ctx, cfg, limit)
return Result(c, err, ret) return Result(c, err, ret)
}) })
// retro-compat e.GET("/ips/last", func(c echo.Context) (err error) {
e.GET("/ips/lastday", func(c echo.Context) (err error) { interval := "30m"
interval := "1 day" if c.QueryParam("interval") != "" {
interval = c.QueryParam("interval")
}
ret, err := models.GetIPsLast(ctx, cfg, interval) ret, err := models.GetIPsLast(ctx, cfg, interval)
return Result(c, err, ret) return Result(c, err, ret)
}) })
e.GET("/ips/last", func(c echo.Context) (err error) { e.GET("/ips/withouthostinfo", func(c echo.Context) (err error) {
interval := "10 minutes" ret, err := models.GetIPsWithoutHostInfo(ctx, cfg)
ret, err := models.GetIPsLast(ctx, cfg, interval)
return Result(c, err, ret) return Result(c, err, ret)
}) })
e.POST("/ips", func(c echo.Context) (err error) { e.POST("/ips", func(c echo.Context) (err error) {
session := cfg.Db.NewSession()
defer session.Close()
var apiips = []models.APIIP{}
var ips = []models.IP{} var ips = []models.IP{}
var msg string var msg string
err = c.Bind(&ips) err = c.Bind(&apiips)
if err != nil { if err != nil {
return Result(c, err, ips) return Result(c, err, apiips)
} }
numinsert, numupdate, _, _ := models.InsertIPBulk(cfg, &ips) for _, apiip := range apiips {
if numinsert > 0 { var ip = new(models.IP)
msg = fmt.Sprintf("inserted %d IP", numinsert) ip.APIParse(apiip)
log.Println(msg) ips = append(ips, *ip)
}
if numupdate > 0 {
if len(msg) > 0 {
msg = fmt.Sprintf("%s, updated %d IP", msg, numupdate)
} else {
msg = fmt.Sprintf("updated %d IP", numupdate)
}
log.Println(msg)
} }
numinsert, numupdate, _ := models.InsertIPBulk(session, &ips)
msg = fmt.Sprintf("Inserted %d IP, Updated %d IP", numinsert, numupdate)
return Result(c, err, msg) return Result(c, err, msg)
}) })
e.GET("/ips/trustlist", func(c echo.Context) (err error) { e.POST("/event", func(c echo.Context) (err error) {
trustlists, err := models.GetTrustlists(*cfg) session := cfg.Db.NewSession()
defer session.Close()
var apievent = new(models.APIEvent)
var event = new(models.Event)
err = c.Bind(apievent)
if err != nil {
return Result(c, fmt.Errorf("error when parsing body"), "")
}
event.APIParse(session, *apievent)
err = event.Insert(cfg)
if err != nil {
return Result(c, err, "")
}
return Result(c, err, "OK")
})
e.POST("/hostinfo", func(c echo.Context) (err error) {
var hostinfos = []models.HostInfo{}
err = c.Bind(&hostinfos)
if err != nil {
return Result(c, err, hostinfos)
}
models.ProcessHostInfo(cfg, hostinfos)
return Result(c, err, "")
})
e.GET("/config", func(c echo.Context) (err error) {
globalconfig, err := models.GetAllConfigv2(cfg)
return Result(c, err, globalconfig)
})
e.GET("/config/trustlist", func(c echo.Context) (err error) {
trustlists, err := models.GetTrustlists(cfg)
return Result(c, err, trustlists) return Result(c, err, trustlists)
}) })
e.POST("/ips/trustlist", func(c echo.Context) (err error) { e.POST("/config/trustlist", func(c echo.Context) (err error) {
var cidr models.Trustlist 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)
}) })
e.DELETE("/ips/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.Trustlist var cidr models.CfgTrustlist
err = cidr.Delete(*cfg, ip) _, err = cidr.Delete(cfg, ip)
if err != nil { return
return c.JSON(http.StatusOK, "Deleted old CIDR") })
} e.GET("/config/sets", func(c echo.Context) (err error) {
sets, err := models.GetSets(cfg)
return Result(c, err, sets)
})
e.GET("/config/ws", func(c echo.Context) (err error) {
folders, err := models.GetWS(cfg)
return Result(c, err, folders)
})
e.GET("/discovery", func(c echo.Context) (err error) {
disc, err := models.DiscoverURLS(cfg, e.Routes())
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 return
}) })
e.Logger.Fatal(
e.Start(
fmt.Sprintf(":%d",
cfg.Switchs.Port)))
} }

View File

@ -10,20 +10,23 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
// RunServer runs the main echo server
func RunServer(ctx *context.Context, cfg *config.Config) (err error) { func RunServer(ctx *context.Context, cfg *config.Config) (err error) {
e := echo.New() e := echo.New()
e.HideBanner = true e.HideBanner = true
RegisterRoutes(e, ctx, cfg) RegisterRoutes(e, ctx, cfg)
err = e.Start(
fmt.Sprintf(":%d",
cfg.Switchs.Port))
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.Switchs.Port)))
return return
} }
// Result handles returns and error management on backend api
func Result(c echo.Context, inputerr error, data interface{}) (err error) { func Result(c echo.Context, inputerr error, data interface{}) (err error) {
if inputerr != nil { if inputerr != nil {
if inputerr.Error() == "not found" {
return c.String(http.StatusNotFound, "Content not found")
}
if inputerr.Error() == "Not Found" { if inputerr.Error() == "Not Found" {
return c.String(http.StatusNotFound, "Content not found") return c.String(http.StatusNotFound, "Content not found")
} }
@ -41,7 +44,6 @@ func Result(c echo.Context, inputerr error, data interface{}) (err error) {
return c.JSON(http.StatusOK, data) return c.JSON(http.StatusOK, data)
} }
// ConfigAccess make ip authorization to configuration
func ConfigAccess(cfg config.Config, ip string) (ret bool) { func ConfigAccess(cfg config.Config, ip string) (ret bool) {
switch ip { switch ip {
case "127.0.0.1": case "127.0.0.1":

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

@ -4,6 +4,7 @@ import (
"flag" "flag"
"log" "log"
"os" "os"
"time"
) )
// Usage displays possible arguments // Usage displays possible arguments
@ -20,3 +21,9 @@ func Advice(advice string) {
} }
os.Exit(0) os.Exit(0)
} }
func Server() {
for {
time.Sleep(time.Second)
}
}

View File

@ -48,6 +48,17 @@ linters:
- nlreturn - nlreturn
- testpackage - testpackage
- wsl - wsl
- varnamelen
- nilnil
- ireturn
- govet
- forcetypeassert
- cyclop
- containedctx
- revive
- nosnakecase
- exhaustruct
- depguard
issues: issues:
exclude-rules: exclude-rules:

View File

@ -1,5 +1,120 @@
# 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
### Fix bugs
* Fix unexpected behavior when buffer ends with backslash ( #383 )
* Fix stream decoding of escaped character ( #387 )
# v0.9.10 - 2022/07/15
### Fix bugs
* Fix boundary exception of type caching ( #382 )
# v0.9.9 - 2022/07/15
### Fix bugs
* Fix encoding of directed interface with typed nil ( #377 )
* Fix embedded primitive type encoding using alias ( #378 )
* Fix slice/array type encoding with types implementing MarshalJSON ( #379 )
* Fix unicode decoding when the expected buffer state is not met after reading ( #380 )
# v0.9.8 - 2022/06/30
### Fix bugs
* Fix decoding of surrogate-pair ( #365 )
* Fix handling of embedded primitive type ( #366 )
* Add validation of escape sequence for decoder ( #367 )
* Fix stream tokenizing respecting UseNumber ( #369 )
* Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 )
### Improve performance
* Improve performance of linkRecursiveCode ( #368 )
# v0.9.7 - 2022/04/22
### Fix bugs
#### Encoder
* Add filtering process for encoding on slow path ( #355 )
* Fix encoding of interface{} with pointer type ( #363 )
#### Decoder
* Fix map key decoder that implements UnmarshalJSON ( #353 )
* Fix decoding of []uint8 type ( #361 )
### New features
* Add DebugWith option for encoder ( #356 )
# v0.9.6 - 2022/03/22
### Fix bugs
* Correct the handling of the minimum value of int type for decoder ( #344 )
* Fix bugs of stream decoder's bufferSize ( #349 )
* Add a guard to use typeptr more safely ( #351 )
### Improve decoder performance
* Improve escapeString's performance ( #345 )
### Others
* Update go version for CI ( #347 )
# v0.9.5 - 2022/03/04
### Fix bugs
* Fix panic when decoding time.Time with context ( #328 )
* Fix reading the next character in buffer to nul consideration ( #338 )
* Fix incorrect handling on skipValue ( #341 )
### Improve decoder performance
* Improve performance when a payload contains escape sequence ( #334 )
# v0.9.4 - 2022/01/21 # v0.9.4 - 2022/01/21
* Fix IsNilForMarshaler for string type with omitempty ( #323 )
* Fix the case where the embedded field is at the end ( #326 ) * Fix the case where the embedded field is at the end ( #326 )
# v0.9.3 - 2022/01/14 # v0.9.3 - 2022/01/14

View File

@ -22,7 +22,7 @@ cover-html: cover
.PHONY: lint .PHONY: lint
lint: golangci-lint lint: golangci-lint
golangci-lint run $(BIN_DIR)/golangci-lint run
golangci-lint: | $(BIN_DIR) golangci-lint: | $(BIN_DIR)
@{ \ @{ \
@ -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 get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.36.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

@ -184,7 +184,7 @@ func Marshal(v interface{}) ([]byte, error) {
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process. `json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped. In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
Therefore, the arguments for `Marshal` and `Unmarshal` are always escape to the heap. Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap.
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping. However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package. `reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.

View File

@ -83,6 +83,37 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
return validateEndBuf(src, cursor) return validateEndBuf(src, cursor)
} }
var (
pathDecoder = decoder.NewPathDecoder()
)
func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
if path.path.RootSelectorOnly {
return [][]byte{data}, nil
}
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)
ctx := decoder.TakeRuntimeContext()
ctx.Buf = src
ctx.Option.Flags = 0
ctx.Option.Flags |= decoder.PathOption
ctx.Option.Path = path.path
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0)
if err != nil {
decoder.ReleaseRuntimeContext(ctx)
return nil, err
}
decoder.ReleaseRuntimeContext(ctx)
if err := validateEndBuf(src, cursor); err != nil {
return nil, err
}
return paths, nil
}
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error { func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
src := make([]byte, len(data)+1) // append nul byte to the end src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data) copy(src, data)

View File

@ -1,7 +1,7 @@
version: '2' version: '2'
services: services:
go-json: go-json:
image: golang:1.17 image: golang:1.18
volumes: volumes:
- '.:/go/src/go-json' - '.:/go/src/go-json'
deploy: deploy:

View File

@ -3,6 +3,7 @@ package json
import ( import (
"context" "context"
"io" "io"
"os"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/encoder"
@ -51,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
@ -62,6 +63,7 @@ func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, o
ctx.Option.Flag |= encoder.HTMLEscapeOption ctx.Option.Flag |= encoder.HTMLEscapeOption
} }
ctx.Option.Flag |= encoder.NormalizeUTF8Option ctx.Option.Flag |= encoder.NormalizeUTF8Option
ctx.Option.DebugOut = os.Stdout
for _, optFunc := range optFuncs { for _, optFunc := range optFuncs {
optFunc(ctx.Option) optFunc(ctx.Option)
} }
@ -118,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

@ -37,3 +37,5 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
type UnsupportedTypeError = errors.UnsupportedTypeError type UnsupportedTypeError = errors.UnsupportedTypeError
type UnsupportedValueError = errors.UnsupportedValueError type UnsupportedValueError = errors.UnsupportedValueError
type PathError = errors.PathError

View File

@ -35,3 +35,7 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
p = *(*unsafe.Pointer)(p) p = *(*unsafe.Pointer)(p)
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset)) return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
} }
func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return d.dec.DecodePath(ctx, cursor, depth)
}

View File

@ -1,6 +1,7 @@
package decoder package decoder
import ( import (
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
@ -18,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,
@ -167,3 +170,7 @@ func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
} }
} }
} }
func (d *arrayDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: array decoder does not support decode path")
}

View File

@ -0,0 +1,438 @@
package decoder
import (
"fmt"
"reflect"
"strconv"
)
var (
nilValue = reflect.ValueOf(nil)
)
func AssignValue(src, dst reflect.Value) error {
if dst.Type().Kind() != reflect.Ptr {
return fmt.Errorf("invalid dst type. required pointer type: %T", dst.Type())
}
casted, err := castValue(dst.Elem().Type(), src)
if err != nil {
return err
}
dst.Elem().Set(casted)
return nil
}
func castValue(t reflect.Type, v reflect.Value) (reflect.Value, error) {
switch t.Kind() {
case reflect.Int:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int(vv.Int())), nil
case reflect.Int8:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int8(vv.Int())), nil
case reflect.Int16:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int16(vv.Int())), nil
case reflect.Int32:
vv, err := castInt(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(int32(vv.Int())), nil
case reflect.Int64:
return castInt(v)
case reflect.Uint:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint(vv.Uint())), nil
case reflect.Uint8:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint8(vv.Uint())), nil
case reflect.Uint16:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint16(vv.Uint())), nil
case reflect.Uint32:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uint32(vv.Uint())), nil
case reflect.Uint64:
return castUint(v)
case reflect.Uintptr:
vv, err := castUint(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(uintptr(vv.Uint())), nil
case reflect.String:
return castString(v)
case reflect.Bool:
return castBool(v)
case reflect.Float32:
vv, err := castFloat(v)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(float32(vv.Float())), nil
case reflect.Float64:
return castFloat(v)
case reflect.Array:
return castArray(t, v)
case reflect.Slice:
return castSlice(t, v)
case reflect.Map:
return castMap(t, v)
case reflect.Struct:
return castStruct(t, v)
}
return v, nil
}
func castInt(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(int64(v.Uint())), nil
case reflect.String:
i64, err := strconv.ParseInt(v.String(), 10, 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(i64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(int64(1)), nil
}
return reflect.ValueOf(int64(0)), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(int64(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castInt(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to int64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castInt(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to int64 from empty slice")
case reflect.Interface:
return castInt(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to int64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to int64 from struct")
case reflect.Ptr:
return castInt(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to int64 from %s", v.Type().Kind())
}
func castUint(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(uint64(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v, nil
case reflect.String:
u64, err := strconv.ParseUint(v.String(), 10, 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(u64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(uint64(1)), nil
}
return reflect.ValueOf(uint64(0)), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(uint64(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castUint(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to uint64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castUint(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to uint64 from empty slice")
case reflect.Interface:
return castUint(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to uint64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to uint64 from struct")
case reflect.Ptr:
return castUint(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to uint64 from %s", v.Type().Kind())
}
func castString(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(fmt.Sprint(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(fmt.Sprint(v.Uint())), nil
case reflect.String:
return v, nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf("true"), nil
}
return reflect.ValueOf("false"), nil
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(fmt.Sprint(v.Float())), nil
case reflect.Array:
if v.Len() > 0 {
return castString(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castString(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
case reflect.Interface:
return castString(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to string from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to string from struct")
case reflect.Ptr:
return castString(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to string from %s", v.Type().Kind())
}
func castBool(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch v.Int() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
switch v.Uint() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Uint())
case reflect.String:
b, err := strconv.ParseBool(v.String())
if err != nil {
return nilValue, err
}
return reflect.ValueOf(b), nil
case reflect.Bool:
return v, nil
case reflect.Float32, reflect.Float64:
switch v.Float() {
case 0:
return reflect.ValueOf(false), nil
case 1:
return reflect.ValueOf(true), nil
}
return nilValue, fmt.Errorf("failed to cast to bool from %f", v.Float())
case reflect.Array:
if v.Len() > 0 {
return castBool(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castBool(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
case reflect.Interface:
return castBool(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to string from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to string from struct")
case reflect.Ptr:
return castBool(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to bool from %s", v.Type().Kind())
}
func castFloat(v reflect.Value) (reflect.Value, error) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(float64(v.Int())), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return reflect.ValueOf(float64(v.Uint())), nil
case reflect.String:
f64, err := strconv.ParseFloat(v.String(), 64)
if err != nil {
return nilValue, err
}
return reflect.ValueOf(f64), nil
case reflect.Bool:
if v.Bool() {
return reflect.ValueOf(float64(1)), nil
}
return reflect.ValueOf(float64(0)), nil
case reflect.Float32, reflect.Float64:
return v, nil
case reflect.Array:
if v.Len() > 0 {
return castFloat(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to float64 from empty array")
case reflect.Slice:
if v.Len() > 0 {
return castFloat(v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to float64 from empty slice")
case reflect.Interface:
return castFloat(reflect.ValueOf(v.Interface()))
case reflect.Map:
return nilValue, fmt.Errorf("failed to cast to float64 from map")
case reflect.Struct:
return nilValue, fmt.Errorf("failed to cast to float64 from struct")
case reflect.Ptr:
return castFloat(v.Elem())
}
return nilValue, fmt.Errorf("failed to cast to float64 from %s", v.Type().Kind())
}
func castArray(t reflect.Type, v reflect.Value) (reflect.Value, error) {
kind := v.Type().Kind()
if kind == reflect.Interface {
return castArray(t, reflect.ValueOf(v.Interface()))
}
if kind != reflect.Slice && kind != reflect.Array {
return nilValue, fmt.Errorf("failed to cast to array from %s", kind)
}
if t.Elem() == v.Type().Elem() {
return v, nil
}
if t.Len() != v.Len() {
return nilValue, fmt.Errorf("failed to cast [%d]array from slice of %d length", t.Len(), v.Len())
}
ret := reflect.New(t).Elem()
for i := 0; i < v.Len(); i++ {
vv, err := castValue(t.Elem(), v.Index(i))
if err != nil {
return nilValue, err
}
ret.Index(i).Set(vv)
}
return ret, nil
}
func castSlice(t reflect.Type, v reflect.Value) (reflect.Value, error) {
kind := v.Type().Kind()
if kind == reflect.Interface {
return castSlice(t, reflect.ValueOf(v.Interface()))
}
if kind != reflect.Slice && kind != reflect.Array {
return nilValue, fmt.Errorf("failed to cast to slice from %s", kind)
}
if t.Elem() == v.Type().Elem() {
return v, nil
}
ret := reflect.MakeSlice(t, v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
vv, err := castValue(t.Elem(), v.Index(i))
if err != nil {
return nilValue, err
}
ret.Index(i).Set(vv)
}
return ret, nil
}
func castMap(t reflect.Type, v reflect.Value) (reflect.Value, error) {
ret := reflect.MakeMap(t)
switch v.Type().Kind() {
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
key, err := castValue(t.Key(), iter.Key())
if err != nil {
return nilValue, err
}
value, err := castValue(t.Elem(), iter.Value())
if err != nil {
return nilValue, err
}
ret.SetMapIndex(key, value)
}
return ret, nil
case reflect.Interface:
return castMap(t, reflect.ValueOf(v.Interface()))
case reflect.Slice:
if v.Len() > 0 {
return castMap(t, v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to map from empty slice")
}
return nilValue, fmt.Errorf("failed to cast to map from %s", v.Type().Kind())
}
func castStruct(t reflect.Type, v reflect.Value) (reflect.Value, error) {
ret := reflect.New(t).Elem()
switch v.Type().Kind() {
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
k, err := castString(key)
if err != nil {
return nilValue, err
}
fieldName := k.String()
field, ok := t.FieldByName(fieldName)
if ok {
value, err := castValue(field.Type, iter.Value())
if err != nil {
return nilValue, err
}
ret.FieldByName(fieldName).Set(value)
}
}
return ret, nil
case reflect.Struct:
for i := 0; i < v.Type().NumField(); i++ {
name := v.Type().Field(i).Name
ret.FieldByName(name).Set(v.FieldByName(name))
}
return ret, nil
case reflect.Interface:
return castStruct(t, reflect.ValueOf(v.Interface()))
case reflect.Slice:
if v.Len() > 0 {
return castStruct(t, v.Index(0))
}
return nilValue, fmt.Errorf("failed to cast to struct from empty slice")
default:
return nilValue, fmt.Errorf("failed to cast to struct from %s", v.Type().Kind())
}
}

View File

@ -1,6 +1,7 @@
package decoder package decoder
import ( import (
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
@ -76,3 +77,7 @@ func (d *boolDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
} }
return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor) return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor)
} }
func (d *boolDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: bool decoder does not support decode path")
}

View File

@ -2,6 +2,7 @@ package decoder
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
@ -23,9 +24,8 @@ func byteUnmarshalerSliceDecoder(typ *runtime.Type, structName string, fieldName
unmarshalDecoder = newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName) unmarshalDecoder = newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName)
case runtime.PtrTo(typ).Implements(unmarshalTextType): case runtime.PtrTo(typ).Implements(unmarshalTextType):
unmarshalDecoder = newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName) unmarshalDecoder = newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName)
} default:
if unmarshalDecoder == nil { unmarshalDecoder, _ = compileUint8(typ, structName, fieldName)
return nil
} }
return newSliceDecoder(unmarshalDecoder, typ, 1, structName, fieldName) return newSliceDecoder(unmarshalDecoder, typ, 1, structName, fieldName)
} }
@ -79,6 +79,10 @@ func (d *bytesDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
return cursor, nil return cursor, nil
} }
func (d *bytesDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: []byte decoder does not support decode path")
}
func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) { func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) {
c := s.skipWhiteSpace() c := s.skipWhiteSpace()
if c == '[' { if c == '[' {

View File

@ -24,7 +24,7 @@ func init() {
if typeAddr == nil { if typeAddr == nil {
typeAddr = &runtime.TypeAddr{} typeAddr = &runtime.TypeAddr{}
} }
cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift) cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift+1)
} }
func loadDecoderMap() map[uintptr]Decoder { func loadDecoderMap() map[uintptr]Decoder {
@ -154,6 +154,9 @@ func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeTo
if runtime.PtrTo(typ).Implements(unmarshalTextType) { if runtime.PtrTo(typ).Implements(unmarshalTextType) {
return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil
} }
if typ.Kind() == reflect.String {
return newStringDecoder(structName, fieldName), nil
}
dec, err := compile(typ, structName, fieldName, structTypeToDecoder) dec, err := compile(typ, structName, fieldName, structTypeToDecoder)
if err != nil { if err != nil {
return nil, err return nil, err
@ -390,7 +393,25 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
} }
allFields = append(allFields, fieldSet) allFields = append(allFields, fieldSet)
} }
} else {
fieldSet := &structFieldSet{
dec: pdec,
offset: field.Offset,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
} }
allFields = append(allFields, fieldSet)
}
} else {
fieldSet := &structFieldSet{
dec: dec,
offset: field.Offset,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
}
allFields = append(allFields, fieldSet)
} }
} else { } else {
if tag.IsString && isStringTagSupportedType(runtime.Type2RType(field.Type)) { if tag.IsString && isStringTagSupportedType(runtime.Type2RType(field.Type)) {

View File

@ -1,3 +1,4 @@
//go:build !race
// +build !race // +build !race
package decoder package decoder

View File

@ -1,3 +1,4 @@
//go:build race
// +build race // +build race
package decoder package decoder

View File

@ -156,3 +156,15 @@ func (d *floatDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
d.op(p, f64) d.op(p, f64)
return cursor, nil return cursor, nil
} }
func (d *floatDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
bytes, c, err := d.decodeByte(buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}

View File

@ -2,6 +2,7 @@ package decoder
import ( import (
"bytes" "bytes"
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
@ -139,3 +140,7 @@ func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
} }
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
} }
func (d *funcDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: func decoder does not support decode path")
}

View File

@ -192,15 +192,15 @@ func (d *intDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
} }
switch d.kind { switch d.kind {
case reflect.Int8: case reflect.Int8:
if i64 <= -1*(1<<7) || (1<<7) <= i64 { if i64 < -1*(1<<7) || (1<<7) <= i64 {
return d.typeError(bytes, s.totalOffset()) return d.typeError(bytes, s.totalOffset())
} }
case reflect.Int16: case reflect.Int16:
if i64 <= -1*(1<<15) || (1<<15) <= i64 { if i64 < -1*(1<<15) || (1<<15) <= i64 {
return d.typeError(bytes, s.totalOffset()) return d.typeError(bytes, s.totalOffset())
} }
case reflect.Int32: case reflect.Int32:
if i64 <= -1*(1<<31) || (1<<31) <= i64 { if i64 < -1*(1<<31) || (1<<31) <= i64 {
return d.typeError(bytes, s.totalOffset()) return d.typeError(bytes, s.totalOffset())
} }
} }
@ -225,18 +225,22 @@ func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
} }
switch d.kind { switch d.kind {
case reflect.Int8: case reflect.Int8:
if i64 <= -1*(1<<7) || (1<<7) <= i64 { if i64 < -1*(1<<7) || (1<<7) <= i64 {
return 0, d.typeError(bytes, cursor) return 0, d.typeError(bytes, cursor)
} }
case reflect.Int16: case reflect.Int16:
if i64 <= -1*(1<<15) || (1<<15) <= i64 { if i64 < -1*(1<<15) || (1<<15) <= i64 {
return 0, d.typeError(bytes, cursor) return 0, d.typeError(bytes, cursor)
} }
case reflect.Int32: case reflect.Int32:
if i64 <= -1*(1<<31) || (1<<31) <= i64 { if i64 < -1*(1<<31) || (1<<31) <= i64 {
return 0, d.typeError(bytes, cursor) return 0, d.typeError(bytes, cursor)
} }
} }
d.op(p, i64) d.op(p, i64)
return cursor, nil return cursor, nil
} }
func (d *intDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: int decoder does not support decode path")
}

View File

@ -94,6 +94,7 @@ func (d *interfaceDecoder) numDecoder(s *Stream) Decoder {
var ( var (
emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem()) emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem())
EmptyInterfaceType = emptyInterfaceType
interfaceMapType = runtime.Type2RType( interfaceMapType = runtime.Type2RType(
reflect.TypeOf((*map[string]interface{})(nil)).Elem(), reflect.TypeOf((*map[string]interface{})(nil)).Elem(),
) )
@ -456,3 +457,72 @@ func (d *interfaceDecoder) decodeEmptyInterface(ctx *RuntimeContext, cursor, dep
} }
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor) return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
} }
func NewPathDecoder() Decoder {
ifaceDecoder := &interfaceDecoder{
typ: emptyInterfaceType,
structName: "",
fieldName: "",
floatDecoder: newFloatDecoder("", "", func(p unsafe.Pointer, v float64) {
*(*interface{})(p) = v
}),
numberDecoder: newNumberDecoder("", "", func(p unsafe.Pointer, v json.Number) {
*(*interface{})(p) = v
}),
stringDecoder: newStringDecoder("", ""),
}
ifaceDecoder.sliceDecoder = newSliceDecoder(
ifaceDecoder,
emptyInterfaceType,
emptyInterfaceType.Size(),
"", "",
)
ifaceDecoder.mapDecoder = newMapDecoder(
interfaceMapType,
stringType,
ifaceDecoder.stringDecoder,
interfaceMapType.Elem(),
ifaceDecoder,
"", "",
)
return ifaceDecoder
}
var (
truebytes = []byte("true")
falsebytes = []byte("false")
)
func (d *interfaceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
cursor = skipWhiteSpace(buf, cursor)
switch buf[cursor] {
case '{':
return d.mapDecoder.DecodePath(ctx, cursor, depth)
case '[':
return d.sliceDecoder.DecodePath(ctx, cursor, depth)
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return d.floatDecoder.DecodePath(ctx, cursor, depth)
case '"':
return d.stringDecoder.DecodePath(ctx, cursor, depth)
case 't':
if err := validateTrue(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{truebytes}, cursor, nil
case 'f':
if err := validateFalse(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 5
return [][]byte{falsebytes}, cursor, nil
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
}
return nil, cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
}

View File

@ -43,3 +43,13 @@ func (d *invalidDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsa
Field: d.fieldName, Field: d.fieldName,
} }
} }
func (d *invalidDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}

View File

@ -87,13 +87,13 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if mapValue == nil { if mapValue == nil {
mapValue = makemap(d.mapType, 0) mapValue = makemap(d.mapType, 0)
} }
if s.buf[s.cursor+1] == '}' { s.cursor++
if s.skipWhiteSpace() == '}' {
*(*unsafe.Pointer)(p) = mapValue *(*unsafe.Pointer)(p) = mapValue
s.cursor += 2 s.cursor++
return nil return nil
} }
for { for {
s.cursor++
k := unsafe_New(d.keyType) k := unsafe_New(d.keyType)
if err := d.keyDecoder.DecodeStream(s, depth, k); err != nil { if err := d.keyDecoder.DecodeStream(s, depth, k); err != nil {
return err return err
@ -117,6 +117,7 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if !s.equalChar(',') { if !s.equalChar(',') {
return errors.ErrExpected("comma after object value", s.totalOffset()) return errors.ErrExpected("comma after object value", s.totalOffset())
} }
s.cursor++
} }
} }
@ -184,3 +185,96 @@ func (d *mapDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
cursor++ cursor++
} }
} }
func (d *mapDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
depth++
if depth > maxDecodeNestingDepth {
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
}
cursor = skipWhiteSpace(buf, cursor)
buflen := int64(len(buf))
if buflen < 2 {
return nil, 0, errors.ErrExpected("{} for map", cursor)
}
switch buf[cursor] {
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
case '{':
default:
return nil, 0, errors.ErrExpected("{ character for map value", cursor)
}
cursor++
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == '}' {
cursor++
return nil, cursor, nil
}
keyDecoder, ok := d.keyDecoder.(*stringDecoder)
if !ok {
return nil, 0, &errors.UnmarshalTypeError{
Value: "string",
Type: reflect.TypeOf(""),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}
ret := [][]byte{}
for {
key, keyCursor, err := keyDecoder.decodeByte(buf, cursor)
if err != nil {
return nil, 0, err
}
cursor = skipWhiteSpace(buf, keyCursor)
if buf[cursor] != ':' {
return nil, 0, errors.ErrExpected("colon after object key", cursor)
}
cursor++
child, found, err := ctx.Option.Path.Field(string(key))
if err != nil {
return nil, 0, err
}
if found {
if child != nil {
oldPath := ctx.Option.Path.node
ctx.Option.Path.node = child
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
if err != nil {
return nil, 0, err
}
ctx.Option.Path.node = oldPath
ret = append(ret, paths...)
cursor = c
} else {
start := cursor
end, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
ret = append(ret, buf[start:end])
cursor = end
}
} else {
c, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
cursor = c
}
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == '}' {
cursor++
return ret, cursor, nil
}
if buf[cursor] != ',' {
return nil, 0, errors.ErrExpected("comma after object value", cursor)
}
cursor++
}
}

View File

@ -51,6 +51,17 @@ func (d *numberDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
return cursor, nil return cursor, nil
} }
func (d *numberDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}
func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) { func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) {
start := s.cursor start := s.cursor
for { for {

View File

@ -7,9 +7,11 @@ type OptionFlags uint8
const ( const (
FirstWinOption OptionFlags = 1 << iota FirstWinOption OptionFlags = 1 << iota
ContextOption ContextOption
PathOption
) )
type Option struct { type Option struct {
Flags OptionFlags Flags OptionFlags
Context context.Context Context context.Context
Path *Path
} }

View File

@ -0,0 +1,670 @@
package decoder
import (
"fmt"
"reflect"
"strconv"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type PathString string
func (s PathString) Build() (*Path, error) {
builder := new(PathBuilder)
return builder.Build([]rune(s))
}
type PathBuilder struct {
root PathNode
node PathNode
singleQuotePathSelector bool
doubleQuotePathSelector bool
}
func (b *PathBuilder) Build(buf []rune) (*Path, error) {
node, err := b.build(buf)
if err != nil {
return nil, err
}
return &Path{
node: node,
RootSelectorOnly: node == nil,
SingleQuotePathSelector: b.singleQuotePathSelector,
DoubleQuotePathSelector: b.doubleQuotePathSelector,
}, nil
}
func (b *PathBuilder) build(buf []rune) (PathNode, error) {
if len(buf) == 0 {
return nil, errors.ErrEmptyPath()
}
if buf[0] != '$' {
return nil, errors.ErrInvalidPath("JSON Path must start with a $ character")
}
if len(buf) == 1 {
return nil, nil
}
buf = buf[1:]
offset, err := b.buildNext(buf)
if err != nil {
return nil, err
}
if len(buf) > offset {
return nil, errors.ErrInvalidPath("remain invalid path %q", buf[offset:])
}
return b.root, nil
}
func (b *PathBuilder) buildNextCharIfExists(buf []rune, cursor int) (int, error) {
if len(buf) > cursor {
offset, err := b.buildNext(buf[cursor:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
return cursor, nil
}
func (b *PathBuilder) buildNext(buf []rune) (int, error) {
switch buf[0] {
case '.':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
offset, err := b.buildSelector(buf[1:])
if err != nil {
return 0, err
}
return offset + 1, nil
case '[':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
offset, err := b.buildIndex(buf[1:])
if err != nil {
return 0, err
}
return offset + 1, nil
default:
return 0, errors.ErrInvalidPath("expect dot or left bracket character. but found %c character", buf[0])
}
}
func (b *PathBuilder) buildSelector(buf []rune) (int, error) {
switch buf[0] {
case '.':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with double dot character")
}
offset, err := b.buildPathRecursive(buf[1:])
if err != nil {
return 0, err
}
return 1 + offset, nil
case '[', ']', '$', '*':
return 0, errors.ErrInvalidPath("found invalid path character %c after dot", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '$', '*', ']':
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
case '.':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
offset, err := b.buildSelector(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '[':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
offset, err := b.buildIndex(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '"':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with double quote character")
}
offset, err := b.buildQuoteSelector(buf[cursor+1:], DoubleQuotePathSelector)
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
}
b.addSelectorNode(string(buf))
return len(buf), nil
}
func (b *PathBuilder) buildQuoteSelector(buf []rune, sel QuotePathSelector) (int, error) {
switch buf[0] {
case '[', ']', '$', '.', '*', '\'', '"':
return 0, errors.ErrInvalidPath("found invalid path character %c after quote", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '\'':
if sel != SingleQuotePathSelector {
return 0, errors.ErrInvalidPath("found double quote character in field selector with single quote context")
}
if len(buf) <= cursor+1 {
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character in field selector context")
}
if buf[cursor+1] != ']' {
return 0, errors.ErrInvalidPath("expect right bracket for field selector with single quote but found %c", buf[cursor+1])
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
b.singleQuotePathSelector = true
return b.buildNextCharIfExists(buf, cursor+2)
case '"':
if sel != DoubleQuotePathSelector {
return 0, errors.ErrInvalidPath("found single quote character in field selector with double quote context")
}
selector := buf[:cursor]
b.addSelectorNode(string(selector))
b.doubleQuotePathSelector = true
return b.buildNextCharIfExists(buf, cursor+1)
}
}
return 0, errors.ErrInvalidPath("couldn't find quote character in selector quote path context")
}
func (b *PathBuilder) buildPathRecursive(buf []rune) (int, error) {
switch buf[0] {
case '.', '[', ']', '$', '*':
return 0, errors.ErrInvalidPath("found invalid path character %c after double dot", buf[0])
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case '$', '*', ']':
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
case '.':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
}
selector := buf[:cursor]
b.addRecursiveNode(string(selector))
offset, err := b.buildSelector(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
case '[':
if cursor+1 >= len(buf) {
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
}
selector := buf[:cursor]
b.addRecursiveNode(string(selector))
offset, err := b.buildIndex(buf[cursor+1:])
if err != nil {
return 0, err
}
return cursor + 1 + offset, nil
}
}
b.addRecursiveNode(string(buf))
return len(buf), nil
}
func (b *PathBuilder) buildIndex(buf []rune) (int, error) {
switch buf[0] {
case '.', '[', ']', '$':
return 0, errors.ErrInvalidPath("found invalid path character %c after left bracket", buf[0])
case '\'':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character")
}
offset, err := b.buildQuoteSelector(buf[1:], SingleQuotePathSelector)
if err != nil {
return 0, err
}
return 1 + offset, nil
case '*':
if len(buf) == 1 {
return 0, errors.ErrInvalidPath("JSON Path ends with star character")
}
if buf[1] != ']' {
return 0, errors.ErrInvalidPath("expect right bracket character for index all path but found %c character", buf[1])
}
b.addIndexAllNode()
offset := len("*]")
if len(buf) > 2 {
buildOffset, err := b.buildNext(buf[2:])
if err != nil {
return 0, err
}
return offset + buildOffset, nil
}
return offset, nil
}
for cursor := 0; cursor < len(buf); cursor++ {
switch buf[cursor] {
case ']':
index, err := strconv.ParseInt(string(buf[:cursor]), 10, 64)
if err != nil {
return 0, errors.ErrInvalidPath("%q is unexpected index path", buf[:cursor])
}
b.addIndexNode(int(index))
return b.buildNextCharIfExists(buf, cursor+1)
}
}
return 0, errors.ErrInvalidPath("couldn't find right bracket character in index path context")
}
func (b *PathBuilder) addIndexAllNode() {
node := newPathIndexAllNode()
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addRecursiveNode(selector string) {
node := newPathRecursiveNode(selector)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addSelectorNode(name string) {
node := newPathSelectorNode(name)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
func (b *PathBuilder) addIndexNode(idx int) {
node := newPathIndexNode(idx)
if b.root == nil {
b.root = node
b.node = node
} else {
b.node = b.node.chain(node)
}
}
type QuotePathSelector int
const (
SingleQuotePathSelector QuotePathSelector = 1
DoubleQuotePathSelector QuotePathSelector = 2
)
type Path struct {
node PathNode
RootSelectorOnly bool
SingleQuotePathSelector bool
DoubleQuotePathSelector bool
}
func (p *Path) Field(sel string) (PathNode, bool, error) {
if p.node == nil {
return nil, false, nil
}
return p.node.Field(sel)
}
func (p *Path) Get(src, dst reflect.Value) error {
if p.node == nil {
return nil
}
return p.node.Get(src, dst)
}
func (p *Path) String() string {
if p.node == nil {
return "$"
}
return p.node.String()
}
type PathNode interface {
fmt.Stringer
Index(idx int) (PathNode, bool, error)
Field(fieldName string) (PathNode, bool, error)
Get(src, dst reflect.Value) error
chain(PathNode) PathNode
target() bool
single() bool
}
type BasePathNode struct {
child PathNode
}
func (n *BasePathNode) chain(node PathNode) PathNode {
n.child = node
return node
}
func (n *BasePathNode) target() bool {
return n.child == nil
}
func (n *BasePathNode) single() bool {
return true
}
type PathSelectorNode struct {
*BasePathNode
selector string
}
func newPathSelectorNode(selector string) *PathSelectorNode {
return &PathSelectorNode{
BasePathNode: &BasePathNode{},
selector: selector,
}
}
func (n *PathSelectorNode) Index(idx int) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathSelectorNode) Field(fieldName string) (PathNode, bool, error) {
if n.selector == fieldName {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathSelectorNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Map:
iter := src.MapRange()
for iter.Next() {
key, ok := iter.Key().Interface().(string)
if !ok {
return fmt.Errorf("invalid map key type %T", src.Type().Key())
}
child, found, err := n.Field(key)
if err != nil {
return err
}
if found {
if child != nil {
return child.Get(iter.Value(), dst)
}
return AssignValue(iter.Value(), dst)
}
}
case reflect.Struct:
typ := src.Type()
for i := 0; i < typ.Len(); i++ {
tag := runtime.StructTagFromField(typ.Field(i))
child, found, err := n.Field(tag.Key)
if err != nil {
return err
}
if found {
if child != nil {
return child.Get(src.Field(i), dst)
}
return AssignValue(src.Field(i), dst)
}
}
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
case reflect.Float64, reflect.String, reflect.Bool:
return AssignValue(src, dst)
}
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
}
func (n *PathSelectorNode) String() string {
s := fmt.Sprintf(".%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type PathIndexNode struct {
*BasePathNode
selector int
}
func newPathIndexNode(selector int) *PathIndexNode {
return &PathIndexNode{
BasePathNode: &BasePathNode{},
selector: selector,
}
}
func (n *PathIndexNode) Index(idx int) (PathNode, bool, error) {
if n.selector == idx {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathIndexNode) Field(fieldName string) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathIndexNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Array, reflect.Slice:
if src.Len() > n.selector {
if n.child != nil {
return n.child.Get(src.Index(n.selector), dst)
}
return AssignValue(src.Index(n.selector), dst)
}
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get [%d] value from %s", n.selector, src.Type())
}
func (n *PathIndexNode) String() string {
s := fmt.Sprintf("[%d]", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}
type PathIndexAllNode struct {
*BasePathNode
}
func newPathIndexAllNode() *PathIndexAllNode {
return &PathIndexAllNode{
BasePathNode: &BasePathNode{},
}
}
func (n *PathIndexAllNode) Index(idx int) (PathNode, bool, error) {
return n.child, true, nil
}
func (n *PathIndexAllNode) Field(fieldName string) (PathNode, bool, error) {
return nil, false, &errors.PathError{}
}
func (n *PathIndexAllNode) Get(src, dst reflect.Value) error {
switch src.Type().Kind() {
case reflect.Array, reflect.Slice:
var arr []interface{}
for i := 0; i < src.Len(); i++ {
var v interface{}
rv := reflect.ValueOf(&v)
if n.child != nil {
if err := n.child.Get(src.Index(i), rv); err != nil {
return err
}
} else {
if err := AssignValue(src.Index(i), rv); err != nil {
return err
}
}
arr = append(arr, v)
}
if err := AssignValue(reflect.ValueOf(arr), dst); err != nil {
return err
}
return nil
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get all value from %s", src.Type())
}
func (n *PathIndexAllNode) String() string {
s := "[*]"
if n.child != nil {
s += n.child.String()
}
return s
}
type PathRecursiveNode struct {
*BasePathNode
selector string
}
func newPathRecursiveNode(selector string) *PathRecursiveNode {
node := newPathSelectorNode(selector)
return &PathRecursiveNode{
BasePathNode: &BasePathNode{
child: node,
},
selector: selector,
}
}
func (n *PathRecursiveNode) Field(fieldName string) (PathNode, bool, error) {
if n.selector == fieldName {
return n.child, true, nil
}
return nil, false, nil
}
func (n *PathRecursiveNode) Index(_ int) (PathNode, bool, error) {
return n, true, nil
}
func valueToSliceValue(v interface{}) []interface{} {
rv := reflect.ValueOf(v)
ret := []interface{}{}
if rv.Type().Kind() == reflect.Slice || rv.Type().Kind() == reflect.Array {
for i := 0; i < rv.Len(); i++ {
ret = append(ret, rv.Index(i).Interface())
}
return ret
}
return []interface{}{v}
}
func (n *PathRecursiveNode) Get(src, dst reflect.Value) error {
if n.child == nil {
return fmt.Errorf("failed to get by recursive path ..%s", n.selector)
}
var arr []interface{}
switch src.Type().Kind() {
case reflect.Map:
iter := src.MapRange()
for iter.Next() {
key, ok := iter.Key().Interface().(string)
if !ok {
return fmt.Errorf("invalid map key type %T", src.Type().Key())
}
child, found, err := n.Field(key)
if err != nil {
return err
}
if found {
var v interface{}
rv := reflect.ValueOf(&v)
_ = child.Get(iter.Value(), rv)
arr = append(arr, valueToSliceValue(v)...)
} else {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(iter.Value(), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Struct:
typ := src.Type()
for i := 0; i < typ.Len(); i++ {
tag := runtime.StructTagFromField(typ.Field(i))
child, found, err := n.Field(tag.Key)
if err != nil {
return err
}
if found {
var v interface{}
rv := reflect.ValueOf(&v)
_ = child.Get(src.Field(i), rv)
arr = append(arr, valueToSliceValue(v)...)
} else {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(src.Field(i), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Array, reflect.Slice:
for i := 0; i < src.Len(); i++ {
var v interface{}
rv := reflect.ValueOf(&v)
_ = n.Get(src.Index(i), rv)
if v != nil {
arr = append(arr, valueToSliceValue(v)...)
}
}
_ = AssignValue(reflect.ValueOf(arr), dst)
return nil
case reflect.Ptr:
return n.Get(src.Elem(), dst)
case reflect.Interface:
return n.Get(reflect.ValueOf(src.Interface()), dst)
}
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
}
func (n *PathRecursiveNode) String() string {
s := fmt.Sprintf("..%s", n.selector)
if n.child != nil {
s += n.child.String()
}
return s
}

View File

@ -1,6 +1,7 @@
package decoder package decoder
import ( import (
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/runtime" "github.com/goccy/go-json/internal/runtime"
@ -34,6 +35,10 @@ func (d *ptrDecoder) contentDecoder() Decoder {
//go:linkname unsafe_New reflect.unsafe_New //go:linkname unsafe_New reflect.unsafe_New
func unsafe_New(*runtime.Type) unsafe.Pointer func unsafe_New(*runtime.Type) unsafe.Pointer
func UnsafeNew(t *runtime.Type) unsafe.Pointer {
return unsafe_New(t)
}
func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
if s.skipWhiteSpace() == nul { if s.skipWhiteSpace() == nul {
s.read() s.read()
@ -80,8 +85,13 @@ 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
return cursor, nil return cursor, nil
} }
func (d *ptrDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: ptr decoder does not support decode path")
}

View File

@ -299,3 +299,82 @@ func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
} }
} }
} }
func (d *sliceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
buf := ctx.Buf
depth++
if depth > maxDecodeNestingDepth {
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
}
ret := [][]byte{}
for {
switch buf[cursor] {
case ' ', '\n', '\t', '\r':
cursor++
continue
case 'n':
if err := validateNull(buf, cursor); err != nil {
return nil, 0, err
}
cursor += 4
return [][]byte{nullbytes}, cursor, nil
case '[':
cursor++
cursor = skipWhiteSpace(buf, cursor)
if buf[cursor] == ']' {
cursor++
return ret, cursor, nil
}
idx := 0
for {
child, found, err := ctx.Option.Path.node.Index(idx)
if err != nil {
return nil, 0, err
}
if found {
if child != nil {
oldPath := ctx.Option.Path.node
ctx.Option.Path.node = child
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
if err != nil {
return nil, 0, err
}
ctx.Option.Path.node = oldPath
ret = append(ret, paths...)
cursor = c
} else {
start := cursor
end, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
ret = append(ret, buf[start:end])
cursor = end
}
} else {
c, err := skipValue(buf, cursor, depth)
if err != nil {
return nil, 0, err
}
cursor = c
}
cursor = skipWhiteSpace(buf, cursor)
switch buf[cursor] {
case ']':
cursor++
return ret, cursor, nil
case ',':
idx++
default:
return nil, 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor)
}
cursor++
}
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return nil, 0, d.errNumber(cursor)
default:
return nil, 0, errors.ErrUnexpectedEndOfJSON("slice", cursor)
}
}
}

View File

@ -103,7 +103,7 @@ func (s *Stream) statForRetry() ([]byte, int64, unsafe.Pointer) {
func (s *Stream) Reset() { func (s *Stream) Reset() {
s.reset() s.reset()
s.bufSize = initBufSize s.bufSize = int64(len(s.buf))
} }
func (s *Stream) More() bool { func (s *Stream) More() bool {
@ -138,8 +138,11 @@ func (s *Stream) Token() (interface{}, error) {
s.cursor++ s.cursor++
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
bytes := floatBytes(s) bytes := floatBytes(s)
s := *(*string)(unsafe.Pointer(&bytes)) str := *(*string)(unsafe.Pointer(&bytes))
f64, err := strconv.ParseFloat(s, 64) if s.UseNumber {
return json.Number(str), nil
}
f64, err := strconv.ParseFloat(str, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -277,7 +280,7 @@ func (s *Stream) skipObject(depth int64) error {
if char(p, cursor) == nul { if char(p, cursor) == nul {
s.cursor = cursor s.cursor = cursor
if s.read() { if s.read() {
_, cursor, p = s.statForRetry() _, cursor, p = s.stat()
continue continue
} }
return errors.ErrUnexpectedEndOfJSON("string of object", cursor) return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -340,7 +343,7 @@ func (s *Stream) skipArray(depth int64) error {
if char(p, cursor) == nul { if char(p, cursor) == nul {
s.cursor = cursor s.cursor = cursor
if s.read() { if s.read() {
_, cursor, p = s.statForRetry() _, cursor, p = s.stat()
continue continue
} }
return errors.ErrUnexpectedEndOfJSON("string of object", cursor) return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -398,7 +401,7 @@ func (s *Stream) skipValue(depth int64) error {
if char(p, cursor) == nul { if char(p, cursor) == nul {
s.cursor = cursor s.cursor = cursor
if s.read() { if s.read() {
_, cursor, p = s.statForRetry() _, cursor, p = s.stat()
continue continue
} }
return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset()) return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset())
@ -423,7 +426,6 @@ func (s *Stream) skipValue(depth int64) error {
continue continue
} else if c == nul { } else if c == nul {
if s.read() { if s.read() {
s.cursor-- // for retry current character
_, cursor, p = s.stat() _, cursor, p = s.stat()
continue continue
} }

View File

@ -1,6 +1,8 @@
package decoder package decoder
import ( import (
"bytes"
"fmt"
"reflect" "reflect"
"unicode" "unicode"
"unicode/utf16" "unicode/utf16"
@ -58,6 +60,17 @@ func (d *stringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
return cursor, nil return cursor, nil
} }
func (d *stringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
if err != nil {
return nil, 0, err
}
if bytes == nil {
return [][]byte{nullbytes}, c, nil
}
return [][]byte{bytes}, c, nil
}
var ( var (
hexToInt = [256]int{ hexToInt = [256]int{
'0': 0, '0': 0,
@ -93,24 +106,30 @@ func unicodeToRune(code []byte) rune {
return r return r
} }
func readAtLeast(s *Stream, n int64, p *unsafe.Pointer) bool {
for s.cursor+n >= s.length {
if !s.read() {
return false
}
*p = s.bufptr()
}
return true
}
func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) { func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) {
const defaultOffset = 5 const defaultOffset = 5
const surrogateOffset = 11 const surrogateOffset = 11
if s.cursor+defaultOffset >= s.length { if !readAtLeast(s, defaultOffset, &p) {
if !s.read() {
return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
} }
p = s.bufptr()
}
r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset]) r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset])
if utf16.IsSurrogate(r) { if utf16.IsSurrogate(r) {
if s.cursor+surrogateOffset >= s.length { if !readAtLeast(s, surrogateOffset, &p) {
s.read() return unicode.ReplacementChar, defaultOffset, p, nil
p = s.bufptr()
} }
if s.cursor+surrogateOffset >= s.length || s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' { if s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' {
return unicode.ReplacementChar, defaultOffset, p, nil return unicode.ReplacementChar, defaultOffset, p, nil
} }
r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset]) r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset])
@ -163,6 +182,7 @@ RETRY:
if !s.read() { if !s.read() {
return nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) return nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
} }
p = s.bufptr()
goto RETRY goto RETRY
default: default:
return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset()) return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset())
@ -308,49 +328,36 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
cursor++ cursor++
start := cursor start := cursor
b := (*sliceHeader)(unsafe.Pointer(&buf)).data b := (*sliceHeader)(unsafe.Pointer(&buf)).data
escaped := 0
for { for {
switch char(b, cursor) { switch char(b, cursor) {
case '\\': case '\\':
escaped++
cursor++ cursor++
switch char(b, cursor) { switch char(b, cursor) {
case '"': case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
buf[cursor] = '"' cursor++
buf = append(buf[:cursor-1], buf[cursor:]...)
case '\\':
buf[cursor] = '\\'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '/':
buf[cursor] = '/'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'b':
buf[cursor] = '\b'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'f':
buf[cursor] = '\f'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'n':
buf[cursor] = '\n'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'r':
buf[cursor] = '\r'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 't':
buf[cursor] = '\t'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'u': case 'u':
buflen := int64(len(buf)) buflen := int64(len(buf))
if cursor+5 >= buflen { if cursor+5 >= buflen {
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor) return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
} }
code := unicodeToRune(buf[cursor+1 : cursor+5]) for i := int64(1); i <= 4; i++ {
unicode := []byte(string(code)) c := char(b, cursor+i)
buf = append(append(buf[:cursor-1], unicode...), buf[cursor+5:]...) if !(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) {
return nil, 0, errors.ErrSyntax(fmt.Sprintf("json: invalid character %c in \\u hexadecimal character escape", c), cursor+i)
}
}
cursor += 5
default: default:
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor) return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
} }
continue continue
case '"': case '"':
literal := buf[start:cursor] literal := buf[start:cursor]
if escaped > 0 {
literal = literal[:unescapeString(literal)]
}
cursor++ cursor++
return literal, cursor, nil return literal, cursor, nil
case nul: case nul:
@ -369,3 +376,77 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
} }
} }
} }
var unescapeMap = [256]byte{
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t',
}
func unsafeAdd(ptr unsafe.Pointer, offset int) unsafe.Pointer {
return unsafe.Pointer(uintptr(ptr) + uintptr(offset))
}
func unescapeString(buf []byte) int {
p := (*sliceHeader)(unsafe.Pointer(&buf)).data
end := unsafeAdd(p, len(buf))
src := unsafeAdd(p, bytes.IndexByte(buf, '\\'))
dst := src
for src != end {
c := char(src, 0)
if c == '\\' {
escapeChar := char(src, 1)
if escapeChar != 'u' {
*(*byte)(dst) = unescapeMap[escapeChar]
src = unsafeAdd(src, 2)
dst = unsafeAdd(dst, 1)
} else {
v1 := hexToInt[char(src, 2)]
v2 := hexToInt[char(src, 3)]
v3 := hexToInt[char(src, 4)]
v4 := hexToInt[char(src, 5)]
code := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if code >= 0xd800 && code < 0xdc00 && uintptr(unsafeAdd(src, 11)) < uintptr(end) {
if char(src, 6) == '\\' && char(src, 7) == 'u' {
v1 := hexToInt[char(src, 8)]
v2 := hexToInt[char(src, 9)]
v3 := hexToInt[char(src, 10)]
v4 := hexToInt[char(src, 11)]
lo := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if lo >= 0xdc00 && lo < 0xe000 {
code = (code-0xd800)<<10 | (lo - 0xdc00) + 0x10000
src = unsafeAdd(src, 6)
}
}
}
var b [utf8.UTFMax]byte
n := utf8.EncodeRune(b[:], code)
switch n {
case 4:
*(*byte)(unsafeAdd(dst, 3)) = b[3]
fallthrough
case 3:
*(*byte)(unsafeAdd(dst, 2)) = b[2]
fallthrough
case 2:
*(*byte)(unsafeAdd(dst, 1)) = b[1]
fallthrough
case 1:
*(*byte)(unsafeAdd(dst, 0)) = b[0]
}
src = unsafeAdd(src, 6)
dst = unsafeAdd(dst, n)
}
} else {
*(*byte)(dst) = c
src = unsafeAdd(src, 1)
dst = unsafeAdd(dst, 1)
}
}
return int(uintptr(dst) - uintptr(p))
}

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 {
@ -817,3 +839,7 @@ func (d *structDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
cursor++ cursor++
} }
} }
func (d *structDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: struct decoder does not support decode path")
}

View File

@ -10,6 +10,7 @@ import (
type Decoder interface { type Decoder interface {
Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error) Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error)
DecodePath(*RuntimeContext, int64, int64) ([][]byte, int64, error)
DecodeStream(*Stream, int64, unsafe.Pointer) error DecodeStream(*Stream, int64, unsafe.Pointer) error
} }

View File

@ -188,3 +188,7 @@ func (d *uintDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
d.op(p, u64) d.op(p, u64)
return cursor, nil return cursor, nil
} }
func (d *uintDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: uint decoder does not support decode path")
}

View File

@ -1,7 +1,9 @@
package decoder package decoder
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
@ -46,13 +48,20 @@ func (d *unmarshalJSONDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Poi
typ: d.typ, typ: d.typ,
ptr: p, ptr: p,
})) }))
switch v := v.(type) {
case unmarshalerContext:
var ctx context.Context
if (s.Option.Flags & ContextOption) != 0 { if (s.Option.Flags & ContextOption) != 0 {
if err := v.(unmarshalerContext).UnmarshalJSON(s.Option.Context, dst); err != nil { ctx = s.Option.Context
} else {
ctx = context.Background()
}
if err := v.UnmarshalJSON(ctx, dst); err != nil {
d.annotateError(s.cursor, err) d.annotateError(s.cursor, err)
return err return err
} }
} else { case json.Unmarshaler:
if err := v.(json.Unmarshaler).UnmarshalJSON(dst); err != nil { if err := v.UnmarshalJSON(dst); err != nil {
d.annotateError(s.cursor, err) d.annotateError(s.cursor, err)
return err return err
} }
@ -89,3 +98,7 @@ func (d *unmarshalJSONDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
} }
return end, nil return end, nil
} }
func (d *unmarshalJSONDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: unmarshal json decoder does not support decode path")
}

View File

@ -3,6 +3,7 @@ package decoder
import ( import (
"bytes" "bytes"
"encoding" "encoding"
"fmt"
"unicode" "unicode"
"unicode/utf16" "unicode/utf16"
"unicode/utf8" "unicode/utf8"
@ -142,7 +143,11 @@ func (d *unmarshalTextDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
return end, nil return end, nil
} }
func unquoteBytes(s []byte) (t []byte, ok bool) { func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
}
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

@ -1,6 +1,7 @@
package decoder package decoder
import ( import (
"fmt"
"reflect" "reflect"
"unsafe" "unsafe"
@ -66,3 +67,7 @@ func (d *wrappedStringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
ctx.Buf = oldBuf ctx.Buf = oldBuf
return c, nil return c, nil
} }
func (d *wrappedStringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
return nil, 0, fmt.Errorf("json: wrapped string decoder does not support decode path")
}

View File

@ -2,6 +2,7 @@ package encoder
import ( import (
"fmt" "fmt"
"reflect"
"unsafe" "unsafe"
"github.com/goccy/go-json/internal/runtime" "github.com/goccy/go-json/internal/runtime"
@ -383,7 +384,7 @@ func (c *StructCode) Kind() CodeKind {
} }
func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode { func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode {
if field.isAnonymous { if isEmbeddedStruct(field) {
return c.lastAnonymousFieldCode(firstField) return c.lastAnonymousFieldCode(firstField)
} }
lastField := firstField lastField := firstField
@ -396,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
} }
@ -436,11 +440,6 @@ func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
} }
if isEndField { if isEndField {
endField := fieldCodes.Last() endField := fieldCodes.Last()
if field.isAnonymous {
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 {
@ -697,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
@ -1003,3 +1010,14 @@ func convertPtrOp(code *Opcode) OpType {
} }
return code.Op return code.Op
} }
func isEmbeddedStruct(field *StructFieldCode) bool {
if !field.isAnonymous {
return false
}
t := field.typ
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Kind() == reflect.Struct
}

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

@ -31,7 +31,7 @@ func init() {
if typeAddr == nil { if typeAddr == nil {
typeAddr = &runtime.TypeAddr{} typeAddr = &runtime.TypeAddr{}
} }
cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift) cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange>>typeAddr.AddrShift+1)
} }
func loadOpcodeMap() map[uintptr]*OpcodeSet { func loadOpcodeMap() map[uintptr]*OpcodeSet {
@ -480,14 +480,17 @@ 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)
case typ.Kind() == reflect.Map: case typ.Kind() == reflect.Map:
return c.ptrCode(runtime.PtrTo(typ)) return c.ptrCode(runtime.PtrTo(typ))
default: default:
code, err := c.typeToCodeWithPtr(typ, false) // isPtr was originally used to indicate whether the type of top level is pointer.
// However, since the slice/array element is a specification that can get the pointer address, explicitly set isPtr to true.
// See here for related issues: https://github.com/goccy/go-json/issues/370
code, err := c.typeToCodeWithPtr(typ, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -503,8 +506,6 @@ func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
func (c *Compiler) mapKeyCode(typ *runtime.Type) (Code, error) { func (c *Compiler) mapKeyCode(typ *runtime.Type) (Code, error) {
switch { switch {
case c.implementsMarshalJSON(typ):
return c.marshalJSONCode(typ)
case c.implementsMarshalText(typ): case c.implementsMarshalText(typ):
return c.marshalTextCode(typ) return c.marshalTextCode(typ)
} }
@ -616,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)
@ -625,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,
@ -853,6 +861,9 @@ func (c *Compiler) implementsMarshalText(typ *runtime.Type) bool {
} }
func (c *Compiler) isNilableType(typ *runtime.Type) bool { func (c *Compiler) isNilableType(typ *runtime.Type) bool {
if !runtime.IfaceIndir(typ) {
return true
}
switch typ.Kind() { switch typ.Kind() {
case reflect.Ptr: case reflect.Ptr:
return true return true
@ -885,29 +896,40 @@ func (c *Compiler) codeToOpcode(ctx *compileContext, typ *runtime.Type, code Cod
} }
func (c *Compiler) linkRecursiveCode(ctx *compileContext) { func (c *Compiler) linkRecursiveCode(ctx *compileContext) {
recursiveCodes := map[uintptr]*CompiledCode{}
for _, recursive := range *ctx.recursiveCodes { for _, recursive := range *ctx.recursiveCodes {
typeptr := uintptr(unsafe.Pointer(recursive.Type)) typeptr := uintptr(unsafe.Pointer(recursive.Type))
codes := ctx.structTypeToCodes[typeptr] codes := ctx.structTypeToCodes[typeptr]
compiled := recursive.Jmp if recursiveCode, ok := recursiveCodes[typeptr]; ok {
compiled.Code = copyOpcode(codes.First()) *recursive.Jmp = *recursiveCode
code := compiled.Code continue
code.End.Next = newEndOp(&compileContext{}, recursive.Type) }
code.Op = code.Op.PtrHeadToHead()
beforeLastCode := code.End code := copyOpcode(codes.First())
lastCode := beforeLastCode.Next code.Op = code.Op.PtrHeadToHead()
lastCode := newEndOp(&compileContext{}, recursive.Type)
lastCode.Op = OpRecursiveEnd
// OpRecursiveEnd must set before call TotalLength
code.End.Next = lastCode
totalLength := code.TotalLength() totalLength := code.TotalLength()
// Idx, ElemIdx, Length must set after call TotalLength
lastCode.Idx = uint32((totalLength + 1) * uintptrSize) lastCode.Idx = uint32((totalLength + 1) * uintptrSize)
lastCode.ElemIdx = lastCode.Idx + uintptrSize lastCode.ElemIdx = lastCode.Idx + uintptrSize
lastCode.Length = lastCode.Idx + 2*uintptrSize lastCode.Length = lastCode.Idx + 2*uintptrSize
code.End.Next.Op = OpRecursiveEnd
// extend length to alloc slot for elemIdx + length // extend length to alloc slot for elemIdx + length
curTotalLength := uintptr(recursive.TotalLength()) + 3 curTotalLength := uintptr(recursive.TotalLength()) + 3
nextTotalLength := uintptr(totalLength) + 3 nextTotalLength := uintptr(totalLength) + 3
compiled := recursive.Jmp
compiled.Code = code
compiled.CurLen = curTotalLength compiled.CurLen = curTotalLength
compiled.NextLen = nextTotalLength compiled.NextLen = nextTotalLength
compiled.Linked = true compiled.Linked = true
recursiveCodes[typeptr] = compiled
} }
} }

View File

@ -4,8 +4,12 @@
package encoder package encoder
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) { func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr { if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
return compileToGetCodeSetSlowPath(typeptr) codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
} }
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
if codeSet := cachedOpcodeSets[index]; codeSet != nil { if codeSet := cachedOpcodeSets[index]; codeSet != nil {

View File

@ -10,8 +10,12 @@ import (
var setsMu sync.RWMutex var setsMu sync.RWMutex
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) { func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr { if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
return compileToGetCodeSetSlowPath(typeptr) codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
} }
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
setsMu.RLock() setsMu.RLock()

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"
@ -363,7 +365,7 @@ func copyOpcode(code *Opcode) *Opcode {
func setTotalLengthToInterfaceOp(code *Opcode) { func setTotalLengthToInterfaceOp(code *Opcode) {
for c := code; !c.IsEnd(); { for c := code; !c.IsEnd(); {
if c.Op == OpInterface { if c.Op == OpInterface || c.Op == OpInterfacePtr {
c.Length = uint32(code.TotalLength()) c.Length = uint32(code.TotalLength())
} }
c = c.IterNext() c = c.IterNext()
@ -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

@ -1,6 +1,9 @@
package encoder package encoder
import "context" import (
"context"
"io"
)
type OptionFlag uint8 type OptionFlag uint8
@ -19,6 +22,8 @@ type Option struct {
Flag OptionFlag Flag OptionFlag
ColorScheme *ColorScheme ColorScheme *ColorScheme
Context context.Context Context context.Context
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,18 +15,24 @@ 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 {
fmt.Println("=============[DEBUG]===============") w := ctx.Option.DebugOut
fmt.Println("* [TYPE]") fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Println(codeSet.Type) fmt.Fprintln(w, "* [TYPE]")
fmt.Printf("\n") fmt.Fprintln(w, codeSet.Type)
fmt.Println("* [ALL OPCODE]") fmt.Fprintf(w, "\n")
fmt.Println(code.Dump()) fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Printf("\n") fmt.Fprintln(w, code.Dump())
fmt.Println("* [CONTEXT]") fmt.Fprintf(w, "\n")
fmt.Printf("%+v\n", ctx) fmt.Fprintln(w, "* [CONTEXT]")
fmt.Println("===================================") fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err) panic(err)
} }
}() }()

View File

@ -3,6 +3,7 @@ package vm
import ( import (
"math" "math"
"reflect"
"sort" "sort"
"unsafe" "unsafe"
@ -194,10 +195,13 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ typ = iface.typ
} }
if ifacePtr == nil { if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b) b = appendNullComma(ctx, b)
code = code.Next code = code.Next
break break
} }
}
ctx.KeepRefs = append(ctx.KeepRefs, up) ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
if err != nil { if err != nil {

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============") w := ctx.Option.DebugOut
fmt.Println("* [TYPE]") fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Println(codeSet.Type) fmt.Fprintln(w, "* [TYPE]")
fmt.Printf("\n") fmt.Fprintln(w, codeSet.Type)
fmt.Println("* [ALL OPCODE]") fmt.Fprintf(w, "\n")
fmt.Println(code.Dump()) fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Printf("\n") fmt.Fprintln(w, code.Dump())
fmt.Println("* [CONTEXT]") fmt.Fprintf(w, "\n")
fmt.Printf("%+v\n", ctx) fmt.Fprintln(w, "* [CONTEXT]")
fmt.Println("===================================") fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err) panic(err)
} }
}() }()

View File

@ -3,6 +3,7 @@ package vm_color
import ( import (
"math" "math"
"reflect"
"sort" "sort"
"unsafe" "unsafe"
@ -194,10 +195,13 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ typ = iface.typ
} }
if ifacePtr == nil { if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b) b = appendNullComma(ctx, b)
code = code.Next code = code.Next
break break
} }
}
ctx.KeepRefs = append(ctx.KeepRefs, up) ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
if err != nil { if err != nil {

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============") w := ctx.Option.DebugOut
fmt.Println("* [TYPE]") fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Println(codeSet.Type) fmt.Fprintln(w, "* [TYPE]")
fmt.Printf("\n") fmt.Fprintln(w, codeSet.Type)
fmt.Println("* [ALL OPCODE]") fmt.Fprintf(w, "\n")
fmt.Println(code.Dump()) fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Printf("\n") fmt.Fprintln(w, code.Dump())
fmt.Println("* [CONTEXT]") fmt.Fprintf(w, "\n")
fmt.Printf("%+v\n", ctx) fmt.Fprintln(w, "* [CONTEXT]")
fmt.Println("===================================") fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err) panic(err)
} }
}() }()

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

@ -3,6 +3,7 @@ package vm_color_indent
import ( import (
"math" "math"
"reflect"
"sort" "sort"
"unsafe" "unsafe"
@ -194,10 +195,13 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ typ = iface.typ
} }
if ifacePtr == nil { if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b) b = appendNullComma(ctx, b)
code = code.Next code = code.Next
break break
} }
}
ctx.KeepRefs = append(ctx.KeepRefs, up) ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
if err != nil { if err != nil {

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============") w := ctx.Option.DebugOut
fmt.Println("* [TYPE]") fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Println(codeSet.Type) fmt.Fprintln(w, "* [TYPE]")
fmt.Printf("\n") fmt.Fprintln(w, codeSet.Type)
fmt.Println("* [ALL OPCODE]") fmt.Fprintf(w, "\n")
fmt.Println(code.Dump()) fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Printf("\n") fmt.Fprintln(w, code.Dump())
fmt.Println("* [CONTEXT]") fmt.Fprintf(w, "\n")
fmt.Printf("%+v\n", ctx) fmt.Fprintln(w, "* [CONTEXT]")
fmt.Println("===================================") fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err) panic(err)
} }
}() }()

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

@ -3,6 +3,7 @@ package vm_indent
import ( import (
"math" "math"
"reflect"
"sort" "sort"
"unsafe" "unsafe"
@ -194,10 +195,13 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b
typ = iface.typ typ = iface.typ
} }
if ifacePtr == nil { if ifacePtr == nil {
isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ)
if !isDirectedNil {
b = appendNullComma(ctx, b) b = appendNullComma(ctx, b)
code = code.Next code = code.Next
break break
} }
}
ctx.KeepRefs = append(ctx.KeepRefs, up) ctx.KeepRefs = append(ctx.KeepRefs, up)
ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))
if err != nil { if err != nil {

View File

@ -162,3 +162,22 @@ func ErrInvalidBeginningOfValue(c byte, cursor int64) *SyntaxError {
Offset: cursor, Offset: cursor,
} }
} }
type PathError struct {
msg string
}
func (e *PathError) Error() string {
return fmt.Sprintf("json: invalid path format: %s", e.msg)
}
func ErrInvalidPath(msg string, args ...interface{}) *PathError {
if len(args) != 0 {
return &PathError{msg: fmt.Sprintf(msg, args...)}
}
return &PathError{msg: msg}
}
func ErrEmptyPath() *PathError {
return &PathError{msg: "path is empty"}
}

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

@ -13,7 +13,11 @@ func getTag(field reflect.StructField) string {
func IsIgnoredStructField(field reflect.StructField) bool { func IsIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" { if field.PkgPath != "" {
if field.Anonymous { if field.Anonymous {
if !(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct) && field.Type.Kind() != reflect.Struct { t := field.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return true return true
} }
} else { } else {

View File

@ -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

@ -1,6 +1,8 @@
package json package json
import ( import (
"io"
"github.com/goccy/go-json/internal/decoder" "github.com/goccy/go-json/internal/decoder"
"github.com/goccy/go-json/internal/encoder" "github.com/goccy/go-json/internal/encoder"
) )
@ -39,6 +41,20 @@ func Debug() EncodeOptionFunc {
} }
} }
// DebugWith sets the destination to write debug messages.
func DebugWith(w io.Writer) EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.DebugOut = w
}
}
// 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) {

84
vendor/github.com/goccy/go-json/path.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
package json
import (
"reflect"
"github.com/goccy/go-json/internal/decoder"
)
// CreatePath creates JSON Path.
//
// JSON Path rule
// $ : root object or element. The JSON Path format must start with this operator, which refers to the outermost level of the JSON-formatted string.
// . : child operator. You can identify child values using dot-notation.
// .. : recursive descent.
// [] : subscript operator. If the JSON object is an array, you can use brackets to specify the array index.
// [*] : all objects/elements for array.
//
// Reserved words must be properly escaped when included in Path.
//
// Escape Rule
// single quote style escape: e.g.) `$['a.b'].c`
// double quote style escape: e.g.) `$."a.b".c`
func CreatePath(p string) (*Path, error) {
path, err := decoder.PathString(p).Build()
if err != nil {
return nil, err
}
return &Path{path: path}, nil
}
// Path represents JSON Path.
type Path struct {
path *decoder.Path
}
// RootSelectorOnly whether only the root selector ($) is used.
func (p *Path) RootSelectorOnly() bool {
return p.path.RootSelectorOnly
}
// UsedSingleQuotePathSelector whether single quote-based escaping was done when building the JSON Path.
func (p *Path) UsedSingleQuotePathSelector() bool {
return p.path.SingleQuotePathSelector
}
// UsedSingleQuotePathSelector whether double quote-based escaping was done when building the JSON Path.
func (p *Path) UsedDoubleQuotePathSelector() bool {
return p.path.DoubleQuotePathSelector
}
// Extract extracts a specific JSON string.
func (p *Path) Extract(data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
return extractFromPath(p, data, optFuncs...)
}
// PathString returns original JSON Path string.
func (p *Path) PathString() string {
return p.path.String()
}
// Unmarshal extract and decode the value of the part corresponding to JSON Path from the input data.
func (p *Path) Unmarshal(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
contents, err := extractFromPath(p, data, optFuncs...)
if err != nil {
return err
}
results := make([]interface{}, 0, len(contents))
for _, content := range contents {
var result interface{}
if err := Unmarshal(content, &result); err != nil {
return err
}
results = append(results, result)
}
if err := decoder.AssignValue(reflect.ValueOf(results), reflect.ValueOf(v)); err != nil {
return err
}
return nil
}
// Get extract and substitute the value of the part corresponding to JSON Path from the input value.
func (p *Path) Get(src, dst interface{}) error {
return p.path.Get(reflect.ValueOf(src), reflect.ValueOf(dst))
}

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,276 @@
# 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
**Security**
* We are deprecating JWT middleware in this repository. Please use https://github.com/labstack/echo-jwt instead.
JWT middleware is moved to separate repository to allow us to bump/upgrade version of JWT implementation (`github.com/golang-jwt/jwt`) we are using
which we can not do in Echo core because this would break backwards compatibility guarantees we try to maintain.
* This minor version bumps minimum Go version to 1.17 (from 1.16) due `golang.org/x/` packages we depend on. There are
several vulnerabilities fixed in these libraries.
Echo still tries to support last 4 Go versions but there are occasions we can not guarantee this promise.
**Enhancements**
* Bump x/text to 0.3.8 [#2305](https://github.com/labstack/echo/pull/2305)
* Bump dependencies and add notes about Go releases we support [#2336](https://github.com/labstack/echo/pull/2336)
* Add helper interface for ProxyBalancer interface [#2316](https://github.com/labstack/echo/pull/2316)
* Expose `middleware.CreateExtractors` function so we can use it from echo-contrib repository [#2338](https://github.com/labstack/echo/pull/2338)
* Refactor func(Context) error to HandlerFunc [#2315](https://github.com/labstack/echo/pull/2315)
* Improve function comments [#2329](https://github.com/labstack/echo/pull/2329)
* Add new method HTTPError.WithInternal [#2340](https://github.com/labstack/echo/pull/2340)
* Replace io/ioutil package usages [#2342](https://github.com/labstack/echo/pull/2342)
* Add staticcheck to CI flow [#2343](https://github.com/labstack/echo/pull/2343)
* Replace relative path determination from proprietary to std [#2345](https://github.com/labstack/echo/pull/2345)
* Remove square brackets from ipv6 addresses in XFF (X-Forwarded-For header) [#2182](https://github.com/labstack/echo/pull/2182)
* Add testcases for some BodyLimit middleware configuration options [#2350](https://github.com/labstack/echo/pull/2350)
* Additional configuration options for RequestLogger and Logger middleware [#2341](https://github.com/labstack/echo/pull/2341)
* Add route to request log [#2162](https://github.com/labstack/echo/pull/2162)
* GitHub Workflows security hardening [#2358](https://github.com/labstack/echo/pull/2358)
* Add govulncheck to CI and bump dependencies [#2362](https://github.com/labstack/echo/pull/2362)
* Fix rate limiter docs [#2366](https://github.com/labstack/echo/pull/2366)
* Refactor how `e.Routes()` work and introduce `e.OnAddRouteHandler` callback [#2337](https://github.com/labstack/echo/pull/2337)
## v4.9.1 - 2022-10-12
**Fixes**
* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295)
**Enhancements**
* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272)
* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291)
* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254)
* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275)
* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301)
## v4.9.0 - 2022-09-04
**Security**
* Fix open redirect vulnerability in handlers serving static directories (e.Static, e.StaticFs, echo.StaticDirectoryHandler) [#2260](https://github.com/labstack/echo/pull/2260)
**Enhancements**
* Allow configuring ErrorHandler in CSRF middleware [#2257](https://github.com/labstack/echo/pull/2257)
* Replace HTTP method constants in tests with stdlib constants [#2247](https://github.com/labstack/echo/pull/2247)
## v4.8.0 - 2022-08-10
**Most notable things**
You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237)
```go
e.Add("COPY", "/*", func(c echo.Context) error
return c.String(http.StatusOK, "OK COPY")
})
```
You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217)
```go
e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
g := e.Group("/images")
g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
```
**Enhancements**
* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127)
* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145)
* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187)
* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191)
* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176)
* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209)
* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217)
* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227)
* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237)
## v4.7.2 - 2022-03-16
**Fixes**
* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
**Enhancements**
* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
## v4.7.1 - 2022-03-13
**Fixes**
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
**Enhancements**
* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
## v4.7.0 - 2022-03-01
**Enhancements**
* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
**Fixes**
* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
**General**
* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
## v4.6.3 - 2022-01-10 ## v4.6.3 - 2022-01-10
**Fixes** **Fixes**

View File

@ -9,9 +9,11 @@ tag:
check: lint vet race ## Check project check: lint vet race ## Check project
init: init:
@go get -u golang.org/x/lint/golint @go install golang.org/x/lint/golint@latest
@go install honnef.co/go/tools/cmd/staticcheck@latest
lint: ## Lint the files lint: ## Lint the files
@staticcheck ${PKG_LIST}
@golint -set_exit_status ${PKG_LIST} @golint -set_exit_status ${PKG_LIST}
vet: ## Vet the files vet: ## Vet the files
@ -29,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.15" goversion ?= "1.19"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15 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,28 +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)
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://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
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules). High performance, extensible, minimalist Go web framework.
Therefore a Go version capable of understanding /vN suffixed imports is required:
- 1.9.7+ * [Official website](https://echo.labstack.com)
- 1.10.3+ * [Quick start](https://echo.labstack.com/docs/quick-start)
- 1.14+ * [Middlewares](https://echo.labstack.com/docs/category/middleware)
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
way of using Echo going forward.
For older versions, please use the latest v3 tag.
## Feature Overview ### 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
@ -40,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>
@ -59,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
@ -92,10 +101,32 @@ func hello(c echo.Context) error {
} }
``` ```
## Help # Official middleware repositories
- [Forum](https://github.com/labstack/echo/discussions) Following list of middleware is maintained by Echo team.
- [Chat](https://gitter.im/labstack/echo)
| Repository | Description |
|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt) | [JWT](https://github.com/golang-jwt/jwt) middleware |
| [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 |
# Third-party middleware repositories
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.
## Contribute ## Contribute

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 {
@ -111,11 +119,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil { if err := b.BindPathParams(c, i); err != nil {
return err return err
} }
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH) // Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that. // For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding. // The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body method := c.Request().Method
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete { 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 {
if isElemString {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) 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()))
} }
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok { field = field.Elem()
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,6 +1,11 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
"encoding"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -52,8 +57,11 @@ import (
* time * time
* duration * duration
* BindUnmarshaler() interface * BindUnmarshaler() interface
* TextUnmarshaler() interface
* JSONUnmarshaler() interface
* UnixTime() - converts unix time (integer) to time.Time * UnixTime() - converts unix time (integer) to time.Time
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time * UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error` * CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
*/ */
@ -204,7 +212,7 @@ func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []st
return b.customFunc(sourceParam, customFunc, false) return b.customFunc(sourceParam, customFunc, false)
} }
// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist. // MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist.
func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder { func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
return b.customFunc(sourceParam, customFunc, true) return b.customFunc(sourceParam, customFunc, true)
} }
@ -241,7 +249,7 @@ func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
return b return b
} }
// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist // MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist
func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder { func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
if b.failFast && b.errors != nil { if b.failFast && b.errors != nil {
return b return b
@ -270,7 +278,7 @@ func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
return b return b
} }
// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist // MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist
func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder { func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
if b.failFast && b.errors != nil { if b.failFast && b.errors != nil {
return b return b
@ -302,7 +310,7 @@ func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler)
return b return b
} }
// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface. // MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface.
// Returns error when value does not exist // Returns error when value does not exist
func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder { func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil { if b.failFast && b.errors != nil {
@ -321,13 +329,85 @@ func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshal
return b return b
} }
// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// BindWithDelimiter binds parameter to destination by suitable conversion function. // BindWithDelimiter binds parameter to destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values // Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, false) return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
} }
// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function. // MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values // Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, true) return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
@ -376,7 +456,7 @@ func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, false) return b.intValue(sourceParam, dest, 64, false)
} }
// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist // MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder { func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, true) return b.intValue(sourceParam, dest, 64, true)
} }
@ -386,7 +466,7 @@ func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, false) return b.intValue(sourceParam, dest, 32, false)
} }
// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist // MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder { func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, true) return b.intValue(sourceParam, dest, 32, true)
} }
@ -396,7 +476,7 @@ func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, false) return b.intValue(sourceParam, dest, 16, false)
} }
// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist // MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder { func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, true) return b.intValue(sourceParam, dest, 16, true)
} }
@ -406,7 +486,7 @@ func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, false) return b.intValue(sourceParam, dest, 8, false)
} }
// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist // MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder { func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, true) return b.intValue(sourceParam, dest, 8, true)
} }
@ -416,7 +496,7 @@ func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, false) return b.intValue(sourceParam, dest, 0, false)
} }
// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist // MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist
func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder { func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, true) return b.intValue(sourceParam, dest, 0, true)
} }
@ -544,7 +624,7 @@ func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, false) return b.intsValue(sourceParam, dest, false)
} }
// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist // MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder { func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, true) return b.intsValue(sourceParam, dest, true)
} }
@ -554,7 +634,7 @@ func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, false) return b.intsValue(sourceParam, dest, false)
} }
// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist // MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder { func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, true) return b.intsValue(sourceParam, dest, true)
} }
@ -564,7 +644,7 @@ func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, false) return b.intsValue(sourceParam, dest, false)
} }
// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist // MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder { func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, true) return b.intsValue(sourceParam, dest, true)
} }
@ -574,7 +654,7 @@ func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, false) return b.intsValue(sourceParam, dest, false)
} }
// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist // MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder { func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, true) return b.intsValue(sourceParam, dest, true)
} }
@ -584,7 +664,7 @@ func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, false) return b.intsValue(sourceParam, dest, false)
} }
// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist // MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder { func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, true) return b.intsValue(sourceParam, dest, true)
} }
@ -594,7 +674,7 @@ func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, false) return b.uintValue(sourceParam, dest, 64, false)
} }
// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist // MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder { func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, true) return b.uintValue(sourceParam, dest, 64, true)
} }
@ -604,7 +684,7 @@ func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, false) return b.uintValue(sourceParam, dest, 32, false)
} }
// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist // MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder { func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, true) return b.uintValue(sourceParam, dest, 32, true)
} }
@ -614,7 +694,7 @@ func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, false) return b.uintValue(sourceParam, dest, 16, false)
} }
// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist // MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder { func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, true) return b.uintValue(sourceParam, dest, 16, true)
} }
@ -624,7 +704,7 @@ func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false) return b.uintValue(sourceParam, dest, 8, false)
} }
// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist // MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder { func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true) return b.uintValue(sourceParam, dest, 8, true)
} }
@ -634,7 +714,7 @@ func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false) return b.uintValue(sourceParam, dest, 8, false)
} }
// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist // MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist
func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder { func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true) return b.uintValue(sourceParam, dest, 8, true)
} }
@ -644,7 +724,7 @@ func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, false) return b.uintValue(sourceParam, dest, 0, false)
} }
// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist // MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist
func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder { func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, true) return b.uintValue(sourceParam, dest, 0, true)
} }
@ -772,7 +852,7 @@ func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, false) return b.uintsValue(sourceParam, dest, false)
} }
// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist // MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder { func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, true) return b.uintsValue(sourceParam, dest, true)
} }
@ -782,7 +862,7 @@ func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, false) return b.uintsValue(sourceParam, dest, false)
} }
// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist // MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder { func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, true) return b.uintsValue(sourceParam, dest, true)
} }
@ -792,7 +872,7 @@ func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, false) return b.uintsValue(sourceParam, dest, false)
} }
// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist // MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder { func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, true) return b.uintsValue(sourceParam, dest, true)
} }
@ -802,7 +882,7 @@ func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
return b.uintsValue(sourceParam, dest, false) return b.uintsValue(sourceParam, dest, false)
} }
// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist // MustUint8s requires parameter value to exist to bind to uint8 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder { func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder {
return b.uintsValue(sourceParam, dest, true) return b.uintsValue(sourceParam, dest, true)
} }
@ -812,7 +892,7 @@ func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder {
return b.uintsValue(sourceParam, dest, false) return b.uintsValue(sourceParam, dest, false)
} }
// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist // MustUints requires parameter value to exist to bind to uint slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder { func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder {
return b.uintsValue(sourceParam, dest, true) return b.uintsValue(sourceParam, dest, true)
} }
@ -822,7 +902,7 @@ func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
return b.boolValue(sourceParam, dest, false) return b.boolValue(sourceParam, dest, false)
} }
// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist // MustBool requires parameter value to exist to bind to bool variable. Returns error when value does not exist
func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder { func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder {
return b.boolValue(sourceParam, dest, true) return b.boolValue(sourceParam, dest, true)
} }
@ -887,7 +967,7 @@ func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder {
return b.boolsValue(sourceParam, dest, false) return b.boolsValue(sourceParam, dest, false)
} }
// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist // MustBools requires parameter values to exist to bind to slice of bool variables. Returns error when values does not exist
func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder { func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder {
return b.boolsValue(sourceParam, dest, true) return b.boolsValue(sourceParam, dest, true)
} }
@ -897,7 +977,7 @@ func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder {
return b.floatValue(sourceParam, dest, 64, false) return b.floatValue(sourceParam, dest, 64, false)
} }
// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist // MustFloat64 requires parameter value to exist to bind to float64 variable. Returns error when value does not exist
func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder { func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder {
return b.floatValue(sourceParam, dest, 64, true) return b.floatValue(sourceParam, dest, 64, true)
} }
@ -907,7 +987,7 @@ func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder {
return b.floatValue(sourceParam, dest, 32, false) return b.floatValue(sourceParam, dest, 32, false)
} }
// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist // MustFloat32 requires parameter value to exist to bind to float32 variable. Returns error when value does not exist
func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder { func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder {
return b.floatValue(sourceParam, dest, 32, true) return b.floatValue(sourceParam, dest, 32, true)
} }
@ -992,7 +1072,7 @@ func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder
return b.floatsValue(sourceParam, dest, false) return b.floatsValue(sourceParam, dest, false)
} }
// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist // MustFloat64s requires parameter values to exist to bind to slice of float64 variables. Returns error when values does not exist
func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder { func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder {
return b.floatsValue(sourceParam, dest, true) return b.floatsValue(sourceParam, dest, true)
} }
@ -1002,7 +1082,7 @@ func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder
return b.floatsValue(sourceParam, dest, false) return b.floatsValue(sourceParam, dest, false)
} }
// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist // MustFloat32s requires parameter values to exist to bind to slice of float32 variables. Returns error when values does not exist
func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder { func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder {
return b.floatsValue(sourceParam, dest, true) return b.floatsValue(sourceParam, dest, true)
} }
@ -1012,7 +1092,7 @@ func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *
return b.time(sourceParam, dest, layout, false) return b.time(sourceParam, dest, layout, false)
} }
// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist // MustTime requires parameter value to exist to bind to time.Time variable. Returns error when value does not exist
func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder { func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder {
return b.time(sourceParam, dest, layout, true) return b.time(sourceParam, dest, layout, true)
} }
@ -1043,7 +1123,7 @@ func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string
return b.times(sourceParam, dest, layout, false) return b.times(sourceParam, dest, layout, false)
} }
// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist // MustTimes requires parameter values to exist to bind to slice of time.Time variables. Returns error when values does not exist
func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder { func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
return b.times(sourceParam, dest, layout, true) return b.times(sourceParam, dest, layout, true)
} }
@ -1084,7 +1164,7 @@ func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBi
return b.duration(sourceParam, dest, false) return b.duration(sourceParam, dest, false)
} }
// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist // MustDuration requires parameter value to exist to bind to time.Duration variable. Returns error when value does not exist
func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder { func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder {
return b.duration(sourceParam, dest, true) return b.duration(sourceParam, dest, true)
} }
@ -1115,7 +1195,7 @@ func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *Valu
return b.durationsValue(sourceParam, dest, false) return b.durationsValue(sourceParam, dest, false)
} }
// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist // MustDurations requires parameter values to exist to bind to slice of time.Duration variables. Returns error when values does not exist
func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder { func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder {
return b.durationsValue(sourceParam, dest, true) return b.durationsValue(sourceParam, dest, true)
} }
@ -1159,36 +1239,57 @@ 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, false) return b.unixTime(sourceParam, dest, false, time.Second)
} }
// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding // MustUnixTime requires parameter value to exist to bind to time.Duration variable (in local time corresponding
// to the given Unix time). Returns error when value does not exist. // to the given Unix time). Returns error when value does not exist.
// //
// 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, false) return b.unixTime(sourceParam, dest, true, time.Second)
} }
// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision). // UnixTimeMilli binds parameter to time.Time variable (in local time corresponding to the given Unix time in millisecond precision).
//
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
// - 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 {
return b.unixTime(sourceParam, dest, false, time.Millisecond)
}
// MustUnixTimeMilli requires parameter value to exist to bind to time.Duration variable (in local time corresponding
// to the given Unix time in millisecond precision). Returns error when value does not exist.
//
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
// - 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 {
return b.unixTime(sourceParam, dest, true, time.Millisecond)
}
// UnixTimeNano binds parameter to time.Time variable (in local time corresponding to the given Unix time in nanosecond precision).
// //
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00 // Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
// 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, true) return b.unixTime(sourceParam, dest, false, time.Nanosecond)
} }
// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding // MustUnixTimeNano requires parameter value to exist to bind to time.Duration variable (in local Time corresponding
// to the given Unix time value in nano second precision). Returns error when value does not exist. // to the given Unix time value in nano second precision). Returns error when value does not exist.
// //
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
@ -1196,13 +1297,13 @@ 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, true) return b.unixTime(sourceParam, dest, true, time.Nanosecond)
} }
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder { func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder {
if b.failFast && b.errors != nil { if b.failFast && b.errors != nil {
return b return b
} }
@ -1221,10 +1322,13 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
return b return b
} }
if isNano { switch precision {
*dest = time.Unix(0, n) case time.Second:
} else {
*dest = time.Unix(n, 0) *dest = time.Unix(n, 0)
case time.Millisecond:
*dest = time.UnixMilli(n)
case time.Nanosecond:
*dest = time.Unix(0, n)
} }
return b return b
} }

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo package echo
import ( import (
@ -9,16 +12,13 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath"
"strings" "strings"
"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
@ -102,8 +102,8 @@ type (
// 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()`.
@ -171,7 +171,11 @@ type (
// 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 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
// 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.
Error(err error) Error(err error)
// Handler returns the matched handler by router. // Handler returns the matched handler by router.
@ -183,7 +187,7 @@ type (
// Logger returns the `Logger` instance. // Logger returns the `Logger` instance.
Logger() Logger Logger() Logger
// Set the logger // SetLogger Set the logger
SetLogger(l Logger) SetLogger(l Logger)
// Echo returns the `Echo` instance. // Echo returns the `Echo` instance.
@ -193,21 +197,41 @@ type (
// 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
pnames []string
pvalues []string
query url.Values query url.Values
handler HandlerFunc
store Map
echo *Echo echo *Echo
logger Logger logger Logger
store Map
lock sync.RWMutex lock sync.RWMutex
}
// following fields are set by Router
// 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 (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
// Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests.
// It is added to context only when Router does not find matching method handler for request.
ContextKeyHeaderAllow = "echo_header_allow"
) )
const ( const (
@ -277,11 +301,16 @@ func (c *context) RealIP() string {
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
i := strings.IndexAny(ip, ",") i := strings.IndexAny(ip, ",")
if i > 0 { if i > 0 {
return strings.TrimSpace(ip[:i]) xffip := strings.TrimSpace(ip[:i])
xffip = strings.TrimPrefix(xffip, "[")
xffip = strings.TrimSuffix(xffip, "]")
return xffip
} }
return ip return ip
} }
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
ip = strings.TrimPrefix(ip, "[")
ip = strings.TrimSuffix(ip, "]")
return ip return ip
} }
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr) ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
@ -315,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
@ -333,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]
@ -475,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)
} }
@ -493,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) {
@ -562,29 +587,6 @@ func (c *context) Stream(code int, contentType string, r io.Reader) (err error)
return return
} }
func (c *context) File(file string) (err error) {
f, err := os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.Join(file, indexPage)
f, err = os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return
}
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
return
}
func (c *context) Attachment(file, name string) error { func (c *context) Attachment(file, name string) error {
return c.contentDisposition(file, name, "attachment") return c.contentDisposition(file, name, "attachment")
} }
@ -593,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)
} }
@ -649,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] = ""
} }
} }

52
vendor/github.com/labstack/echo/v4/context_fs.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"errors"
"io"
"io/fs"
"net/http"
"path/filepath"
)
func (c *context) File(file string) error {
return fsFile(c, file, c.echo.Filesystem)
}
// FileFS serves file from given file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (c *context) FileFS(file string, filesystem fs.FS) error {
return fsFile(c, file, filesystem)
}
func fsFile(c Context, file string, filesystem fs.FS) error {
f, err := filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
f, err = filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return err
}
}
ff, ok := f.(io.ReadSeeker)
if !ok {
return errors.New("file does not implement io.ReadSeeker")
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
return nil
}

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.
@ -37,19 +40,16 @@ Learn more at https://echo.labstack.com
package echo package echo
import ( import (
"bytes"
stdContext "context" stdContext "context"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
stdLog "log" stdLog "log"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
@ -63,22 +63,29 @@ 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. //
Echo struct { // 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.
// Adding new routes after the server has been started is also not safe!
type Echo struct {
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 binded) without having data races. // listener address info (on which interface/port was listener bound) without having data races.
startupMutex sync.RWMutex startupMutex sync.RWMutex
StdLogger *stdLog.Logger
colorer *color.Color colorer *color.Color
// 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.
premiddleware []MiddlewareFunc premiddleware []MiddlewareFunc
middleware []MiddlewareFunc middleware []MiddlewareFunc
maxParam *int maxParam *int
router *Router router *Router
routers map[string]*Router routers map[string]*Router
notFoundHandler HandlerFunc
pool sync.Pool pool sync.Pool
StdLogger *stdLog.Logger
Server *http.Server Server *http.Server
TLSServer *http.Server TLSServer *http.Server
Listener net.Listener Listener net.Listener
@ -96,53 +103,55 @@ type (
Logger Logger Logger Logger
IPExtractor IPExtractor IPExtractor IPExtractor
ListenerNetwork string ListenerNetwork string
}
// Route contains a handler and information for matching against requests. // OnAddRouteHandler is called when Echo adds new route to specific host router.
Route struct { OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
}
// Route contains a handler and information for matching against requests.
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(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(Context) error type HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler. // HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, 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.
@ -161,7 +170,12 @@ const (
// MIME types // MIME types
const ( const (
// MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259
MIMEApplicationJSON = "application/json" 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
@ -186,12 +200,18 @@ const (
PROPFIND = "PROPFIND" PROPFIND = "PROPFIND"
// REPORT Method can be used to get information about a resource, see rfc 3253 // REPORT Method can be used to get information about a resource, see rfc 3253
REPORT = "REPORT" REPORT = "REPORT"
// RouteNotFound is special method type for routes handling "route not found" (404) cases
RouteNotFound = "echo_route_not_found"
) )
// Headers // Headers
const ( const (
HeaderAccept = "Accept" HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding" HeaderAcceptEncoding = "Accept-Encoding"
// HeaderAllow is the name of the "Allow" header field used to list the set of methods
// advertised as supported by the target resource. Returning an Allow header is mandatory
// for status 405 (method not found) and useful for the OPTIONS method in responses.
// See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
HeaderAllow = "Allow" HeaderAllow = "Allow"
HeaderAuthorization = "Authorization" HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition" HeaderContentDisposition = "Content-Disposition"
@ -203,6 +223,7 @@ const (
HeaderIfModifiedSince = "If-Modified-Since" HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified" HeaderLastModified = "Last-Modified"
HeaderLocation = "Location" HeaderLocation = "Location"
HeaderRetryAfter = "Retry-After"
HeaderUpgrade = "Upgrade" HeaderUpgrade = "Upgrade"
HeaderVary = "Vary" HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate" HeaderWWWAuthenticate = "WWW-Authenticate"
@ -212,12 +233,14 @@ const (
HeaderXForwardedSsl = "X-Forwarded-Ssl" HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme" HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP" HeaderXRealIP = "X-Real-Ip"
HeaderXRequestID = "X-Request-ID" HeaderXRequestID = "X-Request-Id"
HeaderXCorrelationID = "X-Correlation-ID" HeaderXCorrelationID = "X-Correlation-Id"
HeaderXRequestedWith = "X-Requested-With" HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server" HeaderServer = "Server"
HeaderOrigin = "Origin" HeaderOrigin = "Origin"
HeaderCacheControl = "Cache-Control"
HeaderConnection = "Connection"
// Access control // Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
@ -242,7 +265,7 @@ const (
const ( const (
// Version of Echo // Version of Echo
Version = "4.6.3" 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 = `
@ -257,8 +280,7 @@ ____________________________________O/_______
` `
) )
var ( var methods = [...]string{
methods = [...]string{
http.MethodConnect, http.MethodConnect,
http.MethodDelete, http.MethodDelete,
http.MethodGet, http.MethodGet,
@ -270,23 +292,51 @@ var (
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
ErrPreconditionFailed = NewHTTPError(http.StatusPreconditionFailed) // HTTP 412 Precondition Failed
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // HTTP 413 Payload Too Large
ErrRequestURITooLong = NewHTTPError(http.StatusRequestURITooLong) // HTTP 414 URI Too Long
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) // HTTP 415 Unsupported Media Type
ErrRequestedRangeNotSatisfiable = NewHTTPError(http.StatusRequestedRangeNotSatisfiable) // HTTP 416 Range Not Satisfiable
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") ErrValidatorNotRegistered = errors.New("validator not registered")
ErrRendererNotRegistered = errors.New("renderer not registered") ErrRendererNotRegistered = errors.New("renderer not registered")
ErrInvalidRedirectCode = errors.New("invalid redirect status code") ErrInvalidRedirectCode = errors.New("invalid redirect status code")
@ -295,20 +345,28 @@ var (
ErrInvalidListenerNetwork = errors.New("invalid listener network") 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
return ErrMethodNotAllowed // another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code.
var MethodNotAllowedHandler = func(c Context) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
if ok && routerAllowMethods != "" {
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) {
e = &Echo{ e = &Echo{
filesystem: createFilesystem(),
Server: new(http.Server), Server: new(http.Server),
TLSServer: new(http.Server), TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{ AutoTLSManager: autocert.Manager{
@ -361,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) {
@ -386,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
@ -469,8 +533,21 @@ func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodTrace, path, h, m...) return e.Add(http.MethodTrace, path, h, m...)
} }
// Any registers a new route for all HTTP methods and path with matching handler // RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases)
// for current request URL.
// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with
// wildcard/match-any character (`/*`, `/download/*` etc).
//
// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(RouteNotFound, path, h, m...)
}
// Any registers a new route for all HTTP methods (supported by Echo) and path with matching handler
// in the router with optional route-level middleware. // in the router with optional route-level middleware.
//
// Note: this method only adds specific set of supported HTTP methods as handler and is not true
// "catch-any-arbitrary-method" way of matching requests.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods)) routes := make([]*Route, len(methods))
for i, m := range methods { for i, m := range methods {
@ -489,50 +566,6 @@ func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middlew
return routes return routes
} }
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
return e.static(prefix, root, e.GET)
}
func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
h := func(c Context) error {
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
// The access path does not exist
return NotFoundHandler(c)
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, p+"/")
}
return c.File(name)
}
// Handle added routes based on trailing slash:
// /prefix => exact route "/prefix" + any route "/prefix/*"
// /prefix/ => only any route "/prefix/*"
if prefix != "" {
if prefix[len(prefix)-1] == '/' {
// Only add any route for intentional trailing slash
return get(prefix+"*", h)
}
get(prefix, h)
}
return get(prefix+"/*", h)
}
func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route, func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
m ...MiddlewareFunc) *Route { m ...MiddlewareFunc) *Route {
return get(path, func(c Context) error { return get(path, func(c Context) error {
@ -545,20 +578,20 @@ func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
return e.file(path, file, e.GET, m...) return e.file(path, file, e.GET, m...)
} }
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { func (e *Echo) add(host, method, path string, handler HandlerFunc, middlewares ...MiddlewareFunc) *Route {
name := handlerName(handler)
router := e.findRouter(host) router := e.findRouter(host)
router.Add(method, path, func(c Context) error { //FIXME: when handler+middleware are both nil ... make it behave like handler removal
h := applyMiddleware(handler, middleware...) name := handlerName(handler)
route := router.add(method, path, name, func(c Context) error {
h := applyMiddleware(handler, middlewares...)
return h(c) return h(c)
}) })
r := &Route{
Method: method, if e.OnAddRouteHandler != nil {
Path: path, e.OnAddRouteHandler(host, *route, handler, middlewares)
Name: name,
} }
e.router.routes[method+path] = r
return r return route
} }
// Add registers a new route for an HTTP method and path with matching handler // Add registers a new route for an HTTP method and path with matching handler
@ -582,7 +615,7 @@ func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
return return
} }
// URI generates a URI from handler. // URI generates an URI from handler.
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string { func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
name := handlerName(handler) name := handlerName(handler)
return e.Reverse(name, params...) return e.Reverse(name, params...)
@ -593,37 +626,15 @@ 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 {
uri := new(bytes.Buffer) return e.router.Reverse(name, params...)
ln := len(params)
n := 0
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
} }
// Routes returns the registered routes. // Routes returns the registered routes for default router.
// In case when Echo serves multiple hosts/domains use `e.Routers()["domain2.site"].Routes()` to get specific host routes.
func (e *Echo) Routes() []*Route { func (e *Echo) Routes() []*Route {
routes := make([]*Route, 0, len(e.router.routes)) return e.router.Routes()
for _, v := range e.router.routes {
routes = append(routes, v)
}
return routes
} }
// AcquireContext returns an empty `Context` instance from the pool. // AcquireContext returns an empty `Context` instance from the pool.
@ -643,7 +654,7 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context // Acquire context
c := e.pool.Get().(*context) c := e.pool.Get().(*context)
c.Reset(r, w) c.Reset(r, w)
h := NotFoundHandler var h HandlerFunc
if e.premiddleware == nil { if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c) e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
@ -717,7 +728,7 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
switch v := fileOrContent.(type) { switch v := fileOrContent.(type) {
case string: case string:
return ioutil.ReadFile(v) return os.ReadFile(v)
case []byte: case []byte:
return v, nil return v, nil
default: default:
@ -765,7 +776,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return s.Serve(e.Listener) return s.Serve(e.Listener)
} }
func (e *Echo) configureServer(s *http.Server) (err error) { func (e *Echo) configureServer(s *http.Server) error {
// Setup // Setup
e.colorer.SetOutput(e.Logger.Output()) e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger s.ErrorLog = e.StdLogger
@ -780,10 +791,11 @@ func (e *Echo) configureServer(s *http.Server) (err error) {
if s.TLSConfig == nil { if s.TLSConfig == nil {
if e.Listener == nil { if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork) l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil { if err != nil {
return err return err
} }
e.Listener = l
} }
if !e.HidePort { if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@ -824,7 +836,7 @@ func (e *Echo) TLSListenerAddr() net.Addr {
} }
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext). // StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) { func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error {
e.startupMutex.Lock() e.startupMutex.Lock()
// Setup // Setup
s := e.Server s := e.Server
@ -841,11 +853,12 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
} }
if e.Listener == nil { if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork) l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil { if err != nil {
e.startupMutex.Unlock() e.startupMutex.Unlock()
return err return err
} }
e.Listener = l
} }
if !e.HidePort { if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@ -899,6 +912,15 @@ func (he *HTTPError) SetInternal(err error) *HTTPError {
return he return he
} }
// WithInternal returns clone of HTTPError with err set to HTTPError.Internal field
func (he *HTTPError) WithInternal(err error) *HTTPError {
return &HTTPError{
Code: he.Code,
Message: he.Message,
Internal: err,
}
}
// Unwrap satisfies the Go 1.13 error wrapper interface. // Unwrap satisfies the Go 1.13 error wrapper interface.
func (he *HTTPError) Unwrap() error { func (he *HTTPError) Unwrap() error {
return he.Internal return he.Internal
@ -928,8 +950,8 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
// GetPath returns RawPath, if it's empty returns Path from URL // GetPath returns RawPath, if it's empty returns Path from URL
// Difference between RawPath and Path is: // Difference between RawPath and Path is:
// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/. // - Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
// * RawPath is an optional field which only gets set if the default encoding is different from Path. // - RawPath is an optional field which only gets set if the default encoding is different from Path.
func GetPath(r *http.Request) string { func GetPath(r *http.Request) string {
path := r.URL.RawPath path := r.URL.RawPath
if path == "" { if path == "" {

162
vendor/github.com/labstack/echo/v4/echo_fs.go generated vendored Normal file
View File

@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
type filesystem struct {
// Filesystem is file system used by Static and File handlers to access files.
// Defaults to os.DirFS(".")
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
Filesystem fs.FS
}
func createFilesystem() filesystem {
return filesystem{
Filesystem: newDefaultFS(),
}
}
// Static registers a new route with path prefix to serve static files from the provided root directory.
func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
subFs := MustSubFS(e.Filesystem, fsRoot)
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(subFs, false),
)
}
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(filesystem, false),
)
}
// StaticDirectoryHandler creates handler function to serve files from provided file system
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
return func(c Context) error {
p := c.Param("*")
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
tmpPath, err := url.PathUnescape(p)
if err != nil {
return fmt.Errorf("failed to unescape path variable: %w", err)
}
p = tmpPath
}
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
fi, err := fs.Stat(fileSystem, name)
if err != nil {
return ErrNotFound
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, sanitizeURI(p+"/"))
}
return fsFile(c, name, fileSystem)
}
}
// FileFS registers a new route with path to serve file from the provided file system.
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
return e.GET(path, StaticFileHandler(file, filesystem), m...)
}
// StaticFileHandler creates handler function to serve file from provided file system
func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
return func(c Context) error {
return fsFile(c, file, filesystem)
}
}
// defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`.
// v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface.
// Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/`
// etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not
// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to
// traverse up from current executable run path.
// NB: private because you really should use fs.FS implementation instances
type defaultFS struct {
prefix string
fs fs.FS
}
func newDefaultFS() *defaultFS {
dir, _ := os.Getwd()
return &defaultFS{
prefix: dir,
fs: nil,
}
}
func (fs defaultFS) Open(name string) (fs.File, error) {
if fs.fs == nil {
return os.Open(name)
}
return fs.fs.Open(name)
}
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
if dFS, ok := currentFs.(*defaultFS); ok {
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
// fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
if !filepath.IsAbs(root) {
root = filepath.Join(dFS.prefix, root)
}
return &defaultFS{
prefix: root,
fs: os.DirFS(root),
}, nil
}
return fs.Sub(currentFs, root)
}
// MustSubFS creates sub FS from current filesystem or panic on failure.
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
//
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
// create sub fs which uses necessary prefix for directory path.
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
subFs, err := subFS(currentFs, fsRoot)
if err != nil {
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
}
return subFs
}
func sanitizeURI(uri string) string {
// double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
// we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
uri = "/" + strings.TrimLeft(uri, `/\`)
}
return uri
}

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.
@ -102,16 +105,18 @@ func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
return return
} }
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(prefix, root string) {
g.static(prefix, root, g.GET)
}
// File implements `Echo#File()` for sub-routes within the Group. // File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) { func (g *Group) File(path, file string) {
g.file(path, file, g.GET) g.file(path, file, g.GET)
} }
// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
//
// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(RouteNotFound, path, h, m...)
}
// Add implements `Echo#Add()` for sub-routes within the Group. // Add implements `Echo#Add()` for sub-routes within the Group.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// Combine into a new slice to avoid accidentally passing the same slice for // Combine into a new slice to avoid accidentally passing the same slice for

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