Merge branch 'develop'
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details

This commit is contained in:
Maxim Lebedev 2023-03-16 23:57:31 +06:00
commit fbda134b7d
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
349 changed files with 155714 additions and 113033 deletions

View File

@ -7,7 +7,7 @@ srcdir = .
GO ?= go GO ?= go
GOFLAGS ?= -buildvcs=true GOFLAGS ?= -buildvcs=true
EXECUTABLE ?= indieauth EXECUTABLE ?= auth
#### End of system configuration section. #### #### End of system configuration section. ####
@ -17,11 +17,9 @@ all: main.go
$(GO) build -v $(GOFLAGS) -o $(EXECUTABLE) $(GO) build -v $(GOFLAGS) -o $(EXECUTABLE)
clean: ## Delete all files in the current directory that are normally created by building the program clean: ## Delete all files in the current directory that are normally created by building the program
-rm $(srcdir)/internal/testing/httptest/{cert,key}.pem
$(GO) clean $(GO) clean
check: ## Perform self-tests check: ## Perform self-tests
$(GO) generate $(srcdir)/internal/testing/httptest/...
$(GO) test -v -cover -failfast -short -shuffle=on $(GOFLAGS) $(srcdir)/... $(GO) test -v -cover -failfast -short -shuffle=on $(GOFLAGS) $(srcdir)/...
.PHONY: help .PHONY: help

View File

@ -1,83 +1,49 @@
--- ---
kind: pipeline kind: "pipeline"
type: docker type: "docker"
name: default name: "default"
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: test - name: "test"
image: golang:latest image: "golang:1.20"
volumes: volumes:
- name: modules - name: "modules"
path: /go/pkg/mod path: "/go/pkg/mod"
commands: commands:
- make check - "make check"
- name: build - name: "build"
image: golang:latest image: "golang:1.20"
volumes: volumes:
- name: modules - name: "modules"
path: /go/pkg/mod path: "/go/pkg/mod"
commands: commands:
- make - "make"
depends_on: depends_on:
- test - "test"
- name: stop-service - name: "delivery"
image: appleboy/drone-ssh image: "drillster/drone-rsync"
settings: settings:
host: hosts:
from_secret: SSH_HOST from_secret: "SSH_HOST_IP"
username: root
key: key:
from_secret: SSH_PRIVATE_KEY from_secret: "SSH_PRIVATE_KEY"
source: "./auth"
target: "/etc/auth/auth"
prescript:
- "systemctl stop auth"
script: script:
- "systemctl stop indieauth" - "systemctl start auth"
depends_on: depends_on:
- build - build
when: when:
branch: branch:
- master - master
- name: delivery
image: appleboy/drone-scp
settings:
host:
from_secret: SSH_HOST
username: root
password: ""
key:
from_secret: SSH_PRIVATE_KEY
target: "/root/indieauth"
source:
- "indieauth"
overwrite: true
# NOTE(toby3d): Just run a previous version of the instance if it failed to deliver the current one.
failure: ignore
depends_on:
- stop-service
when:
branch:
- master
- name: start-service
image: appleboy/drone-ssh
settings:
host:
from_secret: SSH_HOST
username: root
key:
from_secret: SSH_PRIVATE_KEY
script:
- "systemctl start indieauth"
depends_on:
- delivery
when:
branch:
- master
volumes: volumes:
- name: modules - name: modules
temp: {} temp: {}

View File

@ -1,5 +1,5 @@
# [auth.toby3d.me](https://auth.toby3d.me/) [![Build Status](https://drone.toby3d.me/api/badges/toby3d/auth/status.svg)](https://drone.toby3d.me/toby3d/auth) # auth [![Build Status](https://drone.toby3d.me/api/badges/toby3d/auth/status.svg)](https://drone.toby3d.me/toby3d/auth)
> [IndieAuth](https://indieauth.net/source/) personal instance. > Personal [IndieAuth](https://indieauth.net/source/) server.
An attempt to implement my own server to authenticate and authorize third-party applications through [IndieWeb](https://indieweb.org/) sites on [Go](https://go.dev/). Based on the latest versions of the protocol standards and related batteries like [TicketAuth](https://indieweb.org/IndieAuth_Ticket_Auth) and [RelMeAuth](https://microformats.org/wiki/RelMeAuth). An attempt to implement my own server to authenticate and authorize third-party applications through [IndieWeb](https://indieweb.org/) sites on [Go](https://go.dev/). Based on the latest versions of the protocol standards and related batteries like [TicketAuth](https://indieweb.org/IndieAuth_Ticket_Auth) and [RelMeAuth](https://microformats.org/wiki/RelMeAuth).

44
go.mod
View File

@ -4,33 +4,33 @@ go 1.19
require ( require (
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/brianvoe/gofakeit/v6 v6.20.1 github.com/brianvoe/gofakeit/v6 v6.20.2
github.com/caarlos0/env/v6 v6.10.1 github.com/caarlos0/env/v7 v7.1.0
github.com/go-logfmt/logfmt v0.5.1 github.com/go-logfmt/logfmt v0.6.0
github.com/goccy/go-json v0.10.0 github.com/goccy/go-json v0.10.1
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/lestrrat-go/jwx/v2 v2.0.8 github.com/lestrrat-go/jwx/v2 v2.0.8
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/valyala/fasttemplate v1.2.2 github.com/valyala/fasttemplate v1.2.2
github.com/valyala/quicktemplate v1.7.0 github.com/valyala/quicktemplate v1.7.0
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.7
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4 golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
golang.org/x/text v0.6.0 golang.org/x/text v0.8.0
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 inet.af/netaddr v0.0.0-20220811202034-502d2d690317
modernc.org/sqlite v1.20.2 modernc.org/sqlite v1.21.0
source.toby3d.me/toby3d/form v0.3.0 source.toby3d.me/toby3d/form v0.3.0
willnorris.com/go/microformats v1.1.1 willnorris.com/go/microformats v1.2.0
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.14 // indirect github.com/klauspost/compress v1.16.3 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect
@ -38,20 +38,20 @@ require (
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lib/pq v1.10.6 // indirect github.com/lib/pq v1.10.6 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.44.0 // indirect github.com/valyala/fasthttp v1.45.0 // indirect
go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.5.0 // indirect golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.7.0 // indirect golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.5.0 // indirect golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.4.0 // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.5.0 // indirect golang.org/x/tools v0.7.0 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.2 // indirect modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect

107
go.sum
View File

@ -1,15 +1,13 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/brianvoe/gofakeit/v6 v6.20.2 h1:FLloufuC7NcbHqDzVQ42CG9AKryS1gAGCRt8nQRsW+Y=
github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek= github.com/brianvoe/gofakeit/v6 v6.20.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
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=
@ -19,15 +17,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2U
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 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-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
@ -39,11 +36,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
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/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
@ -63,12 +58,12 @@ github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
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/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/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -82,79 +77,71 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFy
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/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM= github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/intern v0.0.0-20220617035311-6925f38cc365 h1:t9hFvR102YlOqU0fQn1wgwhNvSbHGBbbJxX9JKfU3l0= go4.org/intern v0.0.0-20230205224052-192e9f60865c h1:b8WZ7Ja8nKegYxfwDLLwT00ZKv4lXAQrw8LYPK+cHSI=
go4.org/intern v0.0.0-20220617035311-6925f38cc365/go.mod h1:WXRv3p7T6gzt0CcJm43AAKdKVZmcQbwwC7EwquU5BZU= go4.org/intern v0.0.0-20230205224052-192e9f60865c/go.mod h1:RJ0SVrOMpxLhgb5noIV+09zI1RsRlMsbUcSxpWHqbrE=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
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-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-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-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4 h1:CNkDRtCj8otM5CFz5jYvbr8ioXX8flVsLfDWEj0M5kk= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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-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-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-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
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-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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
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-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-20200923182605-d9f96fdee20d/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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-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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
@ -164,31 +151,31 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU= inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.2 h1:9AaVzJH1Yf0u9iOZRjjuvqxLoGqybqVFbAUC5rvi9u8= modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
modernc.org/sqlite v1.20.2/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
source.toby3d.me/toby3d/form v0.3.0 h1:kI8apdFeVr+koqTTGVoIRiR5NMqjrhCJlajYlu+1bVw= source.toby3d.me/toby3d/form v0.3.0 h1:kI8apdFeVr+koqTTGVoIRiR5NMqjrhCJlajYlu+1bVw=
source.toby3d.me/toby3d/form v0.3.0/go.mod h1:drlHMC+j/gb5zsttCSwx8qcYsbaRW+wFfE8bK6y+oeY= source.toby3d.me/toby3d/form v0.3.0/go.mod h1:drlHMC+j/gb5zsttCSwx8qcYsbaRW+wFfE8bK6y+oeY=
willnorris.com/go/microformats v1.1.1 h1:h5tk2luq6KBIRcwMGdksxdeea4GGuWrRFie5460OAbo= willnorris.com/go/microformats v1.2.0 h1:73pzJCLJM69kYE5qsLI9OOC/7sImNVOzya9EQ0+1wmM=
willnorris.com/go/microformats v1.1.1/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0= willnorris.com/go/microformats v1.2.0/go.mod h1:RrlwCSvib4qz+JICKiN7rON4phzQ3HAT7j6s4O2cZj4=

View File

@ -48,7 +48,7 @@ func NewHandler(opts NewHandlerOptions) *Handler {
func (h *Handler) Handler() http.Handler { func (h *Handler) Handler() http.Handler {
chain := middleware.Chain{ chain := middleware.Chain{
middleware.CSRFWithConfig(middleware.CSRFConfig{ middleware.CSRFWithConfig(middleware.CSRFConfig{
Skipper: func(w http.ResponseWriter, r *http.Request) bool { Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
head, _ := urlutil.ShiftPath(r.URL.Path) head, _ := urlutil.ShiftPath(r.URL.Path)
return head == "" return head == ""
@ -65,7 +65,7 @@ func (h *Handler) Handler() http.Handler {
CookieHTTPOnly: true, CookieHTTPOnly: true,
}), }),
middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{ middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
Skipper: func(w http.ResponseWriter, r *http.Request) bool { Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
head, _ := urlutil.ShiftPath(r.URL.Path) head, _ := urlutil.ShiftPath(r.URL.Path)
return r.Method != http.MethodPost || return r.Method != http.MethodPost ||
@ -154,11 +154,8 @@ func (h *Handler) handleAuthorize(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
web.WriteTemplate(w, &web.ErrorPage{ web.WriteTemplate(w, &web.ErrorPage{
BaseOf: baseOf, BaseOf: baseOf,
Error: domain.NewError( Error: domain.NewError(domain.ErrorCodeInvalidClient, "requested redirect_uri is not"+
domain.ErrorCodeInvalidClient, " registered on client_id side", ""),
"requested redirect_uri is not registered on client_id side",
"",
),
}) })
return return

View File

@ -11,10 +11,6 @@ import (
type ( type (
AuthAuthorizationRequest struct { AuthAuthorizationRequest struct {
// Indicates to the authorization server that an authorization
// code should be returned as the response.
ResponseType domain.ResponseType `form:"response_type"` // code
// The client URL. // The client URL.
ClientID domain.ClientID `form:"client_id"` ClientID domain.ClientID `form:"client_id"`
@ -28,12 +24,9 @@ type (
// The hashing method used to calculate the code challenge. // The hashing method used to calculate the code challenge.
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"` CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
// A space-separated list of scopes the client is requesting, // Indicates to the authorization server that an authorization
// e.g. "profile", or "profile create". If the client omits this // code should be returned as the response.
// value, the authorization server MUST NOT issue an access ResponseType domain.ResponseType `form:"response_type"` // code
// token for this authorization code. Only the user's profile
// URL may be returned without any scope requested.
Scope domain.Scopes `form:"scope,omitempty"`
// A parameter set by the client which will be included when the // A parameter set by the client which will be included when the
// user is redirected back to the client. This is used to // user is redirected back to the client. This is used to
@ -43,19 +36,26 @@ type (
// The code challenge as previously described. // The code challenge as previously described.
CodeChallenge string `form:"code_challenge,omitempty"` CodeChallenge string `form:"code_challenge,omitempty"`
// A space-separated list of scopes the client is requesting,
// e.g. "profile", or "profile create". If the client omits this
// value, the authorization server MUST NOT issue an access
// token for this authorization code. Only the user's profile
// URL may be returned without any scope requested.
Scope domain.Scopes `form:"scope,omitempty"`
} }
AuthVerifyRequest struct { AuthVerifyRequest struct {
ClientID domain.ClientID `form:"client_id"` ClientID domain.ClientID `form:"client_id"`
Me domain.Me `form:"me"` Me domain.Me `form:"me"`
RedirectURI domain.URL `form:"redirect_uri"` RedirectURI domain.URL `form:"redirect_uri"`
ResponseType domain.ResponseType `form:"response_type"`
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"` CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
Scope domain.Scopes `form:"scope[],omitempty"` ResponseType domain.ResponseType `form:"response_type"`
Authorize string `form:"authorize"` Authorize string `form:"authorize"`
CodeChallenge string `form:"code_challenge,omitempty"` CodeChallenge string `form:"code_challenge,omitempty"`
State string `form:"state"` State string `form:"state"`
Provider string `form:"provider"` Provider string `form:"provider"`
Scope domain.Scopes `form:"scope[],omitempty"`
} }
AuthExchangeRequest struct { AuthExchangeRequest struct {

View File

@ -1,7 +1,5 @@
package common package common
const charsetUTF8 = "charset=UTF-8"
const ( const (
MIMEApplicationForm string = "application/x-www-form-urlencoded" MIMEApplicationForm string = "application/x-www-form-urlencoded"
MIMEApplicationJSON string = "application/json" MIMEApplicationJSON string = "application/json"
@ -10,6 +8,8 @@ const (
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8 MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain string = "text/plain" MIMETextPlain string = "text/plain"
MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8 MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8
charsetUTF8 = "charset=UTF-8"
) )
const ( const (

View File

@ -12,26 +12,26 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type Action struct { type Action struct {
uid string action string
} }
//nolint:gochecknoglobals // structs cannot be constants //nolint:gochecknoglobals // structs cannot be constants
var ( var (
ActionUnd = Action{uid: ""} // "und" ActionUnd = Action{action: ""} // "und"
// ActionRevoke represent action for revoke token. // ActionRevoke represent action for revoke token.
ActionRevoke = Action{uid: "revoke"} // "revoke" ActionRevoke = Action{action: "revoke"} // "revoke"
// ActionTicket represent action for TicketAuth extension. // ActionTicket represent action for TicketAuth extension.
ActionTicket = Action{uid: "ticket"} // "ticket" ActionTicket = Action{action: "ticket"} // "ticket"
) )
var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "") var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "")
//nolint:gochecknoglobals //nolint:gochecknoglobals
var uidsActions = map[string]Action{ var uidsActions = map[string]Action{
ActionRevoke.uid: ActionRevoke, ActionRevoke.action: ActionRevoke,
ActionTicket.uid: ActionTicket, ActionTicket.action: ActionTicket,
} }
// ParseAction parse string identifier of action into struct enum. // ParseAction parse string identifier of action into struct enum.
@ -74,8 +74,8 @@ func (a *Action) UnmarshalJSON(v []byte) error {
// String returns string representation of action. // String returns string representation of action.
func (a Action) String() string { func (a Action) String() string {
if a.uid != "" { if a.action != "" {
return a.uid return a.action
} }
return common.Und return common.Und

View File

@ -1,6 +1,7 @@
package domain package domain
//nolint:gosec // support old clients //nolint:gosec // support old clients
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha1" "crypto/sha1"
@ -20,17 +21,17 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type CodeChallengeMethod struct { type CodeChallengeMethod struct {
uid string codeChallengeMethod string
} }
//nolint:gochecknoglobals // structs cannot be constants //nolint:gochecknoglobals // structs cannot be constants
var ( var (
CodeChallengeMethodUnd = CodeChallengeMethod{uid: ""} // "und" CodeChallengeMethodUnd = CodeChallengeMethod{codeChallengeMethod: ""} // "und"
CodeChallengeMethodPLAIN = CodeChallengeMethod{uid: "plain"} // "PLAIN" CodeChallengeMethodPLAIN = CodeChallengeMethod{codeChallengeMethod: "plain"} // "PLAIN"
CodeChallengeMethodMD5 = CodeChallengeMethod{uid: "md5"} // "MD5" CodeChallengeMethodMD5 = CodeChallengeMethod{codeChallengeMethod: "md5"} // "MD5"
CodeChallengeMethodS1 = CodeChallengeMethod{uid: "s1"} // "S1" CodeChallengeMethodS1 = CodeChallengeMethod{codeChallengeMethod: "s1"} // "S1"
CodeChallengeMethodS256 = CodeChallengeMethod{uid: "s256"} // "S256" CodeChallengeMethodS256 = CodeChallengeMethod{codeChallengeMethod: "s256"} // "S256"
CodeChallengeMethodS512 = CodeChallengeMethod{uid: "s512"} // "S512" CodeChallengeMethodS512 = CodeChallengeMethod{codeChallengeMethod: "s512"} // "S512"
) )
var ErrCodeChallengeMethodUnknown error = NewError( var ErrCodeChallengeMethodUnknown error = NewError(
@ -41,11 +42,11 @@ var ErrCodeChallengeMethodUnknown error = NewError(
//nolint:gochecknoglobals // maps cannot be constants //nolint:gochecknoglobals // maps cannot be constants
var uidsMethods = map[string]CodeChallengeMethod{ var uidsMethods = map[string]CodeChallengeMethod{
CodeChallengeMethodMD5.uid: CodeChallengeMethodMD5, CodeChallengeMethodMD5.codeChallengeMethod: CodeChallengeMethodMD5,
CodeChallengeMethodPLAIN.uid: CodeChallengeMethodPLAIN, CodeChallengeMethodPLAIN.codeChallengeMethod: CodeChallengeMethodPLAIN,
CodeChallengeMethodS1.uid: CodeChallengeMethodS1, CodeChallengeMethodS1.codeChallengeMethod: CodeChallengeMethodS1,
CodeChallengeMethodS256.uid: CodeChallengeMethodS256, CodeChallengeMethodS256.codeChallengeMethod: CodeChallengeMethodS256,
CodeChallengeMethodS512.uid: CodeChallengeMethodS512, CodeChallengeMethodS512.codeChallengeMethod: CodeChallengeMethodS512,
} }
// ParseCodeChallengeMethod parse string identifier of code challenge method // ParseCodeChallengeMethod parse string identifier of code challenge method
@ -85,13 +86,13 @@ func (ccm *CodeChallengeMethod) UnmarshalJSON(v []byte) error {
} }
func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) { func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(ccm.uid)), nil return []byte(strconv.Quote(ccm.codeChallengeMethod)), nil
} }
// String returns string representation of code challenge method. // String returns string representation of code challenge method.
func (ccm CodeChallengeMethod) String() string { func (ccm CodeChallengeMethod) String() string {
if ccm.uid != "" { if ccm.codeChallengeMethod != "" {
return strings.ToUpper(ccm.uid) return strings.ToUpper(ccm.codeChallengeMethod)
} }
return common.Und return common.Und

View File

@ -1,6 +1,7 @@
package domain_test package domain_test
//nolint:gosec // support old clients //nolint:gosec // support old clients
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha1" "crypto/sha1"

View File

@ -11,14 +11,14 @@ import (
type ( type (
Config struct { Config struct {
Code ConfigCode `envPrefix:"CODE_"`
Database ConfigDatabase `envPrefix:"DATABASE_"`
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
JWT ConfigJWT `envPrefix:"JWT_"`
Server ConfigServer `envPrefix:"SERVER_"` Server ConfigServer `envPrefix:"SERVER_"`
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"` Database ConfigDatabase `envPrefix:"DATABASE_"`
Name string `env:"NAME" envDefault:"IndieAuth"` Name string `env:"NAME" envDefault:"IndieAuth"`
RunMode string `env:"RUN_MODE" envDefault:"dev"` RunMode string `env:"RUN_MODE" envDefault:"dev"`
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
JWT ConfigJWT `envPrefix:"JWT_"`
Code ConfigCode `envPrefix:"CODE_"`
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
} }
ConfigServer struct { ConfigServer struct {
@ -45,10 +45,10 @@ type (
} }
ConfigJWT struct { ConfigJWT struct {
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"` // 1h Algorithm string `env:"ALGORITHM" envDefault:"HS256"`
Algorithm string `env:"ALGORITHM" envDefault:"HS256"` // HS256
Secret string `env:"SECRET"` Secret string `env:"SECRET"`
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"` // 22 Expiry time.Duration `env:"EXPIRY" envDefault:"1h"`
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"`
} }
ConfigIndieAuth struct { ConfigIndieAuth struct {

View File

@ -15,6 +15,8 @@ type (
// Error describes the format of a typical IndieAuth error. // Error describes the format of a typical IndieAuth error.
//nolint:tagliatelle // RFC 6749 section 5.2 //nolint:tagliatelle // RFC 6749 section 5.2
Error struct { Error struct {
frame xerrors.Frame `json:"-"`
// A single error code. // A single error code.
Code ErrorCode `json:"error"` Code ErrorCode `json:"error"`
@ -31,25 +33,23 @@ type (
// authorization request. The exact value received from the // authorization request. The exact value received from the
// client. // client.
State string `json:"-"` State string `json:"-"`
frame xerrors.Frame `json:"-"`
} }
// ErrorCode represent error code described in RFC 6749. // ErrorCode represent error code described in RFC 6749.
ErrorCode struct { ErrorCode struct {
uid string errorCode string
} }
) )
var ( var (
// ErrorCodeUnd describes an unrecognized error code. // ErrorCodeUnd describes an unrecognized error code.
ErrorCodeUnd = ErrorCode{uid: ""} // "und" ErrorCodeUnd = ErrorCode{errorCode: ""} // "und"
// ErrorCodeAccessDenied describes the access_denied error code. // ErrorCodeAccessDenied describes the access_denied error code.
// //
// RFC 6749 section 4.1.2.1: The resource owner or authorization server // RFC 6749 section 4.1.2.1: The resource owner or authorization server
// denied the request. // denied the request.
ErrorCodeAccessDenied = ErrorCode{uid: "access_denied"} // "access_denied" ErrorCodeAccessDenied = ErrorCode{errorCode: "access_denied"} // "access_denied"
// ErrorCodeInvalidClient describes the invalid_client error code. // ErrorCodeInvalidClient describes the invalid_client error code.
// //
@ -65,7 +65,7 @@ var (
// HTTP 401 (Unauthorized) status code and include the // HTTP 401 (Unauthorized) status code and include the
// "WWW-Authenticate" response header field matching the authentication // "WWW-Authenticate" response header field matching the authentication
// scheme used by the client. // scheme used by the client.
ErrorCodeInvalidClient = ErrorCode{uid: "invalid_client"} // "invalid_client" ErrorCodeInvalidClient = ErrorCode{errorCode: "invalid_client"} // "invalid_client"
// ErrorCodeInvalidGrant describes the invalid_grant error code. // ErrorCodeInvalidGrant describes the invalid_grant error code.
// //
@ -73,7 +73,7 @@ var (
// authorization code, resource owner credentials) or refresh token is // authorization code, resource owner credentials) or refresh token is
// invalid, expired, revoked, does not match the redirection URI used in // invalid, expired, revoked, does not match the redirection URI used in
// the authorization request, or was issued to another client. // the authorization request, or was issued to another client.
ErrorCodeInvalidGrant = ErrorCode{uid: "invalid_grant"} // "invalid_grant" ErrorCodeInvalidGrant = ErrorCode{errorCode: "invalid_grant"} // "invalid_grant"
// ErrorCodeInvalidRequest describes the invalid_request error code. // ErrorCodeInvalidRequest describes the invalid_request error code.
// //
@ -88,7 +88,7 @@ var (
// repeats a parameter, includes multiple credentials, utilizes more // repeats a parameter, includes multiple credentials, utilizes more
// than one mechanism for authenticating the client, or is otherwise // than one mechanism for authenticating the client, or is otherwise
// malformed. // malformed.
ErrorCodeInvalidRequest = ErrorCode{uid: "invalid_request"} // "invalid_request" ErrorCodeInvalidRequest = ErrorCode{errorCode: "invalid_request"} // "invalid_request"
// ErrorCodeInvalidScope describes the invalid_scope error code. // ErrorCodeInvalidScope describes the invalid_scope error code.
// //
@ -97,7 +97,7 @@ var (
// //
// RFC 6749 section 5.2: The requested scope is invalid, unknown, // RFC 6749 section 5.2: The requested scope is invalid, unknown,
// malformed, or exceeds the scope granted by the resource owner. // malformed, or exceeds the scope granted by the resource owner.
ErrorCodeInvalidScope = ErrorCode{uid: "invalid_scope"} // "invalid_scope" ErrorCodeInvalidScope = ErrorCode{errorCode: "invalid_scope"} // "invalid_scope"
// ErrorCodeServerError describes the server_error error code. // ErrorCodeServerError describes the server_error error code.
// //
@ -105,7 +105,7 @@ var (
// unexpected condition that prevented it from fulfilling the request. // unexpected condition that prevented it from fulfilling the request.
// (This error code is needed because a 500 Internal Server Error HTTP // (This error code is needed because a 500 Internal Server Error HTTP
// status code cannot be returned to the client via an HTTP redirect.) // status code cannot be returned to the client via an HTTP redirect.)
ErrorCodeServerError = ErrorCode{uid: "server_error"} // "server_error" ErrorCodeServerError = ErrorCode{errorCode: "server_error"} // "server_error"
// ErrorCodeTemporarilyUnavailable describes the temporarily_unavailable error code. // ErrorCodeTemporarilyUnavailable describes the temporarily_unavailable error code.
// //
@ -114,7 +114,7 @@ var (
// maintenance of the server. (This error code is needed because a 503 // maintenance of the server. (This error code is needed because a 503
// Service Unavailable HTTP status code cannot be returned to the client // Service Unavailable HTTP status code cannot be returned to the client
// via an HTTP redirect.) // via an HTTP redirect.)
ErrorCodeTemporarilyUnavailable = ErrorCode{uid: "temporarily_unavailable"} // "temporarily_unavailable" ErrorCodeTemporarilyUnavailable = ErrorCode{errorCode: "temporarily_unavailable"} // "temporarily_unavailable"
// ErrorCodeUnauthorizedClient describes the unauthorized_client error code. // ErrorCodeUnauthorizedClient describes the unauthorized_client error code.
// //
@ -123,53 +123,53 @@ var (
// //
// RFC 6749 section 5.2: The authenticated client is not authorized to // RFC 6749 section 5.2: The authenticated client is not authorized to
// use this authorization grant type. // use this authorization grant type.
ErrorCodeUnauthorizedClient = ErrorCode{uid: "unauthorized_client"} // "unauthorized_client" ErrorCodeUnauthorizedClient = ErrorCode{errorCode: "unauthorized_client"} // "unauthorized_client"
// ErrorCodeUnsupportedGrantType describes the unsupported_grant_type error code. // ErrorCodeUnsupportedGrantType describes the unsupported_grant_type error code.
// //
// RFC 6749 section 5.2: The authorization grant type is not supported // RFC 6749 section 5.2: The authorization grant type is not supported
// by the authorization server. // by the authorization server.
ErrorCodeUnsupportedGrantType = ErrorCode{uid: "unsupported_grant_type"} // "unsupported_grant_type" ErrorCodeUnsupportedGrantType = ErrorCode{errorCode: "unsupported_grant_type"} // "unsupported_grant_type"
// ErrorCodeUnsupportedResponseType describes the unsupported_response_type error code. // ErrorCodeUnsupportedResponseType describes the unsupported_response_type error code.
// //
// RFC 6749 section 4.1.2.1: The authorization server does not support // RFC 6749 section 4.1.2.1: The authorization server does not support
// obtaining an authorization code using this method. // obtaining an authorization code using this method.
ErrorCodeUnsupportedResponseType = ErrorCode{uid: "unsupported_response_type"} // "unsupported_response_type" ErrorCodeUnsupportedResponseType = ErrorCode{errorCode: "unsupported_response_type"} // "unsupported_response_type"
// ErrorCodeInvalidToken describes the invalid_token error code. // ErrorCodeInvalidToken describes the invalid_token error code.
// //
// IndieAuth: The access token provided is expired, revoked, or invalid. // IndieAuth: The access token provided is expired, revoked, or invalid.
ErrorCodeInvalidToken = ErrorCode{uid: "invalid_token"} // "invalid_token" ErrorCodeInvalidToken = ErrorCode{errorCode: "invalid_token"} // "invalid_token"
// ErrorCodeInsufficientScope describes the insufficient_scope error code. // ErrorCodeInsufficientScope describes the insufficient_scope error code.
// //
// IndieAuth: The request requires higher privileges than provided. // IndieAuth: The request requires higher privileges than provided.
ErrorCodeInsufficientScope = ErrorCode{uid: "insufficient_scope"} // "insufficient_scope" ErrorCodeInsufficientScope = ErrorCode{errorCode: "insufficient_scope"} // "insufficient_scope"
) )
var ErrErrorCodeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown error code", "") var ErrErrorCodeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown error code", "")
//nolint:gochecknoglobals // maps cannot be constants //nolint:gochecknoglobals // maps cannot be constants
var uidsErrorCodes = map[string]ErrorCode{ var uidsErrorCodes = map[string]ErrorCode{
ErrorCodeAccessDenied.uid: ErrorCodeAccessDenied, ErrorCodeAccessDenied.errorCode: ErrorCodeAccessDenied,
ErrorCodeInsufficientScope.uid: ErrorCodeInsufficientScope, ErrorCodeInsufficientScope.errorCode: ErrorCodeInsufficientScope,
ErrorCodeInvalidClient.uid: ErrorCodeInvalidClient, ErrorCodeInvalidClient.errorCode: ErrorCodeInvalidClient,
ErrorCodeInvalidGrant.uid: ErrorCodeInvalidGrant, ErrorCodeInvalidGrant.errorCode: ErrorCodeInvalidGrant,
ErrorCodeInvalidRequest.uid: ErrorCodeInvalidRequest, ErrorCodeInvalidRequest.errorCode: ErrorCodeInvalidRequest,
ErrorCodeInvalidScope.uid: ErrorCodeInvalidScope, ErrorCodeInvalidScope.errorCode: ErrorCodeInvalidScope,
ErrorCodeInvalidToken.uid: ErrorCodeInvalidToken, ErrorCodeInvalidToken.errorCode: ErrorCodeInvalidToken,
ErrorCodeServerError.uid: ErrorCodeServerError, ErrorCodeServerError.errorCode: ErrorCodeServerError,
ErrorCodeTemporarilyUnavailable.uid: ErrorCodeTemporarilyUnavailable, ErrorCodeTemporarilyUnavailable.errorCode: ErrorCodeTemporarilyUnavailable,
ErrorCodeUnauthorizedClient.uid: ErrorCodeUnauthorizedClient, ErrorCodeUnauthorizedClient.errorCode: ErrorCodeUnauthorizedClient,
ErrorCodeUnsupportedGrantType.uid: ErrorCodeUnsupportedGrantType, ErrorCodeUnsupportedGrantType.errorCode: ErrorCodeUnsupportedGrantType,
ErrorCodeUnsupportedResponseType.uid: ErrorCodeUnsupportedResponseType, ErrorCodeUnsupportedResponseType.errorCode: ErrorCodeUnsupportedResponseType,
} }
// String returns a string representation of the error code. // String returns a string representation of the error code.
func (ec ErrorCode) String() string { func (ec ErrorCode) String() string {
if ec.uid != "" { if ec.errorCode != "" {
return ec.uid return ec.errorCode
} }
return common.Und return common.Und
@ -193,7 +193,7 @@ func (ec *ErrorCode) UnmarshalForm(v []byte) error {
// MarshalJSON encodes the error code into its string representation in JSON. // MarshalJSON encodes the error code into its string representation in JSON.
func (ec ErrorCode) MarshalJSON() ([]byte, error) { func (ec ErrorCode) MarshalJSON() ([]byte, error) {
return []byte(strconv.QuoteToASCII(ec.uid)), nil return []byte(strconv.QuoteToASCII(ec.errorCode)), nil
} }
// Error returns a string representation of the error, satisfying the error // Error returns a string representation of the error, satisfying the error
@ -231,6 +231,8 @@ func (e Error) SetReirectURI(u *url.URL) {
return return
} }
q := u.Query()
for key, val := range map[string]string{ for key, val := range map[string]string{
"error": e.Code.String(), "error": e.Code.String(),
"error_description": e.Description, "error_description": e.Description,
@ -241,8 +243,10 @@ func (e Error) SetReirectURI(u *url.URL) {
continue continue
} }
u.Query().Set(key, val) q.Set(key, val)
} }
u.RawQuery = q.Encode()
} }
// NewError creates a new Error with the stack pointing to the function call // NewError creates a new Error with the stack pointing to the function call

View File

@ -13,17 +13,17 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type GrantType struct { type GrantType struct {
uid string grantType string
} }
//nolint:gochecknoglobals // structs cannot be constants //nolint:gochecknoglobals // structs cannot be constants
var ( var (
GrantTypeUnd = GrantType{uid: ""} // "und" GrantTypeUnd = GrantType{grantType: ""} // "und"
GrantTypeAuthorizationCode = GrantType{uid: "authorization_code"} // "authorization_code" GrantTypeAuthorizationCode = GrantType{grantType: "authorization_code"} // "authorization_code"
GrantTypeRefreshToken = GrantType{uid: "refresh_token"} // "refresh_token" GrantTypeRefreshToken = GrantType{grantType: "refresh_token"} // "refresh_token"
// TicketAuth extension. // TicketAuth extension.
GrantTypeTicket = GrantType{uid: "ticket"} GrantTypeTicket = GrantType{grantType: "ticket"}
) )
var ErrGrantTypeUnknown error = NewError( var ErrGrantTypeUnknown error = NewError(
@ -34,9 +34,9 @@ var ErrGrantTypeUnknown error = NewError(
//nolint:gochecknoglobals // maps cannot be constants //nolint:gochecknoglobals // maps cannot be constants
var uidsGrantTypes = map[string]GrantType{ var uidsGrantTypes = map[string]GrantType{
GrantTypeAuthorizationCode.uid: GrantTypeAuthorizationCode, GrantTypeAuthorizationCode.grantType: GrantTypeAuthorizationCode,
GrantTypeRefreshToken.uid: GrantTypeRefreshToken, GrantTypeRefreshToken.grantType: GrantTypeRefreshToken,
GrantTypeTicket.uid: GrantTypeTicket, GrantTypeTicket.grantType: GrantTypeTicket,
} }
// ParseGrantType parse grant_type value as GrantType struct enum. // ParseGrantType parse grant_type value as GrantType struct enum.
@ -78,13 +78,13 @@ func (gt *GrantType) UnmarshalJSON(v []byte) error {
} }
func (gt GrantType) MarshalJSON() ([]byte, error) { func (gt GrantType) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(gt.uid)), nil return []byte(strconv.Quote(gt.grantType)), nil
} }
// String returns string representation of grant type. // String returns string representation of grant type.
func (gt GrantType) String() string { func (gt GrantType) String() string {
if gt.uid != "" { if gt.grantType != "" {
return gt.uid return gt.grantType
} }
return common.Und return common.Und

View File

@ -11,14 +11,14 @@ import (
// Me is a URL user identifier. // Me is a URL user identifier.
type Me struct { type Me struct {
id *url.URL me *url.URL
} }
// ParseMe parse string as me URL identifier. // ParseMe parse string as me URL identifier.
// //
//nolint:funlen,cyclop //nolint:funlen,cyclop
func ParseMe(raw string) (*Me, error) { func ParseMe(raw string) (*Me, error) {
id, err := url.Parse(raw) me, err := url.Parse(raw)
if err != nil { if err != nil {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
@ -28,57 +28,57 @@ func ParseMe(raw string) (*Me, error) {
) )
} }
if id.Scheme != "http" && id.Scheme != "https" { if me.Scheme != "http" && me.Scheme != "https" {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile URL MUST have either an https or http scheme, got '"+id.Scheme+"'", "profile URL MUST have either an https or http scheme, got '"+me.Scheme+"'",
"https://indieauth.net/source/#user-profile-url", "https://indieauth.net/source/#user-profile-url",
"", "",
) )
} }
if id.Path == "" { if me.Path == "" {
id.Path = "/" me.Path = "/"
} }
if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") { if strings.Contains(me.Path, "/.") || strings.Contains(me.Path, "/..") {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+ "profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+
"or double-dot path segments, got '"+id.Path+"'", "or double-dot path segments, got '"+me.Path+"'",
"https://indieauth.net/source/#user-profile-url", "https://indieauth.net/source/#user-profile-url",
"", "",
) )
} }
if id.Fragment != "" { if me.Fragment != "" {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile URL MUST NOT contain a fragment component, got '"+id.Fragment+"'", "profile URL MUST NOT contain a fragment component, got '"+me.Fragment+"'",
"https://indieauth.net/source/#user-profile-url", "https://indieauth.net/source/#user-profile-url",
"", "",
) )
} }
if id.User != nil { if me.User != nil {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile URL MUST NOT contain a username or password component, got '"+id.User.String()+"'", "profile URL MUST NOT contain a username or password component, got '"+me.User.String()+"'",
"https://indieauth.net/source/#user-profile-url", "https://indieauth.net/source/#user-profile-url",
"", "",
) )
} }
if id.Host == "" { if me.Host == "" {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile host name MUST be a domain name, got '"+id.Host+"'", "profile host name MUST be a domain name, got '"+me.Host+"'",
"https://indieauth.net/source/#user-profile-url", "https://indieauth.net/source/#user-profile-url",
"", "",
) )
} }
if _, port, _ := net.SplitHostPort(id.Host); port != "" { if _, port, _ := net.SplitHostPort(me.Host); port != "" {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile MUST NOT contain a port, got '"+port+"'", "profile MUST NOT contain a port, got '"+port+"'",
@ -87,7 +87,7 @@ func ParseMe(raw string) (*Me, error) {
) )
} }
if out := net.ParseIP(id.Host); out != nil { if out := net.ParseIP(me.Host); out != nil {
return nil, NewError( return nil, NewError(
ErrorCodeInvalidRequest, ErrorCodeInvalidRequest,
"profile MUST NOT be ipv4 or ipv6 addresses, got '"+out.String()+"'", "profile MUST NOT be ipv4 or ipv6 addresses, got '"+out.String()+"'",
@ -96,7 +96,7 @@ func ParseMe(raw string) (*Me, error) {
) )
} }
return &Me{id: id}, nil return &Me{me: me}, nil
} }
// TestMe returns valid random generated me for tests. // TestMe returns valid random generated me for tests.
@ -108,7 +108,7 @@ func TestMe(tb testing.TB, src string) *Me {
tb.Fatal(err) tb.Fatal(err)
} }
return &Me{id: u} return &Me{me: u}
} }
// UnmarshalForm implements custom unmarshler for form values. // UnmarshalForm implements custom unmarshler for form values.
@ -147,19 +147,19 @@ func (m Me) MarshalJSON() ([]byte, error) {
// URL returns copy of parsed me in *url.URL representation. // URL returns copy of parsed me in *url.URL representation.
func (m Me) URL() *url.URL { func (m Me) URL() *url.URL {
if m.id == nil { if m.me == nil {
return nil return nil
} }
out, _ := url.Parse(m.id.String()) out, _ := url.Parse(m.me.String())
return out return out
} }
// String returns string representation of me. // String returns string representation of me.
func (m Me) String() string { func (m Me) String() string {
if m.id != nil { if m.me != nil {
return m.id.String() return m.me.String()
} }
return "" return ""

View File

@ -11,23 +11,23 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type ResponseType struct { type ResponseType struct {
uid string responseType string
} }
//nolint:gochecknoglobals // structs cannot be constants //nolint:gochecknoglobals // structs cannot be constants
var ( var (
ResponseTypeUnd = ResponseType{uid: ""} // "und" ResponseTypeUnd = ResponseType{responseType: ""} // "und"
// Deprecated(toby3d): Only accept response_type=code requests, and for // Deprecated(toby3d): Only accept response_type=code requests, and for
// backwards-compatible support, treat response_type=id requests as // backwards-compatible support, treat response_type=id requests as
// response_type=code requests: // response_type=code requests:
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type // https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
ResponseTypeID = ResponseType{uid: "id"} // "id" ResponseTypeID = ResponseType{responseType: "id"} // "id"
// Indicates to the authorization server that an authorization code // Indicates to the authorization server that an authorization code
// should be returned as the response: // should be returned as the response:
// https://indieauth.net/source/#authorization-request-li-1 // https://indieauth.net/source/#authorization-request-li-1
ResponseTypeCode = ResponseType{uid: "code"} // "code" ResponseTypeCode = ResponseType{responseType: "code"} // "code"
) )
var ErrResponseTypeUnknown error = NewError( var ErrResponseTypeUnknown error = NewError(
@ -39,9 +39,9 @@ var ErrResponseTypeUnknown error = NewError(
// ParseResponseType parse string as response type struct enum. // ParseResponseType parse string as response type struct enum.
func ParseResponseType(uid string) (ResponseType, error) { func ParseResponseType(uid string) (ResponseType, error) {
switch strings.ToLower(uid) { switch strings.ToLower(uid) {
case ResponseTypeCode.uid: case ResponseTypeCode.responseType:
return ResponseTypeCode, nil return ResponseTypeCode, nil
case ResponseTypeID.uid: case ResponseTypeID.responseType:
return ResponseTypeID, nil return ResponseTypeID, nil
} }
@ -78,13 +78,13 @@ func (rt *ResponseType) UnmarshalJSON(v []byte) error {
} }
func (rt ResponseType) MarshalJSON() ([]byte, error) { func (rt ResponseType) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(rt.uid)), nil return []byte(strconv.Quote(rt.responseType)), nil
} }
// String returns string representation of response type. // String returns string representation of response type.
func (rt ResponseType) String() string { func (rt ResponseType) String() string {
if rt.uid != "" { if rt.responseType != "" {
return rt.uid return rt.responseType
} }
return common.Und return common.Und

View File

@ -13,35 +13,35 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type Scope struct { type Scope struct {
uid string scope string
} }
var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope") var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope")
//nolint:gochecknoglobals // structs cannot be constants //nolint:gochecknoglobals // structs cannot be constants
var ( var (
ScopeUnd = Scope{uid: ""} // "und" ScopeUnd = Scope{scope: ""} // "und"
// https://indieweb.org/scope#Micropub_Scopes // https://indieweb.org/scope#Micropub_Scopes
ScopeCreate = Scope{uid: "create"} // "create" ScopeCreate = Scope{scope: "create"} // "create"
ScopeDelete = Scope{uid: "delete"} // "delete" ScopeDelete = Scope{scope: "delete"} // "delete"
ScopeDraft = Scope{uid: "draft"} // "draft" ScopeDraft = Scope{scope: "draft"} // "draft"
ScopeMedia = Scope{uid: "media"} // "media" ScopeMedia = Scope{scope: "media"} // "media"
ScopeUndelete = Scope{uid: "undelete"} // "undelete" ScopeUndelete = Scope{scope: "undelete"} // "undelete"
ScopeUpdate = Scope{uid: "update"} // "update" ScopeUpdate = Scope{scope: "update"} // "update"
// https://indieweb.org/scope#Microsub_Scopes // https://indieweb.org/scope#Microsub_Scopes
ScopeBlock = Scope{uid: "block"} // "block" ScopeBlock = Scope{scope: "block"} // "block"
ScopeChannels = Scope{uid: "channels"} // "channels" ScopeChannels = Scope{scope: "channels"} // "channels"
ScopeFollow = Scope{uid: "follow"} // "follow" ScopeFollow = Scope{scope: "follow"} // "follow"
ScopeMute = Scope{uid: "mute"} // "mute" ScopeMute = Scope{scope: "mute"} // "mute"
ScopeRead = Scope{uid: "read"} // "read" ScopeRead = Scope{scope: "read"} // "read"
// This scope requests access to the user's default profile information // This scope requests access to the user's default profile information
// which include the following properties: name, photo, url. // which include the following properties: name, photo, url.
// //
// NOTE(toby3d): https://indieauth.net/source/#profile-information // NOTE(toby3d): https://indieauth.net/source/#profile-information
ScopeProfile = Scope{uid: "profile"} // "profile" ScopeProfile = Scope{scope: "profile"} // "profile"
// This scope requests access to the user's email address in the // This scope requests access to the user's email address in the
// following property: email. // following property: email.
@ -51,24 +51,24 @@ var (
// and must be requested along with the profile scope if desired. // and must be requested along with the profile scope if desired.
// //
// NOTE(toby3d): https://indieauth.net/source/#profile-information // NOTE(toby3d): https://indieauth.net/source/#profile-information
ScopeEmail = Scope{uid: "email"} // "email" ScopeEmail = Scope{scope: "email"} // "email"
) )
//nolint:gochecknoglobals // maps cannot be constants //nolint:gochecknoglobals // maps cannot be constants
var uidsScopes = map[string]Scope{ var uidsScopes = map[string]Scope{
ScopeBlock.uid: ScopeBlock, ScopeBlock.scope: ScopeBlock,
ScopeChannels.uid: ScopeChannels, ScopeChannels.scope: ScopeChannels,
ScopeCreate.uid: ScopeCreate, ScopeCreate.scope: ScopeCreate,
ScopeDelete.uid: ScopeDelete, ScopeDelete.scope: ScopeDelete,
ScopeDraft.uid: ScopeDraft, ScopeDraft.scope: ScopeDraft,
ScopeEmail.uid: ScopeEmail, ScopeEmail.scope: ScopeEmail,
ScopeFollow.uid: ScopeFollow, ScopeFollow.scope: ScopeFollow,
ScopeMedia.uid: ScopeMedia, ScopeMedia.scope: ScopeMedia,
ScopeMute.uid: ScopeMute, ScopeMute.scope: ScopeMute,
ScopeProfile.uid: ScopeProfile, ScopeProfile.scope: ScopeProfile,
ScopeRead.uid: ScopeRead, ScopeRead.scope: ScopeRead,
ScopeUndelete.uid: ScopeUndelete, ScopeUndelete.scope: ScopeUndelete,
ScopeUpdate.uid: ScopeUpdate, ScopeUpdate.scope: ScopeUpdate,
} }
// ParseScope parses scope slug into Scope domain. // ParseScope parses scope slug into Scope domain.
@ -97,13 +97,13 @@ func (s *Scope) UnmarshalJSON(v []byte) error {
} }
func (s Scope) MarshalJSON() ([]byte, error) { func (s Scope) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(s.uid)), nil return []byte(strconv.Quote(s.scope)), nil
} }
// String returns string representation of scope. // String returns string representation of scope.
func (s Scope) String() string { func (s Scope) String() string {
if s.uid != "" { if s.scope != "" {
return s.uid return s.scope
} }
return common.Und return common.Und

View File

@ -2,8 +2,9 @@ package http
//nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata //nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata
type MetadataResponse struct { type MetadataResponse struct {
// The server's issuer identifier. // URL of a page containing human-readable information that
Issuer string `json:"issuer"` // developers might need to know when using the server.
ServiceDocumentation string `json:"service_documentation,omitempty"`
// The Authorization Endpoint. // The Authorization Endpoint.
AuthorizationEndpoint string `json:"authorization_endpoint"` AuthorizationEndpoint string `json:"authorization_endpoint"`
@ -14,37 +15,36 @@ type MetadataResponse struct {
// The Introspection Endpoint. // The Introspection Endpoint.
IntrospectionEndpoint string `json:"introspection_endpoint"` IntrospectionEndpoint string `json:"introspection_endpoint"`
// JSON array containing a list of client authentication methods // The User Info Endpoint.
// supported by this introspection endpoint. UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
// The Revocation Endpoint. // The Revocation Endpoint.
RevocationEndpoint string `json:"revocation_endpoint,omitempty"` RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
// The server's issuer identifier.
Issuer string `json:"issuer"`
// JSON array containing the value "none". // JSON array containing the value "none".
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` //nolint:lll RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` //nolint:lll
// JSON array containing scope values supported by the
// IndieAuth server.
ScopesSupported []string `json:"scopes_supported,omitempty"`
// JSON array containing the response_type values supported. // JSON array containing the response_type values supported.
ResponseTypesSupported []string `json:"response_types_supported,omitempty"` ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
// JSON array containing grant type values supported. // JSON array containing grant type values supported.
GrantTypesSupported []string `json:"grant_types_supported,omitempty"` GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
// URL of a page containing human-readable information that // JSON array containing scope values supported by the
// developers might need to know when using the server. // IndieAuth server.
ServiceDocumentation string `json:"service_documentation,omitempty"` ScopesSupported []string `json:"scopes_supported,omitempty"`
// JSON array containing the methods supported for PKCE. // JSON array containing the methods supported for PKCE.
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
// JSON array containing a list of client authentication methods
// supported by this introspection endpoint.
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
// Boolean parameter indicating whether the authorization server // Boolean parameter indicating whether the authorization server
// provides the iss parameter. // provides the iss parameter.
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll
// The User Info Endpoint.
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
} }

View File

@ -18,25 +18,23 @@ import (
type ( type (
//nolint:tagliatelle,lll //nolint:tagliatelle,lll
Response struct { Response struct {
Issuer domain.URL `json:"issuer"` TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"` AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"` IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"` RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"` ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"` TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"` UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"` Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"` GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
// Extensions
TicketEndpoint domain.URL `json:"ticket_endpoint"`
Micropub domain.URL `json:"micropub"`
Microsub domain.URL `json:"microsub"`
} }
httpMetadataRepository struct { httpMetadataRepository struct {
@ -150,29 +148,16 @@ func (r Response) populate(dst *domain.Metadata) {
dst.TicketEndpoint = r.TicketEndpoint.URL dst.TicketEndpoint = r.TicketEndpoint.URL
dst.TokenEndpoint = r.TokenEndpoint.URL dst.TokenEndpoint = r.TokenEndpoint.URL
dst.UserinfoEndpoint = r.UserinfoEndpoint.URL dst.UserinfoEndpoint = r.UserinfoEndpoint.URL
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported,
r.RevocationEndpointAuthMethodsSupported...)
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, r.ResponseTypesSupported...)
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
r.IntrospectionEndpointAuthMethodsSupported...)
dst.GrantTypesSupported = append(dst.GrantTypesSupported, r.GrantTypesSupported...)
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported,
r.CodeChallengeMethodsSupported...)
for _, scope := range r.ScopesSupported { for _, scope := range r.ScopesSupported {
dst.ScopesSupported = append(dst.ScopesSupported, scope) dst.ScopesSupported = append(dst.ScopesSupported, scope)
} }
for _, method := range r.RevocationEndpointAuthMethodsSupported {
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported, method)
}
for _, responseType := range r.ResponseTypesSupported {
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, responseType)
}
for _, method := range r.IntrospectionEndpointAuthMethodsSupported {
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
method)
}
for _, grantType := range r.GrantTypesSupported {
dst.GrantTypesSupported = append(dst.GrantTypesSupported, grantType)
}
for _, method := range r.CodeChallengeMethodsSupported {
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported, method)
}
} }

View File

@ -19,22 +19,22 @@ import (
//nolint:lll,tagliatelle //nolint:lll,tagliatelle
type Response struct { type Response struct {
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"` AuthorizationEndpoint string `json:"authorization_endpoint"`
IntrospectionEndpoint string `json:"introspection_endpoint"` IntrospectionEndpoint string `json:"introspection_endpoint"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
ServiceDocumentation string `json:"service_documentation,omitempty"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
TicketEndpoint string `json:"ticket_endpoint"`
Micropub string `json:"micropub"`
Microsub string `json:"microsub"` Microsub string `json:"microsub"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
Micropub string `json:"micropub"`
Issuer string `json:"issuer"`
ServiceDocumentation string `json:"service_documentation,omitempty"`
TicketEndpoint string `json:"ticket_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
} }

View File

@ -23,7 +23,7 @@ const DefaultRealm string = "Restricted"
const basic string = "basic" const basic string = "basic"
//nolint: gochecknoglobals // nolint: gochecknoglobals
var DefaultBasicAuthConfig = BasicAuthConfig{ var DefaultBasicAuthConfig = BasicAuthConfig{
Skipper: DefaultSkipper, Skipper: DefaultSkipper,
Realm: DefaultRealm, Realm: DefaultRealm,

View File

@ -17,9 +17,8 @@ type (
// Skipper defines a function to skip middleware. // Skipper defines a function to skip middleware.
Skipper Skipper Skipper Skipper
// TokenLength is the length of the generated token. // ErrorHandler defines a function which is executed for returning custom errors.
TokenLength uint8 ErrorHandler CSRFErrorHandler
// Optional. Default value 32.
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that // TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that
// is used to extract token from the request. // is used to extract token from the request.
@ -52,6 +51,14 @@ type (
// Optional. Default value 86400 (24hr). // Optional. Default value 86400 (24hr).
CookieMaxAge int CookieMaxAge int
// Indicates SameSite mode of the CSRF cookie.
// Optional. Default value SameSiteDefaultMode.
CookieSameSite http.SameSite
// TokenLength is the length of the generated token.
// Optional. Default value 32.
TokenLength uint8
// Indicates if CSRF cookie is secure. // Indicates if CSRF cookie is secure.
// Optional. Default value false. // Optional. Default value false.
CookieSecure bool CookieSecure bool
@ -59,13 +66,6 @@ type (
// Indicates if CSRF cookie is HTTP only. // Indicates if CSRF cookie is HTTP only.
// Optional. Default value false. // Optional. Default value false.
CookieHTTPOnly bool CookieHTTPOnly bool
// Indicates SameSite mode of the CSRF cookie.
// Optional. Default value SameSiteDefaultMode.
CookieSameSite http.SameSite
// ErrorHandler defines a function which is executed for returning custom errors.
ErrorHandler CSRFErrorHandler
} }
// CSRFErrorHandler is a function which is executed for creating custom errors. // CSRFErrorHandler is a function which is executed for creating custom errors.

View File

@ -15,25 +15,6 @@ import (
type ( type (
// JWTConfig defines the config for JWT middleware. // JWTConfig defines the config for JWT middleware.
JWTConfig struct { JWTConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// BeforeFunc defines a function which is executed just before
// the middleware.
BeforeFunc BeforeFunc
// SuccessHandler defines a function which is executed for a
// valid token.
SuccessHandler JWTSuccessHandler
// ErrorHandler defines a function which is executed for an
// invalid token. It may be used to define a custom JWT error.
ErrorHandler JWTErrorHandler
// ErrorHandlerWithContext is almost identical to ErrorHandler,
// but it's passed the current context.
ErrorHandlerWithContext JWTErrorHandlerWithContext
// Signing key to validate token. // Signing key to validate token.
// This is one of the three options to provide a token // This is one of the three options to provide a token
// validation key. The order of precedence is a user-defined // validation key. The order of precedence is a user-defined
@ -52,49 +33,24 @@ type (
// provided. // provided.
SigningKeys map[string]any SigningKeys map[string]any
// Signing method used to check the token's signing algorithm. // SuccessHandler defines a function which is executed for a
// // valid token.
// Optional. Default value HS256. SuccessHandler JWTSuccessHandler
SigningMethod jwa.SignatureAlgorithm
// Context key to store user information from the token into // ErrorHandler defines a function which is executed for an
// context. // invalid token. It may be used to define a custom JWT error.
// ErrorHandler JWTErrorHandler
// Optional. Default value "user".
ContextKey string
// Claims are extendable claims data defining token content. // ErrorHandlerWithContext is almost identical to ErrorHandler,
// Used by default ParseTokenFunc implementation. Not used if // but it's passed the current context.
// custom ParseTokenFunc is set. ErrorHandlerWithContext JWTErrorHandlerWithContext
//
// Optional. Default value []jwt.ClaimPair
Claims []jwt.ClaimPair
// TokenLookup is a string in the form of "<source>:<name>" or // BeforeFunc defines a function which is executed just before
// "<source>:<name>,<source>:<name>" that is used to extract // the middleware.
// token from the request. BeforeFunc BeforeFunc
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "cookie:<name>"
// - "form:<name>"
// Multiply sources example:
// - "header: Authorization,cookie: myowncookie"
TokenLookup string
// TokenLookupFuncs defines a list of user-defined functions // Skipper defines a function to skip middleware.
// that extract JWT token from the given context. Skipper Skipper
// This is one of the two options to provide a token extractor.
// The order of precedence is user-defined TokenLookupFuncs, and
// TokenLookup.
// You can also provide both if you want.
TokenLookupFuncs []ValuesExtractor
// AuthScheme to be used in the Authorization header.
//
// Optional. Default value "Bearer".
AuthScheme string
// KeyFunc defines a user-defined function that supplies the // KeyFunc defines a user-defined function that supplies the
// public key for a token validation. The function shall take // public key for a token validation. The function shall take
@ -119,6 +75,50 @@ type (
// using `github.com/golang-jwt/jwt` as JWT implementation library // using `github.com/golang-jwt/jwt` as JWT implementation library
ParseTokenFunc func(auth []byte, w http.ResponseWriter, r *http.Request) (any, error) ParseTokenFunc func(auth []byte, w http.ResponseWriter, r *http.Request) (any, error)
// Context key to store user information from the token into
// context.
//
// Optional. Default value "user".
ContextKey string
// TokenLookup is a string in the form of "<source>:<name>" or
// "<source>:<name>,<source>:<name>" that is used to extract
// token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "cookie:<name>"
// - "form:<name>"
// Multiply sources example:
// - "header: Authorization,cookie: myowncookie"
TokenLookup string
// AuthScheme to be used in the Authorization header.
//
// Optional. Default value "Bearer".
AuthScheme string
// Signing method used to check the token's signing algorithm.
//
// Optional. Default value HS256.
SigningMethod jwa.SignatureAlgorithm
// Claims are extendable claims data defining token content.
// Used by default ParseTokenFunc implementation. Not used if
// custom ParseTokenFunc is set.
//
// Optional. Default value []jwt.ClaimPair
Claims []jwt.ClaimPair
// TokenLookupFuncs defines a list of user-defined functions
// that extract JWT token from the given context.
// This is one of the two options to provide a token extractor.
// The order of precedence is user-defined TokenLookupFuncs, and
// TokenLookup.
// You can also provide both if you want.
TokenLookupFuncs []ValuesExtractor
// ContinueOnIgnoredError allows the next middleware/handler to // ContinueOnIgnoredError allows the next middleware/handler to
// be called when ErrorHandlerWithContext decides to ignore the // be called when ErrorHandlerWithContext decides to ignore the
// error (by returning `nil`). This is useful when parts of your // error (by returning `nil`). This is useful when parts of your

View File

@ -17,9 +17,9 @@ type (
} }
memorySessionRepository struct { memorySessionRepository struct {
config domain.Config
mutex *sync.RWMutex mutex *sync.RWMutex
sessions map[string]Session sessions map[string]Session
config domain.Config
} }
) )

View File

@ -17,9 +17,9 @@ import (
type ( type (
Session struct { Session struct {
CreatedAt sql.NullTime `db:"created_at"`
Code string `db:"code"` Code string `db:"code"`
Data string `db:"data"` Data string `db:"data"`
CreatedAt sql.NullTime `db:"created_at"`
} }
sqlite3SessionRepository struct { sqlite3SessionRepository struct {

View File

@ -12,7 +12,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/testing/sqltest" "source.toby3d.me/toby3d/auth/internal/testing/sqltest"
) )
// nolint: gochecknoglobals // slices cannot be contants //nolint: gochecknoglobals // slices cannot be contants
var tableColumns = []string{"created_at", "code", "data"} var tableColumns = []string{"created_at", "code", "data"}
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {

View File

@ -19,7 +19,7 @@ func New(tb testing.TB) (*bolt.DB, func()) {
filePath := tempFile.Name() filePath := tempFile.Name()
if err := tempFile.Close(); err != nil { if err = tempFile.Close(); err != nil {
tb.Fatal(err) tb.Fatal(err)
} }

View File

@ -19,9 +19,9 @@ import (
) )
type Handler struct { type Handler struct {
config domain.Config
matcher language.Matcher matcher language.Matcher
tickets ticket.UseCase tickets ticket.UseCase
config domain.Config
} }
func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler { func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler {
@ -36,7 +36,7 @@ func (h *Handler) Handler() http.Handler {
//nolint:exhaustivestruct //nolint:exhaustivestruct
chain := middleware.Chain{ chain := middleware.Chain{
middleware.CSRFWithConfig(middleware.CSRFConfig{ middleware.CSRFWithConfig(middleware.CSRFConfig{
Skipper: func(w http.ResponseWriter, r *http.Request) bool { Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
head, _ := urlutil.ShiftPath(r.URL.Path) head, _ := urlutil.ShiftPath(r.URL.Path)
return r.Method == http.MethodPost && head == "ticket" return r.Method == http.MethodPost && head == "ticket"

View File

@ -16,9 +16,9 @@ type (
} }
memoryTicketRepository struct { memoryTicketRepository struct {
config domain.Config
mutex *sync.RWMutex mutex *sync.RWMutex
tickets map[string]Ticket tickets map[string]Ticket
config domain.Config
} }
) )
@ -65,7 +65,7 @@ func (repo *memoryTicketRepository) GC() {
defer ticker.Stop() defer ticker.Stop()
for ts := range ticker.C { for ts := range ticker.C {
ts := ts.UTC() ts = ts.UTC()
repo.mutex.RLock() repo.mutex.RLock()

View File

@ -16,10 +16,10 @@ import (
type ( type (
Ticket struct { Ticket struct {
CreatedAt sql.NullTime `db:"created_at"`
Resource string `db:"resource"` Resource string `db:"resource"`
Subject string `db:"subject"` Subject string `db:"subject"`
Ticket string `db:"ticket"` Ticket string `db:"ticket"`
CreatedAt sql.NullTime `db:"created_at"`
} }
sqlite3TicketRepository struct { sqlite3TicketRepository struct {

View File

@ -12,7 +12,7 @@ import (
repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3" repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3"
) )
//nolint: gochecknoglobals // slices cannot be contants // nolint: gochecknoglobals // slices cannot be contants
var tableColumns = []string{"created_at", "resource", "subject", "ticket"} var tableColumns = []string{"created_at", "resource", "subject", "ticket"}
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {

View File

@ -18,18 +18,18 @@ type (
} }
TokenRefreshRequest struct { TokenRefreshRequest struct {
GrantType domain.GrantType `form:"grant_type"` // refresh_token
// The client ID that was used when the refresh token was issued. // The client ID that was used when the refresh token was issued.
ClientID domain.ClientID `form:"client_id"` ClientID domain.ClientID `form:"client_id"`
GrantType domain.GrantType `form:"grant_type"` // refresh_token
// The refresh token previously offered to the client.
RefreshToken string `form:"refresh_token"`
// The client may request a token with the same or fewer scopes // The client may request a token with the same or fewer scopes
// than the original access token. If omitted, is treated as // than the original access token. If omitted, is treated as
// equal to the original scopes granted. // equal to the original scopes granted.
Scope domain.Scopes `form:"scope"` Scope domain.Scopes `form:"scope"`
// The refresh token previously offered to the client.
RefreshToken string `form:"refresh_token"`
} }
TokenRevocationRequest struct { TokenRevocationRequest struct {

View File

@ -16,11 +16,11 @@ import (
type ( type (
Token struct { Token struct {
CreatedAt sql.NullTime `db:"created_at"`
AccessToken string `db:"access_token"` AccessToken string `db:"access_token"`
ClientID string `db:"client_id"` ClientID string `db:"client_id"`
Me string `db:"me"` Me string `db:"me"`
Scope string `db:"scope"` Scope string `db:"scope"`
CreatedAt sql.NullTime `db:"created_at"`
} }
sqlite3TokenRepository struct { sqlite3TokenRepository struct {

View File

@ -58,6 +58,7 @@ func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
// WARN(toby3d): If the token is not valid, the endpoint still // WARN(toby3d): If the token is not valid, the endpoint still
// MUST return a 200 Response. // MUST return a 200 Response.
_ = encoder.Encode(err) //nolint:errchkjson _ = encoder.Encode(err) //nolint:errchkjson
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return return
@ -70,34 +71,15 @@ func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
"token with 'profile' scope is required to view profile data", "token with 'profile' scope is required to view profile data",
"https://indieauth.net/source/#user-information", "https://indieauth.net/source/#user-information",
)) ))
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
return return
} }
resp := new(UserInformationResponse) //nolint:errchkjson
if userInfo == nil { _ = encoder.Encode(NewUserInformationResponse(userInfo,
_ = encoder.Encode(resp) //nolint:errchkjson userInfo.HasEmail() && tkn.Scope.Has(domain.ScopeEmail)))
return
}
if userInfo.HasName() {
resp.Name = userInfo.GetName()
}
if userInfo.HasURL() {
resp.URL = userInfo.GetURL().String()
}
if userInfo.HasPhoto() {
resp.Photo = userInfo.GetPhoto().String()
}
if tkn.Scope.Has(domain.ScopeEmail) && userInfo.HasEmail() {
resp.Email = userInfo.GetEmail().String()
}
_ = encoder.Encode(resp) //nolint:errchkjson
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }

View File

@ -1,8 +1,36 @@
package http package http
import "source.toby3d.me/toby3d/auth/internal/domain"
type UserInformationResponse struct { type UserInformationResponse struct {
Name string `json:"name,omitempty"` URL *domain.URL `json:"url,omitempty"`
URL string `json:"url,omitempty"` Photo *domain.URL `json:"photo,omitempty"`
Photo string `json:"photo,omitempty"` Email *domain.Email `json:"email,omitempty"`
Email string `json:"email,omitempty"` Name string `json:"name,omitempty"`
}
func NewUserInformationResponse(in *domain.Profile, hasEmail bool) *UserInformationResponse {
out := new(UserInformationResponse)
if in == nil {
return out
}
if in.HasName() {
out.Name = in.GetName()
}
if in.HasURL() {
out.URL = &domain.URL{URL: in.GetURL()}
}
if in.HasPhoto() {
out.Photo = &domain.URL{URL: in.GetPhoto()}
}
if hasEmail {
out.Email = in.GetEmail()
}
return out
} }

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/google/go-cmp/cmp"
"source.toby3d.me/toby3d/auth/internal/common" "source.toby3d.me/toby3d/auth/internal/common"
"source.toby3d.me/toby3d/auth/internal/domain" "source.toby3d.me/toby3d/auth/internal/domain"
@ -57,14 +58,15 @@ func TestUserInfo(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if result.Name != deps.profile.GetName() || exp := &delivery.UserInformationResponse{
result.Photo != deps.profile.GetPhoto().String() { Name: deps.profile.GetName(),
t.Errorf("GET /userinfo = %+v, want %+v", result, &delivery.UserInformationResponse{ URL: &domain.URL{URL: deps.profile.GetURL()},
Name: deps.profile.GetName(), Photo: &domain.URL{URL: deps.profile.GetPhoto()},
URL: deps.profile.GetURL().String(), Email: deps.profile.GetEmail(),
Photo: deps.profile.GetPhoto().String(), }
Email: deps.profile.GetEmail().String(),
}) if diff := cmp.Diff(result, exp, cmp.AllowUnexported(domain.URL{}, domain.Email{})); diff != "" {
t.Errorf("%s %s = %+v, want %+v", req.Method, req.RequestURI, result, exp)
} }
} }

51
main.go
View File

@ -16,13 +16,14 @@ import (
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
@ -106,8 +107,8 @@ var (
//nolint:gochecknoglobals //nolint:gochecknoglobals
var cpuProfilePath, memProfilePath string var cpuProfilePath, memProfilePath string
//go:embed assets/* //go:embed web/static/*
var staticFS embed.FS var static embed.FS
//nolint:gochecknoinits //nolint:gochecknoinits
func init() { func init() {
@ -116,11 +117,9 @@ func init() {
flag.Parse() flag.Parse()
if err := env.Parse(config, env.Options{ if err := env.Parse(config, env.Options{
Environment: nil, Prefix: "AUTH_",
OnSet: nil, TagName: "env",
Prefix: "INDIEAUTH_", UseFieldNameByDefault: true,
RequiredIfNoDef: false,
TagName: "",
}); err != nil { }); err != nil {
logger.Fatalln(err) logger.Fatalln(err)
} }
@ -156,7 +155,7 @@ func main() {
var opts NewAppOptions var opts NewAppOptions
var err error var err error
if opts.Static, err = fs.Sub(staticFS, "assets"); err != nil { if opts.Static, err = fs.Sub(static, filepath.Join("web", "static")); err != nil {
logger.Fatalln(err) logger.Fatalln(err)
} }
@ -164,7 +163,7 @@ func main() {
case "sqlite3": case "sqlite3":
store, err := sqlx.Open("sqlite", config.Database.Path) store, err := sqlx.Open("sqlite", config.Database.Path)
if err != nil { if err != nil {
panic(err) logger.Fatalln(err)
} }
if err = store.Ping(); err != nil { if err = store.Ping(); err != nil {
@ -226,7 +225,7 @@ func main() {
logger.Printf("started at %s, available at %s", config.Server.GetAddress(), logger.Printf("started at %s, available at %s", config.Server.GetAddress(),
config.Server.GetRootURL()) config.Server.GetRootURL())
err := server.ListenAndServe() err = server.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) { if err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Fatalln("cannot listen and serve:", err) logger.Fatalln("cannot listen and serve:", err)
} }
@ -234,7 +233,7 @@ func main() {
<-done <-done
if err := server.Shutdown(ctx); err != nil { if err = server.Shutdown(ctx); err != nil {
logger.Fatalln("failed shutdown of server:", err) logger.Fatalln("failed shutdown of server:", err)
} }
@ -343,42 +342,42 @@ func (app *App) Handler() http.Handler {
}).Handler() }).Handler()
user := userhttpdelivery.NewHandler(app.tokens, config).Handler() user := userhttpdelivery.NewHandler(app.tokens, config).Handler()
ticket := tickethttpdelivery.NewHandler(app.tickets, app.matcher, *config).Handler() ticket := tickethttpdelivery.NewHandler(app.tickets, app.matcher, *config).Handler()
static := http.FileServer(http.FS(app.static)) staticHandler := http.FileServer(http.FS(app.static))
return http.HandlerFunc(middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var head string head, tail := urlutil.ShiftPath(r.URL.Path)
head, r.URL.Path = urlutil.ShiftPath(r.URL.Path)
switch head {
case "", "callback", "token", "introspect", "revocation":
if r.URL.Path != "/" {
r.URL = r.URL.JoinPath(head, r.URL.Path)
} else {
r.URL = r.URL.JoinPath(head)
}
}
switch head { switch head {
default: default:
static.ServeHTTP(w, r) staticHandler.ServeHTTP(w, r)
case "", "callback": case "", "callback":
client.ServeHTTP(w, r) client.ServeHTTP(w, r)
case "token", "introspect", "revocation": case "token", "introspect", "revocation":
token.ServeHTTP(w, r) token.ServeHTTP(w, r)
case ".well-known": case ".well-known":
r.URL.Path = tail
if head, _ = urlutil.ShiftPath(r.URL.Path); head == "oauth-authorization-server" { if head, _ = urlutil.ShiftPath(r.URL.Path); head == "oauth-authorization-server" {
metadata.ServeHTTP(w, r) metadata.ServeHTTP(w, r)
} else { } else {
http.NotFound(w, r) http.NotFound(w, r)
} }
case "authorize": case "authorize":
r.URL.Path = tail
auth.ServeHTTP(w, r) auth.ServeHTTP(w, r)
case "health": case "health":
r.URL.Path = tail
health.ServeHTTP(w, r) health.ServeHTTP(w, r)
case "userinfo": case "userinfo":
r.URL.Path = tail
user.ServeHTTP(w, r) user.ServeHTTP(w, r)
case "ticket": case "ticket":
r.URL.Path = tail
ticket.ServeHTTP(w, r) ticket.ServeHTTP(w, r)
} }
}) /*.Intercept(middleware.LogFmt())*/) }).Intercept(middleware.LogFmt()))
} }

View File

@ -1304,26 +1304,21 @@ func wrapRingBuffer(s *Reader) {
Last two bytes of ring-buffer are initialized to 0, so context calculation Last two bytes of ring-buffer are initialized to 0, so context calculation
could be done uniformly for the first two and all other positions. */ could be done uniformly for the first two and all other positions. */
func ensureRingBuffer(s *Reader) bool { func ensureRingBuffer(s *Reader) bool {
var old_ringbuffer []byte = s.ringbuffer var old_ringbuffer []byte
if s.ringbuffer_size == s.new_ringbuffer_size { if s.ringbuffer_size == s.new_ringbuffer_size {
return true return true
} }
spaceNeeded := int(s.new_ringbuffer_size) + int(kRingBufferWriteAheadSlack)
s.ringbuffer = make([]byte, uint(s.new_ringbuffer_size)+uint(kRingBufferWriteAheadSlack)) if len(s.ringbuffer) < spaceNeeded {
if s.ringbuffer == nil { old_ringbuffer = s.ringbuffer
/* Restore previous value. */ s.ringbuffer = make([]byte, spaceNeeded)
s.ringbuffer = old_ringbuffer
return false
} }
s.ringbuffer[s.new_ringbuffer_size-2] = 0 s.ringbuffer[s.new_ringbuffer_size-2] = 0
s.ringbuffer[s.new_ringbuffer_size-1] = 0 s.ringbuffer[s.new_ringbuffer_size-1] = 0
if !(old_ringbuffer == nil) { if old_ringbuffer != nil {
copy(s.ringbuffer, old_ringbuffer[:uint(s.pos)]) copy(s.ringbuffer, old_ringbuffer[:uint(s.pos)])
old_ringbuffer = nil
} }
s.ringbuffer_size = s.new_ringbuffer_size s.ringbuffer_size = s.new_ringbuffer_size

View File

@ -27,10 +27,16 @@ func NewReader(src io.Reader) *Reader {
} }
// Reset discards the Reader's state and makes it equivalent to the result of // Reset discards the Reader's state and makes it equivalent to the result of
// its original state from NewReader, but writing to src instead. // its original state from NewReader, but reading from src instead.
// This permits reusing a Reader rather than allocating a new one. // This permits reusing a Reader rather than allocating a new one.
// Error is always nil // Error is always nil
func (r *Reader) Reset(src io.Reader) error { func (r *Reader) Reset(src io.Reader) error {
if r.error_code < 0 {
// There was an unrecoverable error, leaving the Reader's state
// undefined. Clear out everything but the buffer.
*r = Reader{buf: r.buf}
}
decoderStateInit(r) decoderStateInit(r)
r.src = src r.src = src
if r.buf == nil { if r.buf == nil {

View File

@ -200,7 +200,6 @@ func decoderStateInit(s *Reader) bool {
s.block_type_trees = nil s.block_type_trees = nil
s.block_len_trees = nil s.block_len_trees = nil
s.ringbuffer = nil
s.ringbuffer_size = 0 s.ringbuffer_size = 0
s.new_ringbuffer_size = 0 s.new_ringbuffer_size = 0
s.ringbuffer_mask = 0 s.ringbuffer_mask = 0

View File

@ -397,6 +397,13 @@ func rTime(ra *rand.Rand, t reflect.StructField, v reflect.Value, tag string) er
// Generate time // Generate time
timeOutput := generate(ra, tag) timeOutput := generate(ra, tag)
// Check to see if timeOutput has monotonic clock reading
// if so, remove it. This is because time.Parse() does not
// support parsing the monotonic clock reading
if strings.Contains(timeOutput, " m=") {
timeOutput = strings.Split(timeOutput, " m=")[0]
}
// Check to see if they are passing in a format to parse the time // Check to see if they are passing in a format to parse the time
timeFormat, timeFormatOK := t.Tag.Lookup("format") timeFormat, timeFormatOK := t.Tag.Lookup("format")
if timeFormatOK { if timeFormatOK {

View File

@ -1,8 +1,8 @@
# env # env
[![Build Status](https://img.shields.io/github/workflow/status/caarlos0/env/build?style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build) [![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env) [![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v6) [![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v7)
A simple and zero-dependencies library to parse environment variables into structs. A simple and zero-dependencies library to parse environment variables into structs.
@ -11,7 +11,7 @@ A simple and zero-dependencies library to parse environment variables into struc
Get the module with: Get the module with:
```sh ```sh
go get github.com/caarlos0/env/v6 go get github.com/caarlos0/env/v7
``` ```
The usage looks like this: The usage looks like this:
@ -23,7 +23,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type config struct { type config struct {
@ -53,7 +53,14 @@ $ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s} {Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
``` ```
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**. ## Caveats
> **Warning**
>
> **This is important!**
- _Unexported fields_ are **ignored**
## Supported types and defaults ## Supported types and defaults
@ -80,11 +87,15 @@ Complete list:
- `encoding.TextUnmarshaler` - `encoding.TextUnmarshaler`
- `url.URL` - `url.URL`
Pointers, slices and slices of pointers of those types are also supported. Pointers, slices and slices of pointers, and maps of those types are also
supported.
You can also use/define a [custom parser func](#custom-parser-funcs) for any You can also use/define a [custom parser func](#custom-parser-funcs) for any
other type you want. other type you want.
You can also use custom keys and values in your maps, as long as you provide a
parser function for them.
If you set the `envDefault` tag for something, this value will be used in the If you set the `envDefault` tag for something, this value will be used in the
case of absence of it in the environment. case of absence of it in the environment.
@ -107,7 +118,7 @@ also accepts a `map[reflect.Type]env.ParserFunc`.
If you add a custom parser for, say `Foo`, it will also be used to parse If you add a custom parser for, say `Foo`, it will also be used to parse
`*Foo` and `[]Foo` types. `*Foo` and `[]Foo` types.
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v6) Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v7)
for more info. for more info.
### A note about `TextUnmarshaler` and `time.Time` ### A note about `TextUnmarshaler` and `time.Time`
@ -148,7 +159,7 @@ type config struct {
## Not Empty fields ## Not Empty fields
While `required` demands the environment variable to be check, it doesn't check its value. While `required` demands the environment variable to be set, it doesn't check its value.
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`). If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
Example: Example:
@ -185,7 +196,7 @@ package main
import ( import (
"fmt" "fmt"
"time" "time"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type config struct { type config struct {
@ -217,6 +228,46 @@ $ SECRET=/tmp/secret \
## Options ## Options
### Use field names as environment variables by default
If you don't want to set the `env` tag on every field, you can use the
`UseFieldNameByDefault` option.
It will use the field name as environment variable name.
Here's an example:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string // will use $USERNAME
Password string // will use $PASSWORD
UserFullName string // will use $USER_FULL_NAME
}
func main() {
cfg := &Config{}
opts := &env.Options{UseFieldNameByDefault: true}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### Environment ### Environment
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values` By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
@ -232,7 +283,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -251,7 +302,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -268,7 +319,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -285,7 +336,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -302,7 +353,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -318,7 +369,7 @@ type ComplexConfig struct {
func main() { func main() {
cfg := ComplexConfig{} cfg := ComplexConfig{}
if err := Parse(&cfg, Options{ if err := Parse(&cfg, Options{
Prefix: "T_", Prefix: "T_",
Environment: map[string]string{ Environment: map[string]string{
"T_FOO_HOME": "/foo", "T_FOO_HOME": "/foo",
@ -336,7 +387,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -352,7 +403,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -374,7 +425,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -391,7 +442,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -409,7 +460,7 @@ func main() {
} }
// Print the loaded data. // Print the loaded data.
fmt.Printf("%+v\n", cfg.envData) fmt.Printf("%+v\n", cfg)
} }
``` ```
@ -425,7 +476,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
) )
type Config struct { type Config struct {
@ -447,6 +498,54 @@ func main() {
} }
``` ```
## Error handling
You can handle the errors the library throws like so:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
var cfg Config
err := env.Parse(&cfg)
if e, ok := err.(*env.AggregateError); ok {
for _, er := range e.Errors {
switch v := er.(type) {
case env.ParseError:
// handle it
case env.NotStructPtrError:
// handle it
case env.NoParserError:
// handle it
case env.NoSupportedTagOptionError:
// handle it
default:
fmt.Printf("Unknown error type %v", v)
}
}
}
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
}
```
> **Info**
>
> If you want to check if an specific error is in the chain, you can also use
> `errors.Is()`.
## Stargazers over time ## Stargazers over time
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env) [![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)

View File

@ -2,7 +2,6 @@ package env
import ( import (
"encoding" "encoding"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -10,14 +9,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
) )
// nolint: gochecknoglobals // nolint: gochecknoglobals
var ( var (
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
// Struct to Parse.
ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct")
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{ defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
reflect.Bool: func(v string) (interface{}, error) { reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v) return strconv.ParseBool(v)
@ -79,14 +75,14 @@ func defaultTypeParsers() map[reflect.Type]ParserFunc {
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) { reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v) u, err := url.Parse(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse URL: %v", err) return nil, newParseValueError("unable to parse URL", err)
} }
return *u, nil return *u, nil
}, },
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) { reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v) s, err := time.ParseDuration(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse duration: %v", err) return nil, newParseValueError("unable to parse duration", err)
} }
return s, err return s, err
}, },
@ -107,15 +103,20 @@ type Options struct {
// TagName specifies another tagname to use rather than the default env. // TagName specifies another tagname to use rather than the default env.
TagName string TagName string
// RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault' // RequiredIfNoDef automatically sets all env as required if they do not
// declare 'envDefault'.
RequiredIfNoDef bool RequiredIfNoDef bool
// OnSet allows to run a function when a value is set // OnSet allows to run a function when a value is set.
OnSet OnSetFn OnSet OnSetFn
// Prefix define a prefix for each key // Prefix define a prefix for each key.
Prefix string Prefix string
// UseFieldNameByDefault defines whether or not env should use the field
// name by default if the `env` key is missing.
UseFieldNameByDefault bool
// Sets to true if we have already configured once. // Sets to true if we have already configured once.
configured bool configured bool
} }
@ -150,6 +151,7 @@ func configure(opts []Options) []Options {
if item.Prefix != "" { if item.Prefix != "" {
opt.Prefix = item.Prefix opt.Prefix = item.Prefix
} }
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
opt.RequiredIfNoDef = item.RequiredIfNoDef opt.RequiredIfNoDef = item.RequiredIfNoDef
} }
@ -183,11 +185,11 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
ptrRef := reflect.ValueOf(v) ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr { if ptrRef.Kind() != reflect.Ptr {
return ErrNotAStructPtr return newAggregateError(NotStructPtrError{})
} }
ref := ptrRef.Elem() ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct { if ref.Kind() != reflect.Struct {
return ErrNotAStructPtr return newAggregateError(NotStructPtrError{})
} }
parsers := defaultTypeParsers() parsers := defaultTypeParsers()
for k, v := range funcMap { for k, v := range funcMap {
@ -200,22 +202,22 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error { func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
refType := ref.Type() refType := ref.Type()
var agrErr aggregateError var agrErr AggregateError
for i := 0; i < refType.NumField(); i++ { for i := 0; i < refType.NumField(); i++ {
refField := ref.Field(i) refField := ref.Field(i)
refTypeField := refType.Field(i) refTypeField := refType.Field(i)
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil { if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
if val, ok := err.(aggregateError); ok { if val, ok := err.(AggregateError); ok {
agrErr.errors = append(agrErr.errors, val.errors...) agrErr.Errors = append(agrErr.Errors, val.Errors...)
} else { } else {
agrErr.errors = append(agrErr.errors, err) agrErr.Errors = append(agrErr.Errors, err)
} }
} }
} }
if len(agrErr.errors) == 0 { if len(agrErr.Errors) == 0 {
return nil return nil
} }
@ -226,7 +228,7 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
if !refField.CanSet() { if !refField.CanSet() {
return nil return nil
} }
if reflect.Ptr == refField.Kind() && refField.Elem().Kind() == reflect.Struct { if reflect.Ptr == refField.Kind() && !refField.IsNil() {
return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...) return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
} }
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" { if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
@ -248,6 +250,19 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
return nil return nil
} }
const underscore rune = '_'
func toEnvName(input string) string {
var output []rune
for i, c := range input {
if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
output = append(output, underscore)
}
output = append(output, unicode.ToUpper(c))
}
return string(output)
}
func get(field reflect.StructField, opts []Options) (val string, err error) { func get(field reflect.StructField, opts []Options) (val string, err error) {
var exists bool var exists bool
var isDefault bool var isDefault bool
@ -258,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
required := opts[0].RequiredIfNoDef required := opts[0].RequiredIfNoDef
prefix := opts[0].Prefix prefix := opts[0].Prefix
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts))) ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
if ownKey == "" && opts[0].UseFieldNameByDefault {
ownKey = toEnvName(field.Name)
}
key := prefix + ownKey key := prefix + ownKey
for _, tag := range tags { for _, tag := range tags {
switch tag { switch tag {
@ -272,7 +290,7 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
case "notEmpty": case "notEmpty":
notEmpty = true notEmpty = true
default: default:
return "", fmt.Errorf("tag option %q not supported", tag) return "", newNoSupportedTagOptionError(tag)
} }
} }
expand := strings.EqualFold(field.Tag.Get("envExpand"), "true") expand := strings.EqualFold(field.Tag.Get("envExpand"), "true")
@ -288,18 +306,18 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
} }
if required && !exists && len(ownKey) > 0 { if required && !exists && len(ownKey) > 0 {
return "", fmt.Errorf(`required environment variable %q is not set`, key) return "", newEnvVarIsNotSet(key)
} }
if notEmpty && val == "" { if notEmpty && val == "" {
return "", fmt.Errorf("environment variable %q should not be empty", key) return "", newEmptyEnvVarError(key)
} }
if loadFile && val != "" { if loadFile && val != "" {
filename := val filename := val
val, err = getFromFile(filename) val, err = getFromFile(filename)
if err != nil { if err != nil {
return "", fmt.Errorf(`could not load content of file "%s" from variable %s: %v`, filename, key, err) return "", newLoadFileContentError(filename, key, err)
} }
} }
@ -325,6 +343,8 @@ func getOr(key, defaultValue string, defExists bool, envs map[string]string) (st
switch { switch {
case (!exists || key == "") && defExists: case (!exists || key == "") && defExists:
return defaultValue, true, true return defaultValue, true, true
case exists && value == "" && defExists:
return defaultValue, true, true
case !exists: case !exists:
return "", false, false return "", false, false
} }
@ -369,8 +389,11 @@ func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[
return nil return nil
} }
if field.Kind() == reflect.Slice { switch field.Kind() {
case reflect.Slice:
return handleSlice(field, value, sf, funcMap) return handleSlice(field, value, sf, funcMap)
case reflect.Map:
return handleMap(field, value, sf, funcMap)
} }
return newNoParserError(sf) return newNoParserError(sf)
@ -417,6 +440,54 @@ func handleSlice(field reflect.Value, value string, sf reflect.StructField, func
return nil return nil
} }
func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
keyType := sf.Type.Key()
keyParserFunc, ok := funcMap[keyType]
if !ok {
keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
elemType := sf.Type.Elem()
elemParserFunc, ok := funcMap[elemType]
if !ok {
elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
separator := sf.Tag.Get("envSeparator")
if separator == "" {
separator = ","
}
result := reflect.MakeMap(sf.Type)
for _, part := range strings.Split(value, separator) {
pairs := strings.Split(part, ":")
if len(pairs) != 2 {
return newParseError(sf, fmt.Errorf(`%q should be in "key:value" format`, part))
}
key, err := keyParserFunc(pairs[0])
if err != nil {
return newParseError(sf, err)
}
elem, err := elemParserFunc(pairs[1])
if err != nil {
return newParseError(sf, err)
}
result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType))
}
field.Set(result)
return nil
}
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler { func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
if reflect.Ptr == field.Kind() { if reflect.Ptr == field.Kind() {
if field.IsNil() { if field.IsNil() {
@ -459,26 +530,6 @@ func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.Struct
return nil return nil
} }
func newParseError(sf reflect.StructField, err error) error {
return parseError{
sf: sf,
err: err,
}
}
type parseError struct {
sf reflect.StructField
err error
}
func (e parseError) Error() string {
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.sf.Name, e.sf.Type, e.err)
}
func newNoParserError(sf reflect.StructField) error {
return fmt.Errorf(`no parser found for field "%s" of type "%s"`, sf.Name, sf.Type)
}
func optsWithPrefix(field reflect.StructField, opts []Options) []Options { func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
subOpts := make([]Options, len(opts)) subOpts := make([]Options, len(opts))
copy(subOpts, opts) copy(subOpts, opts)
@ -487,18 +538,3 @@ func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
} }
return subOpts return subOpts
} }
type aggregateError struct {
errors []error
}
func (e aggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}

164
vendor/github.com/caarlos0/env/v7/error.go generated vendored Normal file
View File

@ -0,0 +1,164 @@
package env
import (
"fmt"
"reflect"
"strings"
)
// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually
// List of the available errors
// ParseError
// NotStructPtrError
// NoParserError
// NoSupportedTagOptionError
// EnvVarIsNotSetError
// EmptyEnvVarError
// LoadFileContentError
// ParseValueError
type AggregateError struct {
Errors []error
}
func newAggregateError(initErr error) error {
return AggregateError{
[]error{
initErr,
},
}
}
func (e AggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.Errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}
// Is conforms with errors.Is.
func (e AggregateError) Is(err error) bool {
for _, ie := range e.Errors {
if reflect.TypeOf(ie) == reflect.TypeOf(err) {
return true
}
}
return false
}
// The error occurs when it's impossible to convert the value for given type.
type ParseError struct {
Name string
Type reflect.Type
Err error
}
func newParseError(sf reflect.StructField, err error) error {
return ParseError{sf.Name, sf.Type, err}
}
func (e ParseError) Error() string {
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
}
// The error occurs when pass something that is not a pointer to a Struct to Parse
type NotStructPtrError struct{}
func (e NotStructPtrError) Error() string {
return "expected a pointer to a Struct"
}
// This error occurs when there is no parser provided for given type
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type NoParserError struct {
Name string
Type reflect.Type
}
func newNoParserError(sf reflect.StructField) error {
return NoParserError{sf.Name, sf.Type}
}
func (e NoParserError) Error() string {
return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
}
// This error occurs when the given tag is not supported
// In-built supported tags: "", "file", "required", "unset", "notEmpty", "envDefault", "envExpand", "envSeparator"
// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
type NoSupportedTagOptionError struct {
Tag string
}
func newNoSupportedTagOptionError(tag string) error {
return NoSupportedTagOptionError{tag}
}
func (e NoSupportedTagOptionError) Error() string {
return fmt.Sprintf("tag option %q not supported", e.Tag)
}
// This error occurs when the required variable is not set
// Read about required fields: https://github.com/caarlos0/env#required-fields
type EnvVarIsNotSetError struct {
Key string
}
func newEnvVarIsNotSet(key string) error {
return EnvVarIsNotSetError{key}
}
func (e EnvVarIsNotSetError) Error() string {
return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
}
// This error occurs when the variable which must be not empty is existing but has an empty value
// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
type EmptyEnvVarError struct {
Key string
}
func newEmptyEnvVarError(key string) error {
return EmptyEnvVarError{key}
}
func (e EmptyEnvVarError) Error() string {
return fmt.Sprintf("environment variable %q should not be empty", e.Key)
}
// This error occurs when it's impossible to load the value from the file
// Read about From file feature: https://github.com/caarlos0/env#from-file
type LoadFileContentError struct {
Filename string
Key string
Err error
}
func newLoadFileContentError(filename, key string, err error) error {
return LoadFileContentError{filename, key, err}
}
func (e LoadFileContentError) Error() string {
return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
}
// This error occurs when it's impossible to convert value using given parser
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type ParseValueError struct {
Msg string
Err error
}
func newParseValueError(message string, err error) error {
return ParseValueError{message, err}
}
func (e ParseValueError) Error() string {
return fmt.Sprintf("%s: %v", e.Msg, e.Err)
}

View File

@ -1,48 +1,82 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0] - 2020-01-03 ## [0.6.0] - 2023-01-30
[0.6.0]: https://github.com/go-logfmt/logfmt/compare/v0.5.1...v0.6.0
### Added
- NewDecoderSize by [@alexanderjophus]
## [0.5.1] - 2021-08-18
[0.5.1]: https://github.com/go-logfmt/logfmt/compare/v0.5.0...v0.5.1
### Changed ### Changed
- Update the `go.mod` file for Go 1.17 as described in the [Go 1.17 release
notes](https://golang.org/doc/go1.17#go-command)
## [0.5.0] - 2020-01-03
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
### Changed
- Remove the dependency on github.com/kr/logfmt by [@ChrisHines] - Remove the dependency on github.com/kr/logfmt by [@ChrisHines]
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines] - Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
## [0.4.0] - 2018-11-21 ## [0.4.0] - 2018-11-21
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
### Added ### Added
- Go module support by [@ChrisHines] - Go module support by [@ChrisHines]
- CHANGELOG by [@ChrisHines] - CHANGELOG by [@ChrisHines]
### Changed ### Changed
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines] - Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
- On panic while printing, attempt to print panic value by [@bboreham] - On panic while printing, attempt to print panic value by [@bboreham]
## [0.3.0] - 2016-11-15 ## [0.3.0] - 2016-11-15
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
### Added ### Added
- Pool buffers for quoted strings and byte slices by [@nussjustin] - Pool buffers for quoted strings and byte slices by [@nussjustin]
### Fixed ### Fixed
- Fuzz fix, quote invalid UTF-8 values by [@judwhite] - Fuzz fix, quote invalid UTF-8 values by [@judwhite]
## [0.2.0] - 2016-05-08 ## [0.2.0] - 2016-05-08
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
### Added ### Added
- Encoder.EncodeKeyvals by [@ChrisHines] - Encoder.EncodeKeyvals by [@ChrisHines]
## [0.1.0] - 2016-03-28 ## [0.1.0] - 2016-03-28
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
### Added ### Added
- Encoder by [@ChrisHines] - Encoder by [@ChrisHines]
- Decoder by [@ChrisHines] - Decoder by [@ChrisHines]
- MarshalKeyvals by [@ChrisHines] - MarshalKeyvals by [@ChrisHines]
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
[@ChrisHines]: https://github.com/ChrisHines [@ChrisHines]: https://github.com/ChrisHines
[@bboreham]: https://github.com/bboreham [@bboreham]: https://github.com/bboreham
[@judwhite]: https://github.com/judwhite [@judwhite]: https://github.com/judwhite
[@nussjustin]: https://github.com/nussjustin [@nussjustin]: https://github.com/nussjustin
[@alexanderjophus]: https://github.com/alexanderjophus

View File

@ -1,20 +1,25 @@
# logfmt
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logfmt/logfmt.svg)](https://pkg.go.dev/github.com/go-logfmt/logfmt) [![Go Reference](https://pkg.go.dev/badge/github.com/go-logfmt/logfmt.svg)](https://pkg.go.dev/github.com/go-logfmt/logfmt)
[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt) [![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt)
[![Github Actions](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml/badge.svg)](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml) [![Github Actions](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml/badge.svg)](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master) [![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=main)
# logfmt
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
format](https://brandur.org/logfmt). It provides an API similar to format][fmt]. It provides an API similar to [encoding/json][json] and
[encoding/json](http://golang.org/pkg/encoding/json/) and [encoding/xml][xml].
[encoding/xml](http://golang.org/pkg/encoding/xml/).
[fmt]: https://brandur.org/logfmt
[json]: https://pkg.go.dev/encoding/json
[xml]: https://pkg.go.dev/encoding/xml
The logfmt format was first documented by Brandur Leach in [this The logfmt format was first documented by Brandur Leach in [this
article](https://brandur.org/logfmt). The format has not been formally article][origin]. The format has not been formally standardized. The most
standardized. The most authoritative public specification to date has been the authoritative public specification to date has been the documentation of a Go
documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt) Language [package][parser] written by Blake Mizerany and Keith Rarick.
written by Blake Mizerany and Keith Rarick.
[origin]: https://brandur.org/logfmt
[parser]: https://pkg.go.dev/github.com/kr/logfmt
## Goals ## Goals
@ -30,4 +35,7 @@ standard as a goal.
## Versioning ## Versioning
Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'. This project publishes releases according to the Go language guidelines for
[developing and publishing modules][pub].
[pub]: https://go.dev/doc/modules/developing

View File

@ -29,6 +29,23 @@ func NewDecoder(r io.Reader) *Decoder {
return dec return dec
} }
// NewDecoderSize returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may read data from r beyond
// the logfmt records requested.
// The size argument specifies the size of the initial buffer that the
// Decoder will use to read records from r.
// If a log line is longer than the size argument, the Decoder will return
// a bufio.ErrTooLong error.
func NewDecoderSize(r io.Reader, size int) *Decoder {
scanner := bufio.NewScanner(r)
scanner.Buffer(make([]byte, 0, size), size)
dec := &Decoder{
s: scanner,
}
return dec
}
// ScanRecord advances the Decoder to the next record, which can then be // ScanRecord advances the Decoder to the next record, which can then be
// parsed with the ScanKeyval method. It returns false when decoding stops, // parsed with the ScanKeyval method. It returns false when decoding stops,
// either by reaching the end of the input or an error. After ScanRecord // either by reaching the end of the input or an error. After ScanRecord

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -294,7 +294,6 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
} }
offset = 0 offset = 0
cGain := 0
if d.chain < 100 { if d.chain < 100 {
for i := prevHead; tries > 0; tries-- { for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] { if wEnd == win[i+length] {
@ -322,10 +321,14 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
return return
} }
// Minimum gain to accept a match.
cGain := 4
// Some like it higher (CSV), some like it lower (JSON) // Some like it higher (CSV), some like it lower (JSON)
const baseCost = 6 const baseCost = 3
// Base is 4 bytes at with an additional cost. // Base is 4 bytes at with an additional cost.
// Matches must be better than this. // Matches must be better than this.
for i := prevHead; tries > 0; tries-- { for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] { if wEnd == win[i+length] {
n := matchLen(win[i:i+minMatchLook], wPos) n := matchLen(win[i:i+minMatchLook], wPos)
@ -333,7 +336,7 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
// Calculate gain. Estimate // Calculate gain. Estimate
newGain := d.h.bitLengthRaw(wPos[:n]) - int(offsetExtraBits[offsetCode(uint32(pos-i))]) - baseCost - int(lengthExtraBits[lengthCodes[(n-3)&255]]) newGain := d.h.bitLengthRaw(wPos[:n]) - int(offsetExtraBits[offsetCode(uint32(pos-i))]) - baseCost - int(lengthExtraBits[lengthCodes[(n-3)&255]])
//fmt.Println(n, "gain:", newGain, "prev:", cGain, "raw:", d.h.bitLengthRaw(wPos[:n])) //fmt.Println("gain:", newGain, "prev:", cGain, "raw:", d.h.bitLengthRaw(wPos[:n]), "this-len:", n, "prev-len:", length)
if newGain > cGain { if newGain > cGain {
length = n length = n
offset = pos - i offset = pos - i
@ -490,27 +493,103 @@ func (d *compressor) deflateLazy() {
} }
if prevLength >= minMatchLength && s.length <= prevLength { if prevLength >= minMatchLength && s.length <= prevLength {
// Check for better match at end... // No better match, but check for better match at end...
// //
// checkOff must be >=2 since we otherwise risk checking s.index // Skip forward a number of bytes.
// Offset of 2 seems to yield best results. // Offset of 2 seems to yield best results. 3 is sometimes better.
const checkOff = 2 const checkOff = 2
prevIndex := s.index - 1
if prevIndex+prevLength+checkOff < s.maxInsertIndex { // Check all, except full length
end := lookahead if prevLength < maxMatchLength-checkOff {
if lookahead > maxMatchLength { prevIndex := s.index - 1
end = maxMatchLength if prevIndex+prevLength < s.maxInsertIndex {
} end := lookahead
end += prevIndex if lookahead > maxMatchLength+checkOff {
idx := prevIndex + prevLength - (4 - checkOff) end = maxMatchLength + checkOff
h := hash4(d.window[idx:]) }
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength + (4 - checkOff) end += prevIndex
if ch2 > minIndex {
length := matchLen(d.window[prevIndex:end], d.window[ch2:]) // Hash at match end.
// It seems like a pure length metric is best. h := hash4(d.window[prevIndex+prevLength:])
if length > prevLength { ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength
prevLength = length if prevIndex-ch2 != prevOffset && ch2 > minIndex+checkOff {
prevOffset = prevIndex - ch2 length := matchLen(d.window[prevIndex+checkOff:end], d.window[ch2+checkOff:])
// It seems like a pure length metric is best.
if length > prevLength {
prevLength = length
prevOffset = prevIndex - ch2
// Extend back...
for i := checkOff - 1; i >= 0; i-- {
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i] {
// Emit tokens we "owe"
for j := 0; j <= i; j++ {
d.tokens.AddLiteral(d.window[prevIndex+j])
if d.tokens.n == maxFlateBlockTokens {
// The block includes the current character
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
return
}
d.tokens.Reset()
}
s.index++
if s.index < s.maxInsertIndex {
h := hash4(d.window[s.index:])
ch := s.hashHead[h]
s.chainHead = int(ch)
s.hashPrev[s.index&windowMask] = ch
s.hashHead[h] = uint32(s.index + s.hashOffset)
}
}
break
} else {
prevLength++
}
}
} else if false {
// Check one further ahead.
// Only rarely better, disabled for now.
prevIndex++
h := hash4(d.window[prevIndex+prevLength:])
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength
if prevIndex-ch2 != prevOffset && ch2 > minIndex+checkOff {
length := matchLen(d.window[prevIndex+checkOff:end], d.window[ch2+checkOff:])
// It seems like a pure length metric is best.
if length > prevLength+checkOff {
prevLength = length
prevOffset = prevIndex - ch2
prevIndex--
// Extend back...
for i := checkOff; i >= 0; i-- {
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i-1] {
// Emit tokens we "owe"
for j := 0; j <= i; j++ {
d.tokens.AddLiteral(d.window[prevIndex+j])
if d.tokens.n == maxFlateBlockTokens {
// The block includes the current character
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
return
}
d.tokens.Reset()
}
s.index++
if s.index < s.maxInsertIndex {
h := hash4(d.window[s.index:])
ch := s.hashHead[h]
s.chainHead = int(ch)
s.hashPrev[s.index&windowMask] = ch
s.hashHead[h] = uint32(s.index + s.hashOffset)
}
}
break
} else {
prevLength++
}
}
}
}
}
} }
} }
} }

View File

@ -1,3 +1,14 @@
This library is a toy proof-of-concept implementation of the
well-known Schonhage-Strassen method for multiplying integers.
It is not expected to have a real life usecase outside number
theory computations, nor is it expected to be used in any production
system.
If you are using it in your project, you may want to carefully
examine the actual requirement or problem you are trying to solve.
# Comparison with the standard library and GMP
Benchmarking math/big vs. bigfft Benchmarking math/big vs. bigfft
Number size old ns/op new ns/op delta Number size old ns/op new ns/op delta

View File

@ -1,36 +0,0 @@
// Trampolines to math/big assembly implementations.
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
JMP mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
TEXT ·subVV(SB),NOSPLIT,$0
JMP mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
JMP mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
TEXT ·subVW(SB),NOSPLIT,$0
JMP mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
JMP mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
JMP mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
JMP mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
JMP mathbig·addMulVVW(SB)

View File

@ -1,38 +0,0 @@
// Trampolines to math/big assembly implementations.
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
JMP mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
// (same as addVV except for SBBQ instead of ADCQ and label names)
TEXT ·subVV(SB),NOSPLIT,$0
JMP mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
JMP mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
TEXT ·subVW(SB),NOSPLIT,$0
JMP mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
JMP mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
JMP mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
JMP mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
JMP mathbig·addMulVVW(SB)

View File

@ -1,36 +0,0 @@
// Trampolines to math/big assembly implementations.
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
B mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
TEXT ·subVV(SB),NOSPLIT,$0
B mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
B mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
TEXT ·subVW(SB),NOSPLIT,$0
B mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
B mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
B mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
B mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
B mathbig·addMulVVW(SB)

View File

@ -1,36 +0,0 @@
// Trampolines to math/big assembly implementations.
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
B mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
TEXT ·subVV(SB),NOSPLIT,$0
B mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
B mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
TEXT ·subVW(SB),NOSPLIT,$0
B mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
B mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
B mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
B mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
B mathbig·addMulVVW(SB)

View File

@ -4,13 +4,30 @@
package bigfft package bigfft
import . "math/big" import (
"math/big"
_ "unsafe"
)
// implemented in arith_$GOARCH.s type Word = big.Word
//go:linkname addVV math/big.addVV
func addVV(z, x, y []Word) (c Word) func addVV(z, x, y []Word) (c Word)
//go:linkname subVV math/big.subVV
func subVV(z, x, y []Word) (c Word) func subVV(z, x, y []Word) (c Word)
//go:linkname addVW math/big.addVW
func addVW(z, x []Word, y Word) (c Word) func addVW(z, x []Word, y Word) (c Word)
//go:linkname subVW math/big.subVW
func subVW(z, x []Word, y Word) (c Word) func subVW(z, x []Word, y Word) (c Word)
//go:linkname shlVU math/big.shlVU
func shlVU(z, x []Word, s uint) (c Word) func shlVU(z, x []Word, s uint) (c Word)
//go:linkname mulAddVWW math/big.mulAddVWW
func mulAddVWW(z, x []Word, y, r Word) (c Word) func mulAddVWW(z, x []Word, y, r Word) (c Word)
//go:linkname addMulVVW math/big.addMulVVW
func addMulVVW(z, x []Word, y Word) (c Word) func addMulVVW(z, x []Word, y Word) (c Word)

View File

@ -1,40 +0,0 @@
// Trampolines to math/big assembly implementations.
// +build mips64 mips64le
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
JMP mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
// (same as addVV except for SBBQ instead of ADCQ and label names)
TEXT ·subVV(SB),NOSPLIT,$0
JMP mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
JMP mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
TEXT ·subVW(SB),NOSPLIT,$0
JMP mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
JMP mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
JMP mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
JMP mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
JMP mathbig·addMulVVW(SB)

View File

@ -1,40 +0,0 @@
// Trampolines to math/big assembly implementations.
// +build mips mipsle
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
JMP mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
// (same as addVV except for SBBQ instead of ADCQ and label names)
TEXT ·subVV(SB),NOSPLIT,$0
JMP mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
JMP mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
TEXT ·subVW(SB),NOSPLIT,$0
JMP mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
JMP mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
JMP mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
JMP mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
JMP mathbig·addMulVVW(SB)

View File

@ -1,38 +0,0 @@
// Trampolines to math/big assembly implementations.
// +build ppc64 ppc64le
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
BR mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
TEXT ·subVV(SB),NOSPLIT,$0
BR mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
BR mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
TEXT ·subVW(SB),NOSPLIT,$0
BR mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
BR mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
BR mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
BR mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
BR mathbig·addMulVVW(SB)

View File

@ -1,39 +0,0 @@
// Trampolines to math/big assembly implementations.
// +build riscv riscv64
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
JMP mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
// (same as addVV except for SBBQ instead of ADCQ and label names)
TEXT ·subVV(SB),NOSPLIT,$0
JMP mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
JMP mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
TEXT ·subVW(SB),NOSPLIT,$0
JMP mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
JMP mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
JMP mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
JMP mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
JMP mathbig·addMulVVW(SB)

View File

@ -1,37 +0,0 @@
// Trampolines to math/big assembly implementations.
#include "textflag.h"
// func addVV(z, x, y []Word) (c Word)
TEXT ·addVV(SB),NOSPLIT,$0
BR mathbig·addVV(SB)
// func subVV(z, x, y []Word) (c Word)
TEXT ·subVV(SB),NOSPLIT,$0
BR mathbig·subVV(SB)
// func addVW(z, x []Word, y Word) (c Word)
TEXT ·addVW(SB),NOSPLIT,$0
BR mathbig·addVW(SB)
// func subVW(z, x []Word, y Word) (c Word)
TEXT ·subVW(SB),NOSPLIT,$0
BR mathbig·subVW(SB)
// func shlVU(z, x []Word, s uint) (c Word)
TEXT ·shlVU(SB),NOSPLIT,$0
BR mathbig·shlVU(SB)
// func shrVU(z, x []Word, s uint) (c Word)
TEXT ·shrVU(SB),NOSPLIT,$0
BR mathbig·shrVU(SB)
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
TEXT ·mulAddVWW(SB),NOSPLIT,$0
BR mathbig·mulAddVWW(SB)
// func addMulVVW(z, x []Word, y Word) (c Word)
TEXT ·addMulVVW(SB),NOSPLIT,$0
BR mathbig·addMulVVW(SB)

View File

@ -1,4 +1,4 @@
# fasthttp [![GoDoc](https://godoc.org/github.com/valyala/fasthttp?status.svg)](http://godoc.org/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp) # fasthttp [![GoDoc](https://pkg.go.dev/badge/github.com/valyala/fasthttp)](https://pkg.go.dev/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)
![FastHTTP  Fastest and reliable HTTP implementation in Go](https://github.com/fasthttp/docs-assets/raw/master/banner@0.5.png) ![FastHTTP  Fastest and reliable HTTP implementation in Go](https://github.com/fasthttp/docs-assets/raw/master/banner@0.5.png)
@ -22,9 +22,9 @@ connections per physical server.
[Install](#install) [Install](#install)
[Documentation](https://godoc.org/github.com/valyala/fasthttp) [Documentation](https://pkg.go.dev/github.com/valyala/fasthttp)
[Examples from docs](https://godoc.org/github.com/valyala/fasthttp#pkg-examples) [Examples from docs](https://pkg.go.dev/github.com/valyala/fasthttp#pkg-examples)
[Code examples](examples) [Code examples](examples)
@ -40,7 +40,7 @@ connections per physical server.
[FAQ](#faq) [FAQ](#faq)
## HTTP server performance comparison with [net/http](https://golang.org/pkg/net/http/) ## HTTP server performance comparison with [net/http](https://pkg.go.dev/net/http)
In short, fasthttp server is up to 10 times faster than net/http. In short, fasthttp server is up to 10 times faster than net/http.
Below are benchmark results. Below are benchmark results.
@ -174,14 +174,14 @@ go get -u github.com/valyala/fasthttp
Unfortunately, fasthttp doesn't provide API identical to net/http. Unfortunately, fasthttp doesn't provide API identical to net/http.
See the [FAQ](#faq) for details. See the [FAQ](#faq) for details.
There is [net/http -> fasthttp handler converter](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor), There is [net/http -> fasthttp handler converter](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor),
but it is better to write fasthttp request handlers by hand in order to use but it is better to write fasthttp request handlers by hand in order to use
all of the fasthttp advantages (especially high performance :) ). all of the fasthttp advantages (especially high performance :) ).
Important points: Important points:
* Fasthttp works with [RequestHandler functions](https://godoc.org/github.com/valyala/fasthttp#RequestHandler) * Fasthttp works with [RequestHandler functions](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
instead of objects implementing [Handler interface](https://golang.org/pkg/net/http/#Handler). instead of objects implementing [Handler interface](https://pkg.go.dev/net/http#Handler).
Fortunately, it is easy to pass bound struct methods to fasthttp: Fortunately, it is easy to pass bound struct methods to fasthttp:
```go ```go
@ -211,8 +211,8 @@ Fortunately, it is easy to pass bound struct methods to fasthttp:
fasthttp.ListenAndServe(":8081", fastHTTPHandler) fasthttp.ListenAndServe(":8081", fastHTTPHandler)
``` ```
* The [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler) * The [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
accepts only one argument - [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx). accepts only one argument - [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx).
It contains all the functionality required for http request processing It contains all the functionality required for http request processing
and response writing. Below is an example of a simple request handler conversion and response writing. Below is an example of a simple request handler conversion
from net/http to fasthttp. from net/http to fasthttp.
@ -278,7 +278,7 @@ like in net/http. The following code is valid for fasthttp:
} }
``` ```
* Fasthttp doesn't provide [ServeMux](https://golang.org/pkg/net/http/#ServeMux), * Fasthttp doesn't provide [ServeMux](https://pkg.go.dev/net/http#ServeMux),
but there are more powerful third-party routers and web frameworks but there are more powerful third-party routers and web frameworks
with fasthttp support: with fasthttp support:
@ -347,78 +347,78 @@ with fasthttp support:
ctx *fasthttp.RequestCtx ctx *fasthttp.RequestCtx
) )
``` ```
* r.Body -> [ctx.PostBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody) * r.Body -> [ctx.PostBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody)
* r.URL.Path -> [ctx.Path()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Path) * r.URL.Path -> [ctx.Path()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Path)
* r.URL -> [ctx.URI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.URI) * r.URL -> [ctx.URI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.URI)
* r.Method -> [ctx.Method()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Method) * r.Method -> [ctx.Method()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Method)
* r.Header -> [ctx.Request.Header](https://godoc.org/github.com/valyala/fasthttp#RequestHeader) * r.Header -> [ctx.Request.Header](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader)
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Peek) * r.Header.Get() -> [ctx.Request.Header.Peek()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Peek)
* r.Host -> [ctx.Host()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Host) * r.Host -> [ctx.Host()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Host)
* r.Form -> [ctx.QueryArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.QueryArgs) + * r.Form -> [ctx.QueryArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
[ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs) [ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.PostForm -> [ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs) * r.PostForm -> [ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.FormValue() -> [ctx.FormValue()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormValue) * r.FormValue() -> [ctx.FormValue()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormValue)
* r.FormFile() -> [ctx.FormFile()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormFile) * r.FormFile() -> [ctx.FormFile()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormFile)
* r.MultipartForm -> [ctx.MultipartForm()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.MultipartForm) * r.MultipartForm -> [ctx.MultipartForm()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
* r.RemoteAddr -> [ctx.RemoteAddr()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RemoteAddr) * r.RemoteAddr -> [ctx.RemoteAddr()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
* r.RequestURI -> [ctx.RequestURI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RequestURI) * r.RequestURI -> [ctx.RequestURI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RequestURI)
* r.TLS -> [ctx.IsTLS()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.IsTLS) * r.TLS -> [ctx.IsTLS()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.IsTLS)
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Cookie) * r.Cookie() -> [ctx.Request.Header.Cookie()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Cookie)
* r.Referer() -> [ctx.Referer()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Referer) * r.Referer() -> [ctx.Referer()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Referer)
* r.UserAgent() -> [ctx.UserAgent()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.UserAgent) * r.UserAgent() -> [ctx.UserAgent()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.UserAgent)
* w.Header() -> [ctx.Response.Header](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader) * w.Header() -> [ctx.Response.Header](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader)
* w.Header().Set() -> [ctx.Response.Header.Set()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.Set) * w.Header().Set() -> [ctx.Response.Header.Set()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.Set)
* w.Header().Set("Content-Type") -> [ctx.SetContentType()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetContentType) * w.Header().Set("Content-Type") -> [ctx.SetContentType()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetContentType)
* w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.SetCookie) * w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
* w.Write() -> [ctx.Write()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Write), * w.Write() -> [ctx.Write()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Write),
[ctx.SetBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBody), [ctx.SetBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBody),
[ctx.SetBodyStream()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStream), [ctx.SetBodyStream()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
[ctx.SetBodyStreamWriter()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter) [ctx.SetBodyStreamWriter()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
* w.WriteHeader() -> [ctx.SetStatusCode()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetStatusCode) * w.WriteHeader() -> [ctx.SetStatusCode()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack) * w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)
* http.Error() -> [ctx.Error()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Error) * http.Error() -> [ctx.Error()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Error)
* http.FileServer() -> [fasthttp.FSHandler()](https://godoc.org/github.com/valyala/fasthttp#FSHandler), * http.FileServer() -> [fasthttp.FSHandler()](https://pkg.go.dev/github.com/valyala/fasthttp#FSHandler),
[fasthttp.FS](https://godoc.org/github.com/valyala/fasthttp#FS) [fasthttp.FS](https://pkg.go.dev/github.com/valyala/fasthttp#FS)
* http.ServeFile() -> [fasthttp.ServeFile()](https://godoc.org/github.com/valyala/fasthttp#ServeFile) * http.ServeFile() -> [fasthttp.ServeFile()](https://pkg.go.dev/github.com/valyala/fasthttp#ServeFile)
* http.Redirect() -> [ctx.Redirect()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Redirect) * http.Redirect() -> [ctx.Redirect()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Redirect)
* http.NotFound() -> [ctx.NotFound()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.NotFound) * http.NotFound() -> [ctx.NotFound()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.NotFound)
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://godoc.org/github.com/valyala/fasthttp#PathRewriteFunc) * http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://pkg.go.dev/github.com/valyala/fasthttp#PathRewriteFunc)
* *VERY IMPORTANT!* Fasthttp disallows holding references * *VERY IMPORTANT!* Fasthttp disallows holding references
to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) or to its' to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) or to its'
members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler). members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
Otherwise [data races](http://blog.golang.org/race-detector) are inevitable. Otherwise [data races](http://go.dev/blog/race-detector) are inevitable.
Carefully inspect all the net/http request handlers converted to fasthttp whether Carefully inspect all the net/http request handlers converted to fasthttp whether
they retain references to RequestCtx or to its' members after returning. they retain references to RequestCtx or to its' members after returning.
RequestCtx provides the following _band aids_ for this case: RequestCtx provides the following _band aids_ for this case:
* Wrap RequestHandler into [TimeoutHandler](https://godoc.org/github.com/valyala/fasthttp#TimeoutHandler). * Wrap RequestHandler into [TimeoutHandler](https://pkg.go.dev/github.com/valyala/fasthttp#TimeoutHandler).
* Call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError) * Call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from RequestHandler if there are references to RequestCtx or to its' members. before returning from RequestHandler if there are references to RequestCtx or to its' members.
See [the example](https://godoc.org/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError) See [the example](https://pkg.go.dev/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError)
for more details. for more details.
Use this brilliant tool - [race detector](http://blog.golang.org/race-detector) - Use this brilliant tool - [race detector](http://go.dev/blog/race-detector) -
for detecting and eliminating data races in your program. If you detected for detecting and eliminating data races in your program. If you detected
data race related to fasthttp in your program, then there is high probability data race related to fasthttp in your program, then there is high probability
you forgot calling [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError) you forgot calling [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler). before returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
* Blind switching from net/http to fasthttp won't give you performance boost. * Blind switching from net/http to fasthttp won't give you performance boost.
While fasthttp is optimized for speed, its' performance may be easily saturated While fasthttp is optimized for speed, its' performance may be easily saturated
by slow [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler). by slow [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
So [profile](http://blog.golang.org/profiling-go-programs) and optimize your So [profile](http://go.dev/blog/pprof) and optimize your
code after switching to fasthttp. For instance, use [quicktemplate](https://github.com/valyala/quicktemplate) code after switching to fasthttp. For instance, use [quicktemplate](https://github.com/valyala/quicktemplate)
instead of [html/template](https://golang.org/pkg/html/template/). instead of [html/template](https://pkg.go.dev/html/template).
* See also [fasthttputil](https://godoc.org/github.com/valyala/fasthttp/fasthttputil), * See also [fasthttputil](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttputil),
[fasthttpadaptor](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor) and [fasthttpadaptor](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor) and
[expvarhandler](https://godoc.org/github.com/valyala/fasthttp/expvarhandler). [expvarhandler](https://pkg.go.dev/github.com/valyala/fasthttp/expvarhandler).
## Performance optimization tips for multi-core systems ## Performance optimization tips for multi-core systems
* Use [reuseport](https://godoc.org/github.com/valyala/fasthttp/reuseport) listener. * Use [reuseport](https://pkg.go.dev/github.com/valyala/fasthttp/reuseport) listener.
* Run a separate server instance per CPU core with GOMAXPROCS=1. * Run a separate server instance per CPU core with GOMAXPROCS=1.
* Pin each server instance to a separate CPU core using [taskset](http://linux.die.net/man/1/taskset). * Pin each server instance to a separate CPU core using [taskset](http://linux.die.net/man/1/taskset).
* Ensure the interrupts of multiqueue network card are evenly distributed between CPU cores. * Ensure the interrupts of multiqueue network card are evenly distributed between CPU cores.
@ -430,21 +430,21 @@ instead of [html/template](https://golang.org/pkg/html/template/).
* Do not allocate objects and `[]byte` buffers - just reuse them as much * Do not allocate objects and `[]byte` buffers - just reuse them as much
as possible. Fasthttp API design encourages this. as possible. Fasthttp API design encourages this.
* [sync.Pool](https://golang.org/pkg/sync/#Pool) is your best friend. * [sync.Pool](https://pkg.go.dev/sync#Pool) is your best friend.
* [Profile your program](http://blog.golang.org/profiling-go-programs) * [Profile your program](http://go.dev/blog/pprof)
in production. in production.
`go tool pprof --alloc_objects your-program mem.pprof` usually gives better `go tool pprof --alloc_objects your-program mem.pprof` usually gives better
insights for optimization opportunities than `go tool pprof your-program cpu.pprof`. insights for optimization opportunities than `go tool pprof your-program cpu.pprof`.
* Write [tests and benchmarks](https://golang.org/pkg/testing/) for hot paths. * Write [tests and benchmarks](https://pkg.go.dev/testing) for hot paths.
* Avoid conversion between `[]byte` and `string`, since this may result in memory * Avoid conversion between `[]byte` and `string`, since this may result in memory
allocation+copy. Fasthttp API provides functions for both `[]byte` and `string` - allocation+copy. Fasthttp API provides functions for both `[]byte` and `string` -
use these functions instead of converting manually between `[]byte` and `string`. use these functions instead of converting manually between `[]byte` and `string`.
There are some exceptions - see [this wiki page](https://github.com/golang/go/wiki/CompilerOptimizations#string-and-byte) There are some exceptions - see [this wiki page](https://github.com/golang/go/wiki/CompilerOptimizations#string-and-byte)
for more details. for more details.
* Verify your tests and production code under * Verify your tests and production code under
[race detector](https://golang.org/doc/articles/race_detector.html) on a regular basis. [race detector](https://go.dev/doc/articles/race_detector.html) on a regular basis.
* Prefer [quicktemplate](https://github.com/valyala/quicktemplate) instead of * Prefer [quicktemplate](https://github.com/valyala/quicktemplate) instead of
[html/template](https://golang.org/pkg/html/template/) in your webserver. [html/template](https://pkg.go.dev/html/template) in your webserver.
## Tricks with `[]byte` buffers ## Tricks with `[]byte` buffers
@ -547,7 +547,7 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
* [kit-plugins](https://github.com/wencan/kit-plugins/tree/master/transport/fasthttp) - go-kit transport implementation for fasthttp. * [kit-plugins](https://github.com/wencan/kit-plugins/tree/master/transport/fasthttp) - go-kit transport implementation for fasthttp.
* [Fiber](https://github.com/gofiber/fiber) - An Expressjs inspired web framework running on Fasthttp * [Fiber](https://github.com/gofiber/fiber) - An Expressjs inspired web framework running on Fasthttp
* [Gearbox](https://github.com/gogearbox/gearbox) - :gear: gearbox is a web framework written in Go with a focus on high performance and memory optimization * [Gearbox](https://github.com/gogearbox/gearbox) - :gear: gearbox is a web framework written in Go with a focus on high performance and memory optimization
* [http2curl](https://github.com/li-jin-gou/http2curl) - A tool to convert fasthttp requests to curl command line
## FAQ ## FAQ
@ -569,21 +569,21 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
Because net/http API limits many optimization opportunities. See the answer Because net/http API limits many optimization opportunities. See the answer
above for more details. Also certain net/http API parts are suboptimal above for more details. Also certain net/http API parts are suboptimal
for use: for use:
* Compare [net/http connection hijacking](https://golang.org/pkg/net/http/#Hijacker) * Compare [net/http connection hijacking](https://pkg.go.dev/net/http#Hijacker)
to [fasthttp connection hijacking](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack). to [fasthttp connection hijacking](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack).
* Compare [net/http Request.Body reading](https://golang.org/pkg/net/http/#Request) * Compare [net/http Request.Body reading](https://pkg.go.dev/net/http#Request)
to [fasthttp request body reading](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody). to [fasthttp request body reading](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody).
* *Why fasthttp doesn't support HTTP/2.0 and WebSockets?* * *Why fasthttp doesn't support HTTP/2.0 and WebSockets?*
[HTTP/2.0 support](https://github.com/fasthttp/http2) is in progress. [WebSockets](https://github.com/fasthttp/websockets) has been done already. [HTTP/2.0 support](https://github.com/fasthttp/http2) is in progress. [WebSockets](https://github.com/fasthttp/websockets) has been done already.
Third parties also may use [RequestCtx.Hijack](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack) Third parties also may use [RequestCtx.Hijack](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)
for implementing these goodies. for implementing these goodies.
* *Are there known net/http advantages comparing to fasthttp?* * *Are there known net/http advantages comparing to fasthttp?*
Yes: Yes:
* net/http supports [HTTP/2.0 starting from go1.6](https://http2.golang.org/). * net/http supports [HTTP/2.0 starting from go1.6](https://pkg.go.dev/golang.org/x/net/http2).
* net/http API is stable, while fasthttp API constantly evolves. * net/http API is stable, while fasthttp API constantly evolves.
* net/http handles more HTTP corner cases. * net/http handles more HTTP corner cases.
* net/http can stream both request and response bodies * net/http can stream both request and response bodies
@ -626,11 +626,11 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
Cool! [File a bug](https://github.com/valyala/fasthttp/issues/new). But before Cool! [File a bug](https://github.com/valyala/fasthttp/issues/new). But before
doing this check the following in your code: doing this check the following in your code:
* Make sure there are no references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) * Make sure there are no references to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx)
or to its' members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler). or to its' members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
* Make sure you call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError) * Make sure you call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler) before returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
if there are references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) if there are references to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx)
or to its' members, which may be accessed by other goroutines. or to its' members, which may be accessed by other goroutines.
* *I didn't find an answer for my question here* * *I didn't find an answer for my question here*

View File

@ -1,6 +1,6 @@
### TL;DR ### TL;DR
We use a simplified version of [Golang Security Policy](https://golang.org/security). We use a simplified version of [Golang Security Policy](https://go.dev/security).
For example, for now we skip CVE assignment. For example, for now we skip CVE assignment.
### Reporting a Security Bug ### Reporting a Security Bug

View File

@ -44,7 +44,7 @@ var argsPool = &sync.Pool{
// //
// Args instance MUST NOT be used from concurrently running goroutines. // Args instance MUST NOT be used from concurrently running goroutines.
type Args struct { type Args struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
args []argsKV args []argsKV
buf []byte buf []byte

12
vendor/github.com/valyala/fasthttp/b2s_new.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
//go:build go1.20
// +build go1.20
package fasthttp
import "unsafe"
// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
func b2s(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}

16
vendor/github.com/valyala/fasthttp/b2s_old.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
//go:build !go1.20
// +build !go1.20
package fasthttp
import "unsafe"
// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
/* #nosec G103 */
return *(*string)(unsafe.Pointer(&b))
}

View File

@ -10,10 +10,8 @@ import (
"io" "io"
"math" "math"
"net" "net"
"reflect"
"sync" "sync"
"time" "time"
"unsafe"
) )
// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst. // AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
@ -317,31 +315,6 @@ func lowercaseBytes(b []byte) {
} }
} }
// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
/* #nosec G103 */
return *(*string)(unsafe.Pointer(&b))
}
// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) (b []byte) {
/* #nosec G103 */
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
/* #nosec G103 */
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}
// AppendUnquotedArg appends url-decoded src to dst and returns appended dst. // AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
// //
// dst may point to src. In this case src will be overwritten. // dst may point to src. In this case src will be overwritten.

View File

@ -1,5 +1,3 @@
// go:build !windows || !race
package fasthttp package fasthttp
import ( import (
@ -179,7 +177,7 @@ var defaultClient Client
// //
// The fields of a Client should not be changed while it is in use. // The fields of a Client should not be changed while it is in use.
type Client struct { type Client struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Client name. Used in User-Agent request header. // Client name. Used in User-Agent request header.
// //
@ -374,6 +372,7 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
// //
// ErrTimeout is returned if the response wasn't returned during // ErrTimeout is returned if the response wasn't returned during
// the given timeout. // the given timeout.
// Immediately returns ErrTimeout if timeout value is negative.
// //
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections // ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
// to the requested host are busy. // to the requested host are busy.
@ -387,6 +386,9 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
// try setting a ReadTimeout. // try setting a ReadTimeout.
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error { func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
req.timeout = timeout req.timeout = timeout
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp) return c.Do(req, resp)
} }
@ -407,6 +409,7 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
// //
// ErrTimeout is returned if the response wasn't returned until // ErrTimeout is returned if the response wasn't returned until
// the given deadline. // the given deadline.
// Immediately returns ErrTimeout if the deadline has already been reached.
// //
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections // ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
// to the requested host are busy. // to the requested host are busy.
@ -415,6 +418,9 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
// and AcquireResponse in performance-critical code. // and AcquireResponse in performance-critical code.
func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error { func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
req.timeout = time.Until(deadline) req.timeout = time.Until(deadline)
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp) return c.Do(req, resp)
} }
@ -470,9 +476,9 @@ func (c *Client) Do(req *Request, resp *Response) error {
host := uri.Host() host := uri.Host()
isTLS := false isTLS := false
if uri.isHttps() { if uri.isHTTPS() {
isTLS = true isTLS = true
} else if !uri.isHttp() { } else if !uri.isHTTP() {
return fmt.Errorf("unsupported protocol %q. http and https are supported", uri.Scheme()) return fmt.Errorf("unsupported protocol %q. http and https are supported", uri.Scheme())
} }
@ -521,6 +527,7 @@ func (c *Client) Do(req *Request, resp *Response) error {
if c.ConfigureClient != nil { if c.ConfigureClient != nil {
if err := c.ConfigureClient(hc); err != nil { if err := c.ConfigureClient(hc); err != nil {
c.mLock.Unlock()
return err return err
} }
} }
@ -642,7 +649,7 @@ const (
// //
// It is safe calling HostClient methods from concurrently running goroutines. // It is safe calling HostClient methods from concurrently running goroutines.
type HostClient struct { type HostClient struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Comma-separated list of upstream HTTP server host addresses, // Comma-separated list of upstream HTTP server host addresses,
// which are passed to Dial in a round-robin manner. // which are passed to Dial in a round-robin manner.
@ -687,7 +694,7 @@ type HostClient struct {
// listed in Addr. // listed in Addr.
// //
// You can change this value while the HostClient is being used // You can change this value while the HostClient is being used
// using HostClient.SetMaxConns(value) // with HostClient.SetMaxConns(value)
// //
// DefaultMaxConnsPerHost is used if not set. // DefaultMaxConnsPerHost is used if not set.
MaxConns int MaxConns int
@ -811,7 +818,7 @@ type HostClient struct {
pendingRequests int32 pendingRequests int32
// pendingClientRequests counts the number of requests that a Client is currently running using this HostClient. // pendingClientRequests counts the number of requests that a Client is currently running using this HostClient.
// It will be incremented ealier than pendingRequests and will be used by Client to see if the HostClient is still in use. // It will be incremented earlier than pendingRequests and will be used by Client to see if the HostClient is still in use.
pendingClientRequests int32 pendingClientRequests int32
connsCleanerRun bool connsCleanerRun bool
@ -1139,6 +1146,7 @@ func ReleaseResponse(resp *Response) {
// //
// ErrTimeout is returned if the response wasn't returned during // ErrTimeout is returned if the response wasn't returned during
// the given timeout. // the given timeout.
// Immediately returns ErrTimeout if timeout value is negative.
// //
// ErrNoFreeConns is returned if all HostClient.MaxConns connections // ErrNoFreeConns is returned if all HostClient.MaxConns connections
// to the host are busy. // to the host are busy.
@ -1152,6 +1160,9 @@ func ReleaseResponse(resp *Response) {
// try setting a ReadTimeout. // try setting a ReadTimeout.
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error { func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
req.timeout = timeout req.timeout = timeout
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp) return c.Do(req, resp)
} }
@ -1167,6 +1178,7 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
// //
// ErrTimeout is returned if the response wasn't returned until // ErrTimeout is returned if the response wasn't returned until
// the given deadline. // the given deadline.
// Immediately returns ErrTimeout if the deadline has already been reached.
// //
// ErrNoFreeConns is returned if all HostClient.MaxConns connections // ErrNoFreeConns is returned if all HostClient.MaxConns connections
// to the host are busy. // to the host are busy.
@ -1175,6 +1187,9 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
// and AcquireResponse in performance-critical code. // and AcquireResponse in performance-critical code.
func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error { func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
req.timeout = time.Until(deadline) req.timeout = time.Until(deadline)
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp) return c.Do(req, resp)
} }
@ -1308,7 +1323,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
req.secureErrorLogMessage = c.SecureErrorLogMessage req.secureErrorLogMessage = c.SecureErrorLogMessage
req.Header.secureErrorLogMessage = c.SecureErrorLogMessage req.Header.secureErrorLogMessage = c.SecureErrorLogMessage
if c.IsTLS != req.URI().isHttps() { if c.IsTLS != req.URI().isHTTPS() {
return false, ErrHostClientRedirectToDifferentScheme return false, ErrHostClientRedirectToDifferentScheme
} }
@ -1519,6 +1534,7 @@ func (c *HostClient) acquireConn(reqTimeout time.Duration, connectionClose bool)
c.conns[n-1] = nil c.conns[n-1] = nil
c.conns = c.conns[:n-1] c.conns = c.conns[:n-1]
default: default:
c.connsLock.Unlock()
return nil, ErrConnPoolStrategyNotImpl return nil, ErrConnPoolStrategyNotImpl
} }
} }
@ -2003,11 +2019,11 @@ func AddMissingPort(addr string, isTLS bool) string {
return addr return addr
} }
isIp6 := addr[0] == '[' isIP6 := addr[0] == '['
if isIp6 { if isIP6 {
// if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port // if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port
isIp6WithoutPort := addr[addrLen-1] == ']' isIP6WithoutPort := addr[addrLen-1] == ']'
if !isIp6WithoutPort { if !isIP6WithoutPort {
return addr return addr
} }
} else { // IPv4 } else { // IPv4
@ -2139,7 +2155,7 @@ func (q *wantConnQueue) peekFront() *wantConn {
return nil return nil
} }
// cleanFront pops any wantConns that are no longer waiting from the head of the // clearFront pops any wantConns that are no longer waiting from the head of the
// queue, reporting whether any were popped. // queue, reporting whether any were popped.
func (q *wantConnQueue) clearFront() (cleaned bool) { func (q *wantConnQueue) clearFront() (cleaned bool) {
for { for {
@ -2165,7 +2181,7 @@ func (q *wantConnQueue) clearFront() (cleaned bool) {
// It is safe calling PipelineClient methods from concurrently running // It is safe calling PipelineClient methods from concurrently running
// goroutines. // goroutines.
type PipelineClient struct { type PipelineClient struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Address of the host to connect to. // Address of the host to connect to.
Addr string Addr string
@ -2279,7 +2295,7 @@ type PipelineClient struct {
} }
type pipelineConnClient struct { type pipelineConnClient struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
Addr string Addr string
Name string Name string

View File

@ -402,7 +402,7 @@ var (
func newCompressWriterPoolMap() []*sync.Pool { func newCompressWriterPoolMap() []*sync.Pool {
// Initialize pools for all the compression levels defined // Initialize pools for all the compression levels defined
// in https://golang.org/pkg/compress/flate/#pkg-constants . // in https://pkg.go.dev/compress/flate#pkg-constants .
// Compression levels are normalized with normalizeCompressLevel, // Compression levels are normalized with normalizeCompressLevel,
// so the fit [0..11]. // so the fit [0..11].
var m []*sync.Pool var m []*sync.Pool
@ -413,7 +413,7 @@ func newCompressWriterPoolMap() []*sync.Pool {
} }
func isFileCompressible(f *os.File, minCompressRatio float64) bool { func isFileCompressible(f *os.File, minCompressRatio float64) bool {
// Try compressing the first 4kb of of the file // Try compressing the first 4kb of the file
// and see if it can be compressed by more than // and see if it can be compressed by more than
// the given minCompressRatio. // the given minCompressRatio.
b := bytebufferpool.Get() b := bytebufferpool.Get()

View File

@ -65,7 +65,7 @@ var cookiePool = &sync.Pool{
// //
// Cookie instance MUST NOT be used from concurrently running goroutines. // Cookie instance MUST NOT be used from concurrently running goroutines.
type Cookie struct { type Cookie struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
key []byte key []byte
value []byte value []byte

View File

@ -121,8 +121,8 @@ func (ln *InmemoryListener) DialWithLocalAddr(local net.Addr) (net.Conn, error)
// Wait until the connection has been accepted. // Wait until the connection has been accepted.
<-accepted <-accepted
} else { } else {
sConn.Close() //nolint:errcheck _ = sConn.Close()
cConn.Close() //nolint:errcheck _ = cConn.Close()
cConn = nil cConn = nil
} }
ln.lock.Unlock() ln.lock.Unlock()

View File

@ -223,7 +223,7 @@ func NewPathPrefixStripper(prefixSize int) PathRewriteFunc {
// //
// It is prohibited copying FS values. Create new values instead. // It is prohibited copying FS values. Create new values instead.
type FS struct { type FS struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Path to the root directory to serve files from. // Path to the root directory to serve files from.
Root string Root string
@ -1304,7 +1304,7 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
} }
// Only re-create the compressed file if there was more than a second between the mod times. // Only re-create the compressed file if there was more than a second between the mod times.
// On MacOS the gzip seems to truncate the nanoseconds in the mod time causing the original file // On macOS the gzip seems to truncate the nanoseconds in the mod time causing the original file
// to look newer than the gzipped file. // to look newer than the gzipped file.
if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second { if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {
// The compressed file became stale. Re-create it. // The compressed file became stale. Re-create it.

View File

@ -24,7 +24,7 @@ const (
// ResponseHeader instance MUST NOT be used from concurrently running // ResponseHeader instance MUST NOT be used from concurrently running
// goroutines. // goroutines.
type ResponseHeader struct { type ResponseHeader struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
disableNormalizing bool disableNormalizing bool
noHTTP11 bool noHTTP11 bool
@ -59,7 +59,7 @@ type ResponseHeader struct {
// RequestHeader instance MUST NOT be used from concurrently running // RequestHeader instance MUST NOT be used from concurrently running
// goroutines. // goroutines.
type RequestHeader struct { type RequestHeader struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
disableNormalizing bool disableNormalizing bool
noHTTP11 bool noHTTP11 bool
@ -109,7 +109,7 @@ func (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) {
h.setNonSpecial(strContentRange, h.bufKV.value) h.setNonSpecial(strContentRange, h.bufKV.value)
} }
// SetByteRanges sets 'Range: bytes=startPos-endPos' header. // SetByteRange sets 'Range: bytes=startPos-endPos' header.
// //
// - If startPos is negative, then 'bytes=-startPos' value is set. // - If startPos is negative, then 'bytes=-startPos' value is set.
// - If endPos is negative, then 'bytes=startPos-' value is set. // - If endPos is negative, then 'bytes=startPos-' value is set.
@ -1495,7 +1495,7 @@ func (h *ResponseHeader) SetCanonical(key, value []byte) {
// SetCookie sets the given response cookie. // SetCookie sets the given response cookie.
// //
// It is save re-using the cookie after the function returns. // It is safe re-using the cookie after the function returns.
func (h *ResponseHeader) SetCookie(cookie *Cookie) { func (h *ResponseHeader) SetCookie(cookie *Cookie) {
h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue) h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue)
} }
@ -3083,7 +3083,7 @@ func (s *headerScanner) next() bool {
n++ n++
for len(s.b) > n && s.b[n] == ' ' { for len(s.b) > n && s.b[n] == ' ' {
n++ n++
// the newline index is a relative index, and lines below trimed `s.b` by `n`, // the newline index is a relative index, and lines below trimmed `s.b` by `n`,
// so the relative newline index also shifted forward. it's safe to decrease // so the relative newline index also shifted forward. it's safe to decrease
// to a minus value, it means it's invalid, and will find the newline again. // to a minus value, it means it's invalid, and will find the newline again.
s.nextNewLine-- s.nextNewLine--

View File

@ -37,7 +37,7 @@ func SetBodySizePoolLimit(reqBodyLimit, respBodyLimit int) {
// //
// Request instance MUST NOT be used from concurrently running goroutines. // Request instance MUST NOT be used from concurrently running goroutines.
type Request struct { type Request struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Request header // Request header
// //
@ -81,7 +81,7 @@ type Request struct {
// //
// Response instance MUST NOT be used from concurrently running goroutines. // Response instance MUST NOT be used from concurrently running goroutines.
type Response struct { type Response struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Response header // Response header
// //
@ -771,7 +771,7 @@ func (req *Request) ResetBody() {
func (req *Request) CopyTo(dst *Request) { func (req *Request) CopyTo(dst *Request) {
req.copyToSkipBody(dst) req.copyToSkipBody(dst)
if req.bodyRaw != nil { if req.bodyRaw != nil {
dst.bodyRaw = req.bodyRaw dst.bodyRaw = append(dst.bodyRaw[:0], req.bodyRaw...)
if dst.body != nil { if dst.body != nil {
dst.body.Reset() dst.body.Reset()
} }
@ -803,7 +803,7 @@ func (req *Request) copyToSkipBody(dst *Request) {
func (resp *Response) CopyTo(dst *Response) { func (resp *Response) CopyTo(dst *Response) {
resp.copyToSkipBody(dst) resp.copyToSkipBody(dst)
if resp.bodyRaw != nil { if resp.bodyRaw != nil {
dst.bodyRaw = resp.bodyRaw dst.bodyRaw = append(dst.bodyRaw, resp.bodyRaw...)
if dst.body != nil { if dst.body != nil {
dst.body.Reset() dst.body.Reset()
} }
@ -852,9 +852,9 @@ func (req *Request) URI() *URI {
// Use this method if a single URI may be reused across multiple requests. // Use this method if a single URI may be reused across multiple requests.
// Otherwise, you can just use SetRequestURI() and it will be parsed as new URI. // Otherwise, you can just use SetRequestURI() and it will be parsed as new URI.
// The URI is copied and can be safely modified later. // The URI is copied and can be safely modified later.
func (req *Request) SetURI(newUri *URI) { func (req *Request) SetURI(newURI *URI) {
if newUri != nil { if newURI != nil {
newUri.CopyTo(&req.uri) newURI.CopyTo(&req.uri)
req.parsedURI = true req.parsedURI = true
return return
} }
@ -893,7 +893,7 @@ func (req *Request) parsePostArgs() {
// isn't 'multipart/form-data'. // isn't 'multipart/form-data'.
var ErrNoMultipartForm = errors.New("request has no multipart/form-data Content-Type") var ErrNoMultipartForm = errors.New("request has no multipart/form-data Content-Type")
// MultipartForm returns requests's multipart form. // MultipartForm returns request's multipart form.
// //
// Returns ErrNoMultipartForm if request's Content-Type // Returns ErrNoMultipartForm if request's Content-Type
// isn't 'multipart/form-data'. // isn't 'multipart/form-data'.
@ -992,6 +992,7 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
return fmt.Errorf("cannot open form file %q (%q): %w", k, fv.Filename, err) return fmt.Errorf("cannot open form file %q (%q): %w", k, fv.Filename, err)
} }
if _, err = copyZeroAlloc(vw, fh); err != nil { if _, err = copyZeroAlloc(vw, fh); err != nil {
_ = fh.Close()
return fmt.Errorf("error when copying form file %q (%q): %w", k, fv.Filename, err) return fmt.Errorf("error when copying form file %q (%q): %w", k, fv.Filename, err)
} }
if err = fh.Close(); err != nil { if err = fh.Close(); err != nil {

View File

@ -25,7 +25,7 @@ type BalancingClient interface {
// //
// It is safe calling LBClient methods from concurrently running goroutines. // It is safe calling LBClient methods from concurrently running goroutines.
type LBClient struct { type LBClient struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Clients must contain non-zero clients list. // Clients must contain non-zero clients list.
// Incoming requests are balanced among these clients. // Incoming requests are balanced among these clients.
@ -70,7 +70,7 @@ func (cc *LBClient) DoTimeout(req *Request, resp *Response, timeout time.Duratio
return cc.get().DoDeadline(req, resp, deadline) return cc.get().DoDeadline(req, resp, deadline)
} }
// Do calls calculates deadline using LBClient.Timeout and calls DoDeadline // Do calculates timeout using LBClient.Timeout and calls DoTimeout
// on the least loaded client. // on the least loaded client.
func (cc *LBClient) Do(req *Request, resp *Response) error { func (cc *LBClient) Do(req *Request, resp *Response) error {
timeout := cc.Timeout timeout := cc.Timeout

View File

@ -5,7 +5,7 @@ package fasthttp
// //
// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details. // See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
// and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example // and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example
type noCopy struct{} //nolint:unused type noCopy struct{}
func (*noCopy) Lock() {} //nolint:unused func (*noCopy) Lock() {}
func (*noCopy) Unlock() {} //nolint:unused func (*noCopy) Unlock() {}

11
vendor/github.com/valyala/fasthttp/s2b_new.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
//go:build go1.20
// +build go1.20
package fasthttp
import "unsafe"
// s2b converts string to a byte slice without memory allocation.
func s2b(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

24
vendor/github.com/valyala/fasthttp/s2b_old.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
//go:build !go1.20
// +build !go1.20
package fasthttp
import (
"reflect"
"unsafe"
)
// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) (b []byte) {
/* #nosec G103 */
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
/* #nosec G103 */
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}

View File

@ -20,8 +20,7 @@ import (
var errNoCertOrKeyProvided = errors.New("cert or key has not provided") var errNoCertOrKeyProvided = errors.New("cert or key has not provided")
var ( var (
// ErrAlreadyServing is returned when calling Serve on a Server // Deprecated: ErrAlreadyServing is never returned from Serve. See issue #633.
// that is already serving connections.
ErrAlreadyServing = errors.New("Server is already serving connections") ErrAlreadyServing = errors.New("Server is already serving connections")
) )
@ -130,7 +129,7 @@ func ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler Reque
// RequestHandler must process incoming requests. // RequestHandler must process incoming requests.
// //
// RequestHandler must call ctx.TimeoutError() before returning // RequestHandler must call ctx.TimeoutError() before returning
// if it keeps references to ctx and/or its' members after the return. // if it keeps references to ctx and/or its members after the return.
// Consider wrapping RequestHandler into TimeoutHandler if response time // Consider wrapping RequestHandler into TimeoutHandler if response time
// must be limited. // must be limited.
type RequestHandler func(ctx *RequestCtx) type RequestHandler func(ctx *RequestCtx)
@ -148,7 +147,7 @@ type ServeHandler func(c net.Conn) error
// //
// It is safe to call Server methods from concurrently running goroutines. // It is safe to call Server methods from concurrently running goroutines.
type Server struct { type Server struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Handler for processing incoming requests. // Handler for processing incoming requests.
// //
@ -377,12 +376,12 @@ type Server struct {
// which will close it when needed. // which will close it when needed.
KeepHijackedConns bool KeepHijackedConns bool
// CloseOnShutdown when true adds a `Connection: close` header when when the server is shutting down. // CloseOnShutdown when true adds a `Connection: close` header when the server is shutting down.
CloseOnShutdown bool CloseOnShutdown bool
// StreamRequestBody enables request body streaming, // StreamRequestBody enables request body streaming,
// and calls the handler sooner when given body is // and calls the handler sooner when given body is
// larger then the current limit. // larger than the current limit.
StreamRequestBody bool StreamRequestBody bool
// ConnState specifies an optional callback function that is // ConnState specifies an optional callback function that is
@ -567,7 +566,7 @@ func CompressHandlerBrotliLevel(h RequestHandler, brotliLevel, otherLevel int) R
// It is forbidden copying RequestCtx instances. // It is forbidden copying RequestCtx instances.
// //
// RequestHandler should avoid holding references to incoming RequestCtx and/or // RequestHandler should avoid holding references to incoming RequestCtx and/or
// its' members after the return. // its members after the return.
// If holding RequestCtx references after the return is unavoidable // If holding RequestCtx references after the return is unavoidable
// (for instance, ctx is passed to a separate goroutine and ctx lifetime cannot // (for instance, ctx is passed to a separate goroutine and ctx lifetime cannot
// be controlled), then the RequestHandler MUST call ctx.TimeoutError() // be controlled), then the RequestHandler MUST call ctx.TimeoutError()
@ -577,7 +576,7 @@ func CompressHandlerBrotliLevel(h RequestHandler, brotliLevel, otherLevel int) R
// running goroutines. The only exception is TimeoutError*, which may be called // running goroutines. The only exception is TimeoutError*, which may be called
// while other goroutines accessing RequestCtx. // while other goroutines accessing RequestCtx.
type RequestCtx struct { type RequestCtx struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
// Incoming request. // Incoming request.
// //
@ -1003,7 +1002,7 @@ func (ctx *RequestCtx) PostArgs() *Args {
return ctx.Request.PostArgs() return ctx.Request.PostArgs()
} }
// MultipartForm returns requests's multipart form. // MultipartForm returns request's multipart form.
// //
// Returns ErrNoMultipartForm if request's content-type // Returns ErrNoMultipartForm if request's content-type
// isn't 'multipart/form-data'. // isn't 'multipart/form-data'.
@ -1234,7 +1233,7 @@ func (ctx *RequestCtx) RemoteAddr() net.Addr {
// SetRemoteAddr sets remote address to the given value. // SetRemoteAddr sets remote address to the given value.
// //
// Set nil value to resore default behaviour for using // Set nil value to restore default behaviour for using
// connection remote address. // connection remote address.
func (ctx *RequestCtx) SetRemoteAddr(remoteAddr net.Addr) { func (ctx *RequestCtx) SetRemoteAddr(remoteAddr net.Addr) {
ctx.remoteAddr = remoteAddr ctx.remoteAddr = remoteAddr
@ -1480,7 +1479,7 @@ func (ctx *RequestCtx) SetBodyStream(bodyStream io.Reader, bodySize int) {
// SetBodyStreamWriter registers the given stream writer for populating // SetBodyStreamWriter registers the given stream writer for populating
// response body. // response body.
// //
// Access to RequestCtx and/or its' members is forbidden from sw. // Access to RequestCtx and/or its members is forbidden from sw.
// //
// This function may be used in the following cases: // This function may be used in the following cases:
// //
@ -1864,7 +1863,7 @@ func (s *Server) Serve(ln net.Listener) error {
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil. // When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.
// Make sure the program doesn't exit and waits instead for Shutdown to return. // Make sure the program doesn't exit and waits instead for Shutdown to return.
// //
// Shutdown does not close keepalive connections so its recommended to set ReadTimeout and IdleTimeout to something else than 0. // Shutdown does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout to something else than 0.
func (s *Server) Shutdown() error { func (s *Server) Shutdown() error {
return s.ShutdownWithContext(context.Background()) return s.ShutdownWithContext(context.Background())
} }
@ -1875,7 +1874,7 @@ func (s *Server) Shutdown() error {
// When ShutdownWithContext is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil. // When ShutdownWithContext is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.
// Make sure the program doesn't exit and waits instead for Shutdown to return. // Make sure the program doesn't exit and waits instead for Shutdown to return.
// //
// ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout and IdleTimeout to something else than 0. // ShutdownWithContext does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout to something else than 0.
func (s *Server) ShutdownWithContext(ctx context.Context) (err error) { func (s *Server) ShutdownWithContext(ctx context.Context) (err error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -1950,12 +1949,12 @@ func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.
if tc, ok := c.(*net.TCPConn); ok && s.TCPKeepalive { if tc, ok := c.(*net.TCPConn); ok && s.TCPKeepalive {
if err := tc.SetKeepAlive(s.TCPKeepalive); err != nil { if err := tc.SetKeepAlive(s.TCPKeepalive); err != nil {
tc.Close() //nolint:errcheck _ = tc.Close()
return nil, err return nil, err
} }
if s.TCPKeepalivePeriod > 0 { if s.TCPKeepalivePeriod > 0 {
if err := tc.SetKeepAlivePeriod(s.TCPKeepalivePeriod); err != nil { if err := tc.SetKeepAlivePeriod(s.TCPKeepalivePeriod); err != nil {
tc.Close() //nolint:errcheck _ = tc.Close()
return nil, err return nil, err
} }
} }
@ -2226,7 +2225,7 @@ func (s *Server) serveConn(c net.Conn) (err error) {
// Reading Headers. // Reading Headers.
// //
// If we have pipline response in the outgoing buffer, // If we have pipeline response in the outgoing buffer,
// we only want to try and read the next headers once. // we only want to try and read the next headers once.
// If we have to wait for the next request we flush the // If we have to wait for the next request we flush the
// outgoing buffer first so it doesn't have to wait. // outgoing buffer first so it doesn't have to wait.

View File

@ -338,8 +338,8 @@ func (d *TCPDialer) tryDial(network string, addr *net.TCPAddr, deadline time.Tim
dialer.LocalAddr = d.LocalAddr dialer.LocalAddr = d.LocalAddr
} }
ctx, cancel_ctx := context.WithDeadline(context.Background(), deadline) ctx, cancelCtx := context.WithDeadline(context.Background(), deadline)
defer cancel_ctx() defer cancelCtx()
conn, err := dialer.DialContext(ctx, network, addr.String()) conn, err := dialer.DialContext(ctx, network, addr.String())
if err != nil && ctx.Err() == context.DeadlineExceeded { if err != nil && ctx.Err() == context.DeadlineExceeded {
return nil, ErrDialTimeout return nil, ErrDialTimeout

View File

@ -18,7 +18,7 @@ func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
func stopTimer(t *time.Timer) { func stopTimer(t *time.Timer) {
if !t.Stop() { if !t.Stop() {
// Collect possibly added time from the channel // Collect possibly added time from the channel
// if timer has been stopped and nobody collected its' value. // if timer has been stopped and nobody collected its value.
select { select {
case <-t.C: case <-t.C:
default: default:
@ -44,7 +44,7 @@ func AcquireTimer(timeout time.Duration) *time.Timer {
// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool // ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool
// and prevents the Timer from firing. // and prevents the Timer from firing.
// //
// Do not access the released time.Timer or read from it's channel otherwise // Do not access the released time.Timer or read from its channel otherwise
// data races may occur. // data races may occur.
func ReleaseTimer(t *time.Timer) { func ReleaseTimer(t *time.Timer) {
stopTimer(t) stopTimer(t)

View File

@ -40,7 +40,7 @@ var uriPool = &sync.Pool{
// //
// URI instance MUST NOT be used from concurrently running goroutines. // URI instance MUST NOT be used from concurrently running goroutines.
type URI struct { type URI struct {
noCopy noCopy //nolint:unused,structcheck noCopy noCopy
pathOriginal []byte pathOriginal []byte
scheme []byte scheme []byte
@ -217,11 +217,11 @@ func (u *URI) SetSchemeBytes(scheme []byte) {
lowercaseBytes(u.scheme) lowercaseBytes(u.scheme)
} }
func (u *URI) isHttps() bool { func (u *URI) isHTTPS() bool {
return bytes.Equal(u.scheme, strHTTPS) return bytes.Equal(u.scheme, strHTTPS)
} }
func (u *URI) isHttp() bool { func (u *URI) isHTTP() bool {
return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP) return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)
} }
@ -504,7 +504,7 @@ func unescape(s []byte, mode encoding) ([]byte, error) {
// appearing in a URL string, according to RFC 3986. // appearing in a URL string, according to RFC 3986.
// //
// Please be informed that for now shouldEscape does not check all // Please be informed that for now shouldEscape does not check all
// reserved characters correctly. See golang.org/issue/5684. // reserved characters correctly. See https://github.com/golang/go/issues/5684.
// //
// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100 // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100
func shouldEscape(c byte, mode encoding) bool { func shouldEscape(c byte, mode encoding) bool {
@ -631,7 +631,6 @@ func normalizePath(dst, src []byte) []byte {
if filepath.Separator == '\\' { if filepath.Separator == '\\' {
// remove \.\ parts // remove \.\ parts
b = dst
for { for {
n := bytes.Index(b, strBackSlashDotBackSlash) n := bytes.Index(b, strBackSlashDotBackSlash)
if n < 0 { if n < 0 {
@ -652,7 +651,8 @@ func normalizePath(dst, src []byte) []byte {
if nn < 0 { if nn < 0 {
nn = 0 nn = 0
} }
n += len(strSlashDotDotBackSlash) - 1 nn++
n += len(strSlashDotDotBackSlash)
copy(b[nn:], b[n:]) copy(b[nn:], b[n:])
b = b[:len(b)-n+nn] b = b[:len(b)-n+nn]
} }

View File

@ -4,8 +4,9 @@
package fasthttp package fasthttp
func addLeadingSlash(dst, src []byte) []byte { func addLeadingSlash(dst, src []byte) []byte {
// zero length and "C:/" case // zero length 、"C:/" and "a" case
if len(src) == 0 || (len(src) > 2 && src[1] != ':') { isDisk := len(src) > 2 && src[1] == ':'
if len(src) == 0 || (!isDisk && src[0] != '/') {
dst = append(dst, '/') dst = append(dst, '/')
} }

3
vendor/go.etcd.io/bbolt/.gitignore generated vendored
View File

@ -3,5 +3,8 @@
*.swp *.swp
/bin/ /bin/
cover.out cover.out
cover-*.out
/.idea /.idea
*.iml *.iml
/cmd/bbolt/bbolt

18
vendor/go.etcd.io/bbolt/.travis.yml generated vendored
View File

@ -1,18 +0,0 @@
language: go
go_import_path: go.etcd.io/bbolt
sudo: false
go:
- 1.15
before_install:
- go get -v golang.org/x/sys/unix
- go get -v honnef.co/go/tools/...
- go get -v github.com/kisielk/errcheck
script:
- make fmt
- make test
- make race
# - make errcheck

71
vendor/go.etcd.io/bbolt/Makefile generated vendored
View File

@ -2,35 +2,62 @@ BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMIT=`git rev-parse --short HEAD` COMMIT=`git rev-parse --short HEAD`
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
race: TESTFLAGS_RACE=-race=false
@TEST_FREELIST_TYPE=hashmap go test -v -race -test.run="TestSimulate_(100op|1000op)" ifdef ENABLE_RACE
@echo "array freelist test" TESTFLAGS_RACE=-race=true
@TEST_FREELIST_TYPE=array go test -v -race -test.run="TestSimulate_(100op|1000op)" endif
TESTFLAGS_CPU=
ifdef CPU
TESTFLAGS_CPU=-cpu=$(CPU)
endif
TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS)
.PHONY: fmt
fmt: fmt:
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]') !(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]')
# go get honnef.co/go/tools/simple .PHONY: lint
gosimple: lint:
gosimple ./... golangci-lint run ./...
# go get honnef.co/go/tools/unused
unused:
unused ./...
# go get github.com/kisielk/errcheck
errcheck:
@errcheck -ignorepkg=bytes -ignore=os:Remove go.etcd.io/bbolt
.PHONY: test
test: test:
TEST_FREELIST_TYPE=hashmap go test -timeout 20m -v -coverprofile cover.out -covermode atomic @echo "hashmap freelist test"
# Note: gets "program not an importable package" in out of path builds TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m
TEST_FREELIST_TYPE=hashmap go test -v ./cmd/bbolt TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
@echo "array freelist test" @echo "array freelist test"
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
@TEST_FREELIST_TYPE=array go test -timeout 20m -v -coverprofile cover.out -covermode atomic .PHONY: coverage
# Note: gets "program not an importable package" in out of path builds coverage:
@TEST_FREELIST_TYPE=array go test -v ./cmd/bbolt @echo "hashmap freelist test"
TEST_FREELIST_TYPE=hashmap go test -v -timeout 30m \
-coverprofile cover-freelist-hashmap.out -covermode atomic
@echo "array freelist test"
TEST_FREELIST_TYPE=array go test -v -timeout 30m \
-coverprofile cover-freelist-array.out -covermode atomic
.PHONY: gofail-enable
gofail-enable: install-gofail
gofail enable .
.PHONY: gofail-disable
gofail-disable:
gofail disable .
.PHONY: install-gofail
install-gofail:
go install go.etcd.io/gofail
.PHONY: test-failpoint
test-failpoint:
@echo "[failpoint] hashmap freelist test"
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
@echo "[failpoint] array freelist test"
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
.PHONY: race fmt errcheck test gosimple unused

21
vendor/go.etcd.io/bbolt/README.md generated vendored
View File

@ -26,7 +26,7 @@ and setting values. That's it.
[gh_ben]: https://github.com/benbjohnson [gh_ben]: https://github.com/benbjohnson
[bolt]: https://github.com/boltdb/bolt [bolt]: https://github.com/boltdb/bolt
[hyc_symas]: https://twitter.com/hyc_symas [hyc_symas]: https://twitter.com/hyc_symas
[lmdb]: http://symas.com/mdb/ [lmdb]: https://www.symas.com/symas-embedded-database-lmdb
## Project Status ## Project Status
@ -78,14 +78,23 @@ New minor versions may add additional features to the API.
### Installing ### Installing
To start using Bolt, install Go and run `go get`: To start using Bolt, install Go and run `go get`:
```sh ```sh
$ go get go.etcd.io/bbolt/... $ go get go.etcd.io/bbolt@latest
``` ```
This will retrieve the library and install the `bolt` command line utility into This will retrieve the library and update your `go.mod` and `go.sum` files.
your `$GOBIN` path.
To run the command line utility, execute:
```sh
$ go run go.etcd.io/bbolt/cmd/bbolt@latest
```
Run `go install` to install the `bbolt` command line utility into
your `$GOBIN` path, which defaults to `$GOPATH/bin` or `$HOME/go/bin` if the
`GOPATH` environment variable is not set.
```sh
$ go install go.etcd.io/bbolt/cmd/bbolt@latest
```
### Importing bbolt ### Importing bbolt
@ -933,7 +942,7 @@ Below is a list of public, open source projects that use Bolt:
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed. * [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies * [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs. * [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
* [Key Value Access Langusge (KVAL)](https://github.com/kval-access-language) - A proposed grammar for key-value datastores offering a bbolt binding. * [Key Value Access Language (KVAL)](https://github.com/kval-access-language) - A proposed grammar for key-value datastores offering a bbolt binding.
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage. * [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. * [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets. * [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.

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