Merge branch 'develop'
This commit is contained in:
commit
fbda134b7d
4
Makefile
4
Makefile
|
@ -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
|
||||||
|
|
|
@ -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: {}
|
||||||
|
|
|
@ -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
44
go.mod
|
@ -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
107
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
51
main.go
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
141
vendor/github.com/caarlos0/env/v6/README.md → vendor/github.com/caarlos0/env/v7/README.md
generated
vendored
141
vendor/github.com/caarlos0/env/v6/README.md → vendor/github.com/caarlos0/env/v7/README.md
generated
vendored
|
@ -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)
|
152
vendor/github.com/caarlos0/env/v6/env.go → vendor/github.com/caarlos0/env/v7/env.go
generated
vendored
152
vendor/github.com/caarlos0/env/v6/env.go → vendor/github.com/caarlos0/env/v7/env.go
generated
vendored
|
@ -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(), ";")
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 math∕big·addVV(SB)
|
|
||||||
|
|
||||||
// func subVV(z, x, y []Word) (c Word)
|
|
||||||
TEXT ·subVV(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addVW(SB)
|
|
||||||
|
|
||||||
// func subVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·subVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·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 math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·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 math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·addVV(SB)
|
|
||||||
|
|
||||||
// func subVV(z, x, y []Word) (c Word)
|
|
||||||
TEXT ·subVV(SB),NOSPLIT,$0
|
|
||||||
B math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·addVW(SB)
|
|
||||||
|
|
||||||
// func subVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·subVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
B math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
B math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·addVV(SB)
|
|
||||||
|
|
||||||
// func subVV(z, x, y []Word) (c Word)
|
|
||||||
TEXT ·subVV(SB),NOSPLIT,$0
|
|
||||||
B math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·addVW(SB)
|
|
||||||
|
|
||||||
// func subVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·subVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
B math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
B math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
B math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 math∕big·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 math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·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 math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·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 math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·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 math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·addVV(SB)
|
|
||||||
|
|
||||||
// func subVV(z, x, y []Word) (c Word)
|
|
||||||
TEXT ·subVV(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·addVW(SB)
|
|
||||||
|
|
||||||
// func subVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·subVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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 math∕big·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 math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·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 math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
JMP math∕big·addMulVVW(SB)
|
|
|
@ -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 math∕big·addVV(SB)
|
|
||||||
|
|
||||||
// func subVV(z, x, y []Word) (c Word)
|
|
||||||
TEXT ·subVV(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·subVV(SB)
|
|
||||||
|
|
||||||
// func addVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·addVW(SB)
|
|
||||||
|
|
||||||
// func subVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·subVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·subVW(SB)
|
|
||||||
|
|
||||||
// func shlVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·shlVU(SB)
|
|
||||||
|
|
||||||
// func shrVU(z, x []Word, s uint) (c Word)
|
|
||||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·shrVU(SB)
|
|
||||||
|
|
||||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
|
||||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·mulAddVWW(SB)
|
|
||||||
|
|
||||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
|
||||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
|
||||||
BR math∕big·addMulVVW(SB)
|
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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--
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,5 +3,8 @@
|
||||||
*.swp
|
*.swp
|
||||||
/bin/
|
/bin/
|
||||||
cover.out
|
cover.out
|
||||||
|
cover-*.out
|
||||||
/.idea
|
/.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
/cmd/bbolt/bbolt
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue