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

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

View File

@ -7,7 +7,7 @@ srcdir = .
GO ?= go
GOFLAGS ?= -buildvcs=true
EXECUTABLE ?= indieauth
EXECUTABLE ?= auth
#### End of system configuration section. ####
@ -17,11 +17,9 @@ all: main.go
$(GO) build -v $(GOFLAGS) -o $(EXECUTABLE)
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
check: ## Perform self-tests
$(GO) generate $(srcdir)/internal/testing/httptest/...
$(GO) test -v -cover -failfast -short -shuffle=on $(GOFLAGS) $(srcdir)/...
.PHONY: help

View File

@ -1,83 +1,49 @@
---
kind: pipeline
type: docker
name: default
kind: "pipeline"
type: "docker"
name: "default"
environment:
CGO_ENABLED: 0
steps:
- name: test
image: golang:latest
- name: "test"
image: "golang:1.20"
volumes:
- name: modules
path: /go/pkg/mod
- name: "modules"
path: "/go/pkg/mod"
commands:
- make check
- "make check"
- name: build
image: golang:latest
- name: "build"
image: "golang:1.20"
volumes:
- name: modules
path: /go/pkg/mod
- name: "modules"
path: "/go/pkg/mod"
commands:
- make
- "make"
depends_on:
- test
- "test"
- name: stop-service
image: appleboy/drone-ssh
- name: "delivery"
image: "drillster/drone-rsync"
settings:
host:
from_secret: SSH_HOST
username: root
hosts:
from_secret: "SSH_HOST_IP"
key:
from_secret: SSH_PRIVATE_KEY
from_secret: "SSH_PRIVATE_KEY"
source: "./auth"
target: "/etc/auth/auth"
prescript:
- "systemctl stop auth"
script:
- "systemctl stop indieauth"
- "systemctl start auth"
depends_on:
- build
when:
branch:
- 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:
- name: modules
temp: {}

View File

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

44
go.mod
View File

@ -4,33 +4,33 @@ go 1.19
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/brianvoe/gofakeit/v6 v6.20.1
github.com/caarlos0/env/v6 v6.10.1
github.com/go-logfmt/logfmt v0.5.1
github.com/goccy/go-json v0.10.0
github.com/brianvoe/gofakeit/v6 v6.20.2
github.com/caarlos0/env/v7 v7.1.0
github.com/go-logfmt/logfmt v0.6.0
github.com/goccy/go-json v0.10.1
github.com/google/go-cmp v0.5.9
github.com/jmoiron/sqlx v1.3.5
github.com/lestrrat-go/jwx/v2 v2.0.8
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/valyala/fasttemplate v1.2.2
github.com/valyala/quicktemplate v1.7.0
go.etcd.io/bbolt v1.3.6
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4
golang.org/x/text v0.6.0
go.etcd.io/bbolt v1.3.7
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
golang.org/x/text v0.8.0
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
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
willnorris.com/go/microformats v1.1.1
willnorris.com/go/microformats v1.2.0
)
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/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // 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/httpcc v1.0.1 // 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/lib/pq v1.10.6 // 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/fasthttp v1.44.0 // indirect
go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/tools v0.5.0 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
github.com/valyala/fasthttp v1.45.0 // indirect
go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.7.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.40.0 // 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/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect

107
go.sum
View File

@ -1,15 +1,13 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/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.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek=
github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/brianvoe/gofakeit/v6 v6.20.2 h1:FLloufuC7NcbHqDzVQ42CG9AKryS1gAGCRt8nQRsW+Y=
github.com/brianvoe/gofakeit/v6 v6.20.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
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/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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo=
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/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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/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.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc=
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
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/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
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/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.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/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-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY=
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
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.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
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/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
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/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
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.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
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-20220617035311-6925f38cc365 h1:t9hFvR102YlOqU0fQn1wgwhNvSbHGBbbJxX9JKfU3l0=
go4.org/intern v0.0.0-20220617035311-6925f38cc365/go.mod h1:WXRv3p7T6gzt0CcJm43AAKdKVZmcQbwwC7EwquU5BZU=
go4.org/intern v0.0.0-20230205224052-192e9f60865c h1:b8WZ7Ja8nKegYxfwDLLwT00ZKv4lXAQrw8LYPK+cHSI=
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-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-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-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-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.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4 h1:CNkDRtCj8otM5CFz5jYvbr8ioXX8flVsLfDWEj0M5kk=
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
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.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.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/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-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-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-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.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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.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-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-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-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-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.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
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-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.3/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.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
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-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-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
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=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
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.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
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/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
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/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
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/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
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/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.2 h1:9AaVzJH1Yf0u9iOZRjjuvqxLoGqybqVFbAUC5rvi9u8=
modernc.org/sqlite v1.20.2/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
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/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/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
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/go.mod h1:drlHMC+j/gb5zsttCSwx8qcYsbaRW+wFfE8bK6y+oeY=
willnorris.com/go/microformats v1.1.1 h1:h5tk2luq6KBIRcwMGdksxdeea4GGuWrRFie5460OAbo=
willnorris.com/go/microformats v1.1.1/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0=
willnorris.com/go/microformats v1.2.0 h1:73pzJCLJM69kYE5qsLI9OOC/7sImNVOzya9EQ0+1wmM=
willnorris.com/go/microformats v1.2.0/go.mod h1:RrlwCSvib4qz+JICKiN7rON4phzQ3HAT7j6s4O2cZj4=

View File

@ -48,7 +48,7 @@ func NewHandler(opts NewHandlerOptions) *Handler {
func (h *Handler) Handler() http.Handler {
chain := middleware.Chain{
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)
return head == ""
@ -65,7 +65,7 @@ func (h *Handler) Handler() http.Handler {
CookieHTTPOnly: true,
}),
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)
return r.Method != http.MethodPost ||
@ -154,11 +154,8 @@ func (h *Handler) handleAuthorize(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
web.WriteTemplate(w, &web.ErrorPage{
BaseOf: baseOf,
Error: domain.NewError(
domain.ErrorCodeInvalidClient,
"requested redirect_uri is not registered on client_id side",
"",
),
Error: domain.NewError(domain.ErrorCodeInvalidClient, "requested redirect_uri is not"+
" registered on client_id side", ""),
})
return

View File

@ -11,10 +11,6 @@ import (
type (
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.
ClientID domain.ClientID `form:"client_id"`
@ -28,12 +24,9 @@ type (
// The hashing method used to calculate the code challenge.
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,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"`
// Indicates to the authorization server that an authorization
// code should be returned as the response.
ResponseType domain.ResponseType `form:"response_type"` // code
// A parameter set by the client which will be included when the
// user is redirected back to the client. This is used to
@ -43,19 +36,26 @@ type (
// The code challenge as previously described.
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 {
ClientID domain.ClientID `form:"client_id"`
Me domain.Me `form:"me"`
RedirectURI domain.URL `form:"redirect_uri"`
ResponseType domain.ResponseType `form:"response_type"`
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
Scope domain.Scopes `form:"scope[],omitempty"`
ResponseType domain.ResponseType `form:"response_type"`
Authorize string `form:"authorize"`
CodeChallenge string `form:"code_challenge,omitempty"`
State string `form:"state"`
Provider string `form:"provider"`
Scope domain.Scopes `form:"scope[],omitempty"`
}
AuthExchangeRequest struct {

View File

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

View File

@ -12,26 +12,26 @@ import (
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
type Action struct {
uid string
action string
}
//nolint:gochecknoglobals // structs cannot be constants
var (
ActionUnd = Action{uid: ""} // "und"
ActionUnd = Action{action: ""} // "und"
// ActionRevoke represent action for revoke token.
ActionRevoke = Action{uid: "revoke"} // "revoke"
ActionRevoke = Action{action: "revoke"} // "revoke"
// ActionTicket represent action for TicketAuth extension.
ActionTicket = Action{uid: "ticket"} // "ticket"
ActionTicket = Action{action: "ticket"} // "ticket"
)
var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "")
//nolint:gochecknoglobals
var uidsActions = map[string]Action{
ActionRevoke.uid: ActionRevoke,
ActionTicket.uid: ActionTicket,
ActionRevoke.action: ActionRevoke,
ActionTicket.action: ActionTicket,
}
// 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.
func (a Action) String() string {
if a.uid != "" {
return a.uid
if a.action != "" {
return a.action
}
return common.Und

View File

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

View File

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

View File

@ -11,14 +11,14 @@ import (
type (
Config struct {
Code ConfigCode `envPrefix:"CODE_"`
Database ConfigDatabase `envPrefix:"DATABASE_"`
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
JWT ConfigJWT `envPrefix:"JWT_"`
Server ConfigServer `envPrefix:"SERVER_"`
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
Database ConfigDatabase `envPrefix:"DATABASE_"`
Name string `env:"NAME" envDefault:"IndieAuth"`
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 {
@ -45,10 +45,10 @@ type (
}
ConfigJWT struct {
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"` // 1h
Algorithm string `env:"ALGORITHM" envDefault:"HS256"` // HS256
Algorithm string `env:"ALGORITHM" envDefault:"HS256"`
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 {

View File

@ -15,6 +15,8 @@ type (
// Error describes the format of a typical IndieAuth error.
//nolint:tagliatelle // RFC 6749 section 5.2
Error struct {
frame xerrors.Frame `json:"-"`
// A single error code.
Code ErrorCode `json:"error"`
@ -31,25 +33,23 @@ type (
// authorization request. The exact value received from the
// client.
State string `json:"-"`
frame xerrors.Frame `json:"-"`
}
// ErrorCode represent error code described in RFC 6749.
ErrorCode struct {
uid string
errorCode string
}
)
var (
// ErrorCodeUnd describes an unrecognized error code.
ErrorCodeUnd = ErrorCode{uid: ""} // "und"
ErrorCodeUnd = ErrorCode{errorCode: ""} // "und"
// ErrorCodeAccessDenied describes the access_denied error code.
//
// RFC 6749 section 4.1.2.1: The resource owner or authorization server
// denied the request.
ErrorCodeAccessDenied = ErrorCode{uid: "access_denied"} // "access_denied"
ErrorCodeAccessDenied = ErrorCode{errorCode: "access_denied"} // "access_denied"
// ErrorCodeInvalidClient describes the invalid_client error code.
//
@ -65,7 +65,7 @@ var (
// HTTP 401 (Unauthorized) status code and include the
// "WWW-Authenticate" response header field matching the authentication
// 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.
//
@ -73,7 +73,7 @@ var (
// authorization code, resource owner credentials) or refresh token is
// invalid, expired, revoked, does not match the redirection URI used in
// 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.
//
@ -88,7 +88,7 @@ var (
// repeats a parameter, includes multiple credentials, utilizes more
// than one mechanism for authenticating the client, or is otherwise
// malformed.
ErrorCodeInvalidRequest = ErrorCode{uid: "invalid_request"} // "invalid_request"
ErrorCodeInvalidRequest = ErrorCode{errorCode: "invalid_request"} // "invalid_request"
// ErrorCodeInvalidScope describes the invalid_scope error code.
//
@ -97,7 +97,7 @@ var (
//
// RFC 6749 section 5.2: The requested scope is invalid, unknown,
// 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.
//
@ -105,7 +105,7 @@ var (
// unexpected condition that prevented it from fulfilling the request.
// (This error code is needed because a 500 Internal Server Error HTTP
// 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.
//
@ -114,7 +114,7 @@ var (
// maintenance of the server. (This error code is needed because a 503
// Service Unavailable HTTP status code cannot be returned to the client
// 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.
//
@ -123,53 +123,53 @@ var (
//
// RFC 6749 section 5.2: The authenticated client is not authorized to
// 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.
//
// RFC 6749 section 5.2: The authorization grant type is not supported
// 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.
//
// RFC 6749 section 4.1.2.1: The authorization server does not support
// 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.
//
// 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.
//
// 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", "")
//nolint:gochecknoglobals // maps cannot be constants
var uidsErrorCodes = map[string]ErrorCode{
ErrorCodeAccessDenied.uid: ErrorCodeAccessDenied,
ErrorCodeInsufficientScope.uid: ErrorCodeInsufficientScope,
ErrorCodeInvalidClient.uid: ErrorCodeInvalidClient,
ErrorCodeInvalidGrant.uid: ErrorCodeInvalidGrant,
ErrorCodeInvalidRequest.uid: ErrorCodeInvalidRequest,
ErrorCodeInvalidScope.uid: ErrorCodeInvalidScope,
ErrorCodeInvalidToken.uid: ErrorCodeInvalidToken,
ErrorCodeServerError.uid: ErrorCodeServerError,
ErrorCodeTemporarilyUnavailable.uid: ErrorCodeTemporarilyUnavailable,
ErrorCodeUnauthorizedClient.uid: ErrorCodeUnauthorizedClient,
ErrorCodeUnsupportedGrantType.uid: ErrorCodeUnsupportedGrantType,
ErrorCodeUnsupportedResponseType.uid: ErrorCodeUnsupportedResponseType,
ErrorCodeAccessDenied.errorCode: ErrorCodeAccessDenied,
ErrorCodeInsufficientScope.errorCode: ErrorCodeInsufficientScope,
ErrorCodeInvalidClient.errorCode: ErrorCodeInvalidClient,
ErrorCodeInvalidGrant.errorCode: ErrorCodeInvalidGrant,
ErrorCodeInvalidRequest.errorCode: ErrorCodeInvalidRequest,
ErrorCodeInvalidScope.errorCode: ErrorCodeInvalidScope,
ErrorCodeInvalidToken.errorCode: ErrorCodeInvalidToken,
ErrorCodeServerError.errorCode: ErrorCodeServerError,
ErrorCodeTemporarilyUnavailable.errorCode: ErrorCodeTemporarilyUnavailable,
ErrorCodeUnauthorizedClient.errorCode: ErrorCodeUnauthorizedClient,
ErrorCodeUnsupportedGrantType.errorCode: ErrorCodeUnsupportedGrantType,
ErrorCodeUnsupportedResponseType.errorCode: ErrorCodeUnsupportedResponseType,
}
// String returns a string representation of the error code.
func (ec ErrorCode) String() string {
if ec.uid != "" {
return ec.uid
if ec.errorCode != "" {
return ec.errorCode
}
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.
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
@ -231,6 +231,8 @@ func (e Error) SetReirectURI(u *url.URL) {
return
}
q := u.Query()
for key, val := range map[string]string{
"error": e.Code.String(),
"error_description": e.Description,
@ -241,8 +243,10 @@ func (e Error) SetReirectURI(u *url.URL) {
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

View File

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

View File

@ -11,14 +11,14 @@ import (
// Me is a URL user identifier.
type Me struct {
id *url.URL
me *url.URL
}
// ParseMe parse string as me URL identifier.
//
//nolint:funlen,cyclop
func ParseMe(raw string) (*Me, error) {
id, err := url.Parse(raw)
me, err := url.Parse(raw)
if err != nil {
return nil, NewError(
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(
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",
"",
)
}
if id.Path == "" {
id.Path = "/"
if me.Path == "" {
me.Path = "/"
}
if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") {
if strings.Contains(me.Path, "/.") || strings.Contains(me.Path, "/..") {
return nil, NewError(
ErrorCodeInvalidRequest,
"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",
"",
)
}
if id.Fragment != "" {
if me.Fragment != "" {
return nil, NewError(
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",
"",
)
}
if id.User != nil {
if me.User != nil {
return nil, NewError(
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",
"",
)
}
if id.Host == "" {
if me.Host == "" {
return nil, NewError(
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",
"",
)
}
if _, port, _ := net.SplitHostPort(id.Host); port != "" {
if _, port, _ := net.SplitHostPort(me.Host); port != "" {
return nil, NewError(
ErrorCodeInvalidRequest,
"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(
ErrorCodeInvalidRequest,
"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.
@ -108,7 +108,7 @@ func TestMe(tb testing.TB, src string) *Me {
tb.Fatal(err)
}
return &Me{id: u}
return &Me{me: u}
}
// 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.
func (m Me) URL() *url.URL {
if m.id == nil {
if m.me == nil {
return nil
}
out, _ := url.Parse(m.id.String())
out, _ := url.Parse(m.me.String())
return out
}
// String returns string representation of me.
func (m Me) String() string {
if m.id != nil {
return m.id.String()
if m.me != nil {
return m.me.String()
}
return ""

View File

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

View File

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

View File

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

View File

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

View File

@ -19,22 +19,22 @@ import (
//nolint:lll,tagliatelle
type Response struct {
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
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"`
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
AuthorizationEndpoint string `json:"authorization_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"`
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"`
}

View File

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

View File

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

View File

@ -15,25 +15,6 @@ import (
type (
// JWTConfig defines the config for JWT middleware.
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.
// This is one of the three options to provide a token
// validation key. The order of precedence is a user-defined
@ -52,49 +33,24 @@ type (
// provided.
SigningKeys map[string]any
// Signing method used to check the token's signing algorithm.
//
// Optional. Default value HS256.
SigningMethod jwa.SignatureAlgorithm
// SuccessHandler defines a function which is executed for a
// valid token.
SuccessHandler JWTSuccessHandler
// Context key to store user information from the token into
// context.
//
// Optional. Default value "user".
ContextKey string
// ErrorHandler defines a function which is executed for an
// invalid token. It may be used to define a custom JWT error.
ErrorHandler JWTErrorHandler
// 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
// ErrorHandlerWithContext is almost identical to ErrorHandler,
// but it's passed the current context.
ErrorHandlerWithContext JWTErrorHandlerWithContext
// 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
// BeforeFunc defines a function which is executed just before
// the middleware.
BeforeFunc BeforeFunc
// 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
// AuthScheme to be used in the Authorization header.
//
// Optional. Default value "Bearer".
AuthScheme string
// Skipper defines a function to skip middleware.
Skipper Skipper
// KeyFunc defines a user-defined function that supplies the
// 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
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
// be called when ErrorHandlerWithContext decides to ignore the
// error (by returning `nil`). This is useful when parts of your

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import (
"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"}
func TestCreate(t *testing.T) {

View File

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

View File

@ -19,9 +19,9 @@ import (
)
type Handler struct {
config domain.Config
matcher language.Matcher
tickets ticket.UseCase
config domain.Config
}
func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler {
@ -36,7 +36,7 @@ func (h *Handler) Handler() http.Handler {
//nolint:exhaustivestruct
chain := middleware.Chain{
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)
return r.Method == http.MethodPost && head == "ticket"

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import (
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"}
func TestCreate(t *testing.T) {

View File

@ -18,18 +18,18 @@ type (
}
TokenRefreshRequest struct {
GrantType domain.GrantType `form:"grant_type"` // refresh_token
// The client ID that was used when the refresh token was issued.
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
// than the original access token. If omitted, is treated as
// equal to the original scopes granted.
Scope domain.Scopes `form:"scope"`
// The refresh token previously offered to the client.
RefreshToken string `form:"refresh_token"`
}
TokenRevocationRequest struct {

View File

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

View File

@ -58,6 +58,7 @@ func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
// WARN(toby3d): If the token is not valid, the endpoint still
// MUST return a 200 Response.
_ = encoder.Encode(err) //nolint:errchkjson
w.WriteHeader(http.StatusOK)
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",
"https://indieauth.net/source/#user-information",
))
w.WriteHeader(http.StatusForbidden)
return
}
resp := new(UserInformationResponse)
if userInfo == nil {
_ = encoder.Encode(resp) //nolint:errchkjson
//nolint:errchkjson
_ = encoder.Encode(NewUserInformationResponse(userInfo,
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)
}

View File

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

View File

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

51
main.go
View File

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

View File

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

View File

@ -27,10 +27,16 @@ func NewReader(src io.Reader) *Reader {
}
// Reset discards the Reader's state and makes it equivalent to the result of
// 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.
// Error is always nil
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)
r.src = src
if r.buf == nil {

View File

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

View File

@ -397,6 +397,13 @@ func rTime(ra *rand.Rand, t reflect.StructField, v reflect.Value, tag string) er
// Generate time
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
timeFormat, timeFormatOK := t.Tag.Lookup("format")
if timeFormatOK {

View File

@ -1,8 +1,8 @@
# 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)
[![](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.
@ -11,7 +11,7 @@ A simple and zero-dependencies library to parse environment variables into struc
Get the module with:
```sh
go get github.com/caarlos0/env/v6
go get github.com/caarlos0/env/v7
```
The usage looks like this:
@ -23,7 +23,7 @@ import (
"fmt"
"time"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
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}
```
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**.
## Caveats
> **Warning**
>
> **This is important!**
- _Unexported fields_ are **ignored**
## Supported types and defaults
@ -80,11 +87,15 @@ Complete list:
- `encoding.TextUnmarshaler`
- `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
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
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
`*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.
### A note about `TextUnmarshaler` and `time.Time`
@ -148,7 +159,7 @@ type config struct {
## 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"`).
Example:
@ -185,7 +196,7 @@ package main
import (
"fmt"
"time"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type config struct {
@ -217,6 +228,46 @@ $ SECRET=/tmp/secret \
## 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
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
@ -232,7 +283,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -251,7 +302,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -268,7 +319,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -285,7 +336,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -302,7 +353,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -318,7 +369,7 @@ type ComplexConfig struct {
func main() {
cfg := ComplexConfig{}
if err := Parse(&cfg, Options{
if err := Parse(&cfg, Options{
Prefix: "T_",
Environment: map[string]string{
"T_FOO_HOME": "/foo",
@ -336,7 +387,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -352,7 +403,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -374,7 +425,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -391,7 +442,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
type Config struct {
@ -409,7 +460,7 @@ func main() {
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg.envData)
fmt.Printf("%+v\n", cfg)
}
```
@ -425,7 +476,7 @@ import (
"fmt"
"log"
"github.com/caarlos0/env/v6"
"github.com/caarlos0/env/v7"
)
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](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)

View File

@ -2,7 +2,6 @@ package env
import (
"encoding"
"errors"
"fmt"
"net/url"
"os"
@ -10,14 +9,11 @@ import (
"strconv"
"strings"
"time"
"unicode"
)
// nolint: gochecknoglobals
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{
reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v)
@ -79,14 +75,14 @@ func defaultTypeParsers() map[reflect.Type]ParserFunc {
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v)
if err != nil {
return nil, fmt.Errorf("unable to parse URL: %v", err)
return nil, newParseValueError("unable to parse URL", err)
}
return *u, nil
},
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v)
if err != nil {
return nil, fmt.Errorf("unable to parse duration: %v", err)
return nil, newParseValueError("unable to parse duration", err)
}
return s, err
},
@ -107,15 +103,20 @@ type Options struct {
// TagName specifies another tagname to use rather than the default env.
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
// OnSet allows to run a function when a value is set
// OnSet allows to run a function when a value is set.
OnSet OnSetFn
// Prefix define a prefix for each key
// Prefix define a prefix for each key.
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.
configured bool
}
@ -150,6 +151,7 @@ func configure(opts []Options) []Options {
if item.Prefix != "" {
opt.Prefix = item.Prefix
}
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
opt.RequiredIfNoDef = item.RequiredIfNoDef
}
@ -183,11 +185,11 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr {
return ErrNotAStructPtr
return newAggregateError(NotStructPtrError{})
}
ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct {
return ErrNotAStructPtr
return newAggregateError(NotStructPtrError{})
}
parsers := defaultTypeParsers()
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 {
refType := ref.Type()
var agrErr aggregateError
var agrErr AggregateError
for i := 0; i < refType.NumField(); i++ {
refField := ref.Field(i)
refTypeField := refType.Field(i)
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
if val, ok := err.(aggregateError); ok {
agrErr.errors = append(agrErr.errors, val.errors...)
if val, ok := err.(AggregateError); ok {
agrErr.Errors = append(agrErr.Errors, val.Errors...)
} 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
}
@ -226,7 +228,7 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
if !refField.CanSet() {
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)...)
}
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
}
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) {
var exists bool
var isDefault bool
@ -258,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
required := opts[0].RequiredIfNoDef
prefix := opts[0].Prefix
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
if ownKey == "" && opts[0].UseFieldNameByDefault {
ownKey = toEnvName(field.Name)
}
key := prefix + ownKey
for _, tag := range tags {
switch tag {
@ -272,7 +290,7 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
case "notEmpty":
notEmpty = true
default:
return "", fmt.Errorf("tag option %q not supported", tag)
return "", newNoSupportedTagOptionError(tag)
}
}
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 {
return "", fmt.Errorf(`required environment variable %q is not set`, key)
return "", newEnvVarIsNotSet(key)
}
if notEmpty && val == "" {
return "", fmt.Errorf("environment variable %q should not be empty", key)
return "", newEmptyEnvVarError(key)
}
if loadFile && val != "" {
filename := val
val, err = getFromFile(filename)
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 {
case (!exists || key == "") && defExists:
return defaultValue, true, true
case exists && value == "" && defExists:
return defaultValue, true, true
case !exists:
return "", false, false
}
@ -369,8 +389,11 @@ func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[
return nil
}
if field.Kind() == reflect.Slice {
switch field.Kind() {
case reflect.Slice:
return handleSlice(field, value, sf, funcMap)
case reflect.Map:
return handleMap(field, value, sf, funcMap)
}
return newNoParserError(sf)
@ -417,6 +440,54 @@ func handleSlice(field reflect.Value, value string, sf reflect.StructField, func
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 {
if reflect.Ptr == field.Kind() {
if field.IsNil() {
@ -459,26 +530,6 @@ func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.Struct
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 {
subOpts := make([]Options, len(opts))
copy(subOpts, opts)
@ -487,18 +538,3 @@ func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
}
return subOpts
}
type aggregateError struct {
errors []error
}
func (e aggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}

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

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

View File

@ -1,48 +1,82 @@
# Changelog
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/),
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
- 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]
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
## [0.4.0] - 2018-11-21
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
### Added
- Go module support by [@ChrisHines]
- CHANGELOG by [@ChrisHines]
### Changed
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
- On panic while printing, attempt to print panic value by [@bboreham]
## [0.3.0] - 2016-11-15
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
### Added
- Pool buffers for quoted strings and byte slices by [@nussjustin]
### Fixed
- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
## [0.2.0] - 2016-05-08
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
### Added
- Encoder.EncodeKeyvals by [@ChrisHines]
## [0.1.0] - 2016-03-28
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
### Added
- Encoder by [@ChrisHines]
- Decoder 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
[@bboreham]: https://github.com/bboreham
[@judwhite]: https://github.com/judwhite
[@nussjustin]: https://github.com/nussjustin
[@alexanderjophus]: https://github.com/alexanderjophus

View File

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

View File

@ -29,6 +29,23 @@ func NewDecoder(r io.Reader) *Decoder {
return dec
}
// 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
// parsed with the ScanKeyval method. It returns false when decoding stops,
// either by reaching the end of the input or an error. After ScanRecord

View File

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

View File

@ -19,7 +19,9 @@ type arrayDecoder struct {
}
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{
valueDecoder: dec,
elemType: elemType,

View File

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

View File

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

View File

@ -397,7 +397,10 @@ func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *
func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode {
// firstField is special StructHead operation for anonymous structure.
// 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 {
lastField = lastField.NextField
}
@ -437,11 +440,6 @@ func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
}
if isEndField {
endField := fieldCodes.Last()
if isEmbeddedStruct(field) {
firstField.End = endField
lastField := c.lastAnonymousFieldCode(firstField)
lastField.NextField = endField
}
if len(codes) > 0 {
codes.First().End = endField
} else {
@ -698,7 +696,15 @@ func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) O
Indent: ctx.indent,
}
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)
ctx.incOpcodeIndex()
return codes

View File

@ -617,6 +617,13 @@ func (c *Compiler) structCode(typ *runtime.Type, isPtr bool) (*StructCode, error
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) {
field := tag.Field
fieldType := runtime.Type2RType(field.Type)
@ -626,7 +633,7 @@ func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTa
key: tag.Key,
tag: tag,
offset: field.Offset,
isAnonymous: field.Anonymous && !tag.IsTaggedKey,
isAnonymous: field.Anonymous && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct,
isTaggedKey: tag.IsTaggedKey,
isNilableType: c.isNilableType(fieldType),
isNilCheck: true,

View File

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

View File

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

View File

@ -294,7 +294,6 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
}
offset = 0
cGain := 0
if d.chain < 100 {
for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] {
@ -322,10 +321,14 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
return
}
// Minimum gain to accept a match.
cGain := 4
// 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.
// Matches must be better than this.
for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] {
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
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 {
length = n
offset = pos - i
@ -490,27 +493,103 @@ func (d *compressor) deflateLazy() {
}
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
// Offset of 2 seems to yield best results.
// Skip forward a number of bytes.
// Offset of 2 seems to yield best results. 3 is sometimes better.
const checkOff = 2
prevIndex := s.index - 1
if prevIndex+prevLength+checkOff < s.maxInsertIndex {
end := lookahead
if lookahead > maxMatchLength {
end = maxMatchLength
}
end += prevIndex
idx := prevIndex + prevLength - (4 - checkOff)
h := hash4(d.window[idx:])
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength + (4 - checkOff)
if ch2 > minIndex {
length := matchLen(d.window[prevIndex:end], d.window[ch2:])
// It seems like a pure length metric is best.
if length > prevLength {
prevLength = length
prevOffset = prevIndex - ch2
// Check all, except full length
if prevLength < maxMatchLength-checkOff {
prevIndex := s.index - 1
if prevIndex+prevLength < s.maxInsertIndex {
end := lookahead
if lookahead > maxMatchLength+checkOff {
end = maxMatchLength + checkOff
}
end += prevIndex
// Hash at match end.
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 {
prevLength = length
prevOffset = prevIndex - ch2
// Extend back...
for i := checkOff - 1; i >= 0; i-- {
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i] {
// Emit tokens we "owe"
for j := 0; j <= i; j++ {
d.tokens.AddLiteral(d.window[prevIndex+j])
if d.tokens.n == maxFlateBlockTokens {
// The block includes the current character
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
return
}
d.tokens.Reset()
}
s.index++
if s.index < s.maxInsertIndex {
h := hash4(d.window[s.index:])
ch := s.hashHead[h]
s.chainHead = int(ch)
s.hashPrev[s.index&windowMask] = ch
s.hashHead[h] = uint32(s.index + s.hashOffset)
}
}
break
} else {
prevLength++
}
}
} else if false {
// Check one further ahead.
// Only rarely better, disabled for now.
prevIndex++
h := hash4(d.window[prevIndex+prevLength:])
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength
if prevIndex-ch2 != prevOffset && ch2 > minIndex+checkOff {
length := matchLen(d.window[prevIndex+checkOff:end], d.window[ch2+checkOff:])
// It seems like a pure length metric is best.
if length > prevLength+checkOff {
prevLength = length
prevOffset = prevIndex - ch2
prevIndex--
// Extend back...
for i := checkOff; i >= 0; i-- {
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i-1] {
// Emit tokens we "owe"
for j := 0; j <= i; j++ {
d.tokens.AddLiteral(d.window[prevIndex+j])
if d.tokens.n == maxFlateBlockTokens {
// The block includes the current character
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
return
}
d.tokens.Reset()
}
s.index++
if s.index < s.maxInsertIndex {
h := hash4(d.window[s.index:])
ch := s.hashHead[h]
s.chainHead = int(ch)
s.hashPrev[s.index&windowMask] = ch
s.hashHead[h] = uint32(s.index + s.hashOffset)
}
}
break
} else {
prevLength++
}
}
}
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,30 @@
package bigfft
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)
//go:linkname subVV math/big.subVV
func subVV(z, x, y []Word) (c Word)
//go:linkname addVW math/big.addVW
func addVW(z, x []Word, y Word) (c Word)
//go:linkname subVW math/big.subVW
func subVW(z, x []Word, y Word) (c Word)
//go:linkname shlVU math/big.shlVU
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)
//go:linkname addMulVVW math/big.addMulVVW
func addMulVVW(z, x []Word, y Word) (c Word)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# fasthttp [![GoDoc](https://godoc.org/github.com/valyala/fasthttp?status.svg)](http://godoc.org/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)
# fasthttp [![GoDoc](https://pkg.go.dev/badge/github.com/valyala/fasthttp)](https://pkg.go.dev/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)
![FastHTTP  Fastest and reliable HTTP implementation in Go](https://github.com/fasthttp/docs-assets/raw/master/banner@0.5.png)
@ -22,9 +22,9 @@ connections per physical server.
[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)
@ -40,7 +40,7 @@ connections per physical server.
[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.
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.
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
all of the fasthttp advantages (especially high performance :) ).
Important points:
* Fasthttp works with [RequestHandler functions](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
instead of objects implementing [Handler interface](https://golang.org/pkg/net/http/#Handler).
* Fasthttp works with [RequestHandler functions](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
instead of objects implementing [Handler interface](https://pkg.go.dev/net/http#Handler).
Fortunately, it is easy to pass bound struct methods to fasthttp:
```go
@ -211,8 +211,8 @@ Fortunately, it is easy to pass bound struct methods to fasthttp:
fasthttp.ListenAndServe(":8081", fastHTTPHandler)
```
* The [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
accepts only one argument - [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx).
* The [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
accepts only one argument - [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx).
It contains all the functionality required for http request processing
and response writing. Below is an example of a simple request handler conversion
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
with fasthttp support:
@ -347,78 +347,78 @@ with fasthttp support:
ctx *fasthttp.RequestCtx
)
```
* r.Body -> [ctx.PostBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody)
* r.URL.Path -> [ctx.Path()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Path)
* r.URL -> [ctx.URI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.URI)
* r.Method -> [ctx.Method()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Method)
* r.Header -> [ctx.Request.Header](https://godoc.org/github.com/valyala/fasthttp#RequestHeader)
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Peek)
* r.Host -> [ctx.Host()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Host)
* r.Form -> [ctx.QueryArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
[ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.PostForm -> [ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.FormValue() -> [ctx.FormValue()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormValue)
* r.FormFile() -> [ctx.FormFile()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormFile)
* r.MultipartForm -> [ctx.MultipartForm()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
* r.RemoteAddr -> [ctx.RemoteAddr()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
* r.RequestURI -> [ctx.RequestURI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RequestURI)
* r.TLS -> [ctx.IsTLS()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.IsTLS)
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Cookie)
* r.Referer() -> [ctx.Referer()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Referer)
* r.UserAgent() -> [ctx.UserAgent()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.UserAgent)
* w.Header() -> [ctx.Response.Header](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader)
* w.Header().Set() -> [ctx.Response.Header.Set()](https://godoc.org/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("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
* w.Write() -> [ctx.Write()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Write),
[ctx.SetBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBody),
[ctx.SetBodyStream()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
[ctx.SetBodyStreamWriter()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
* w.WriteHeader() -> [ctx.SetStatusCode()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack)
* http.Error() -> [ctx.Error()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Error)
* http.FileServer() -> [fasthttp.FSHandler()](https://godoc.org/github.com/valyala/fasthttp#FSHandler),
[fasthttp.FS](https://godoc.org/github.com/valyala/fasthttp#FS)
* http.ServeFile() -> [fasthttp.ServeFile()](https://godoc.org/github.com/valyala/fasthttp#ServeFile)
* http.Redirect() -> [ctx.Redirect()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Redirect)
* http.NotFound() -> [ctx.NotFound()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.NotFound)
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://godoc.org/github.com/valyala/fasthttp#PathRewriteFunc)
* r.Body -> [ctx.PostBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody)
* r.URL.Path -> [ctx.Path()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Path)
* r.URL -> [ctx.URI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.URI)
* r.Method -> [ctx.Method()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Method)
* r.Header -> [ctx.Request.Header](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader)
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Peek)
* r.Host -> [ctx.Host()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Host)
* r.Form -> [ctx.QueryArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
[ctx.PostArgs()](https://pkg.go.dev/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://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormValue)
* r.FormFile() -> [ctx.FormFile()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormFile)
* r.MultipartForm -> [ctx.MultipartForm()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
* r.RemoteAddr -> [ctx.RemoteAddr()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
* r.RequestURI -> [ctx.RequestURI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RequestURI)
* r.TLS -> [ctx.IsTLS()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.IsTLS)
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Cookie)
* r.Referer() -> [ctx.Referer()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Referer)
* r.UserAgent() -> [ctx.UserAgent()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.UserAgent)
* w.Header() -> [ctx.Response.Header](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader)
* 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://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetContentType)
* w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
* w.Write() -> [ctx.Write()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Write),
[ctx.SetBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBody),
[ctx.SetBodyStream()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
[ctx.SetBodyStreamWriter()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
* w.WriteHeader() -> [ctx.SetStatusCode()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)
* http.Error() -> [ctx.Error()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Error)
* http.FileServer() -> [fasthttp.FSHandler()](https://pkg.go.dev/github.com/valyala/fasthttp#FSHandler),
[fasthttp.FS](https://pkg.go.dev/github.com/valyala/fasthttp#FS)
* http.ServeFile() -> [fasthttp.ServeFile()](https://pkg.go.dev/github.com/valyala/fasthttp#ServeFile)
* http.Redirect() -> [ctx.Redirect()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Redirect)
* http.NotFound() -> [ctx.NotFound()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.NotFound)
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://pkg.go.dev/github.com/valyala/fasthttp#PathRewriteFunc)
* *VERY IMPORTANT!* Fasthttp disallows holding references
to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) or to its'
members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
Otherwise [data races](http://blog.golang.org/race-detector) are inevitable.
to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) or to its'
members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
Otherwise [data races](http://go.dev/blog/race-detector) are inevitable.
Carefully inspect all the net/http request handlers converted to fasthttp whether
they retain references to RequestCtx or to its' members after returning.
RequestCtx provides the following _band aids_ for this case:
* Wrap RequestHandler into [TimeoutHandler](https://godoc.org/github.com/valyala/fasthttp#TimeoutHandler).
* Call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
* Wrap RequestHandler into [TimeoutHandler](https://pkg.go.dev/github.com/valyala/fasthttp#TimeoutHandler).
* 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.
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.
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
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)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
you forgot calling [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
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.
While fasthttp is optimized for speed, its' performance may be easily saturated
by slow [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
So [profile](http://blog.golang.org/profiling-go-programs) and optimize your
by slow [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
So [profile](http://go.dev/blog/pprof) and optimize your
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),
[fasthttpadaptor](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor) and
[expvarhandler](https://godoc.org/github.com/valyala/fasthttp/expvarhandler).
* See also [fasthttputil](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttputil),
[fasthttpadaptor](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor) and
[expvarhandler](https://pkg.go.dev/github.com/valyala/fasthttp/expvarhandler).
## 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.
* 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.
@ -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
as possible. Fasthttp API design encourages this.
* [sync.Pool](https://golang.org/pkg/sync/#Pool) is your best friend.
* [Profile your program](http://blog.golang.org/profiling-go-programs)
* [sync.Pool](https://pkg.go.dev/sync#Pool) is your best friend.
* [Profile your program](http://go.dev/blog/pprof)
in production.
`go tool pprof --alloc_objects your-program mem.pprof` usually gives better
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
allocation+copy. Fasthttp API provides functions for both `[]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)
for more details.
* 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
[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
@ -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.
* [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
* [http2curl](https://github.com/li-jin-gou/http2curl) - A tool to convert fasthttp requests to curl command line
## 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
above for more details. Also certain net/http API parts are suboptimal
for use:
* Compare [net/http connection hijacking](https://golang.org/pkg/net/http/#Hijacker)
to [fasthttp connection hijacking](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack).
* Compare [net/http Request.Body reading](https://golang.org/pkg/net/http/#Request)
to [fasthttp request body reading](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody).
* Compare [net/http connection hijacking](https://pkg.go.dev/net/http#Hijacker)
to [fasthttp connection hijacking](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack).
* Compare [net/http Request.Body reading](https://pkg.go.dev/net/http#Request)
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?*
[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.
* *Are there known net/http advantages comparing to fasthttp?*
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 handles more HTTP corner cases.
* 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
doing this check the following in your code:
* Make sure there are no references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx)
or to its' members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
* Make sure you call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
if there are 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://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
* Make sure you call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
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.
* *I didn't find an answer for my question here*

View File

@ -1,6 +1,6 @@
### 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.
### Reporting a Security Bug

View File

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

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

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

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

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

View File

@ -10,10 +10,8 @@ import (
"io"
"math"
"net"
"reflect"
"sync"
"time"
"unsafe"
)
// 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.
//
// dst may point to src. In this case src will be overwritten.

View File

@ -1,5 +1,3 @@
// go:build !windows || !race
package fasthttp
import (
@ -179,7 +177,7 @@ var defaultClient Client
//
// The fields of a Client should not be changed while it is in use.
type Client struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// 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
// the given timeout.
// Immediately returns ErrTimeout if timeout value is negative.
//
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
// 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.
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
req.timeout = timeout
if req.timeout < 0 {
return ErrTimeout
}
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
// the given deadline.
// Immediately returns ErrTimeout if the deadline has already been reached.
//
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
// 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.
func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
req.timeout = time.Until(deadline)
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp)
}
@ -470,9 +476,9 @@ func (c *Client) Do(req *Request, resp *Response) error {
host := uri.Host()
isTLS := false
if uri.isHttps() {
if uri.isHTTPS() {
isTLS = true
} else if !uri.isHttp() {
} else if !uri.isHTTP() {
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 err := c.ConfigureClient(hc); err != nil {
c.mLock.Unlock()
return err
}
}
@ -642,7 +649,7 @@ const (
//
// It is safe calling HostClient methods from concurrently running goroutines.
type HostClient struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Comma-separated list of upstream HTTP server host addresses,
// which are passed to Dial in a round-robin manner.
@ -687,7 +694,7 @@ type HostClient struct {
// listed in Addr.
//
// 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.
MaxConns int
@ -811,7 +818,7 @@ type HostClient struct {
pendingRequests int32
// 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
connsCleanerRun bool
@ -1139,6 +1146,7 @@ func ReleaseResponse(resp *Response) {
//
// ErrTimeout is returned if the response wasn't returned during
// the given timeout.
// Immediately returns ErrTimeout if timeout value is negative.
//
// ErrNoFreeConns is returned if all HostClient.MaxConns connections
// to the host are busy.
@ -1152,6 +1160,9 @@ func ReleaseResponse(resp *Response) {
// try setting a ReadTimeout.
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
req.timeout = timeout
if req.timeout < 0 {
return ErrTimeout
}
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
// the given deadline.
// Immediately returns ErrTimeout if the deadline has already been reached.
//
// ErrNoFreeConns is returned if all HostClient.MaxConns connections
// 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.
func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
req.timeout = time.Until(deadline)
if req.timeout < 0 {
return ErrTimeout
}
return c.Do(req, resp)
}
@ -1308,7 +1323,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
req.secureErrorLogMessage = c.SecureErrorLogMessage
req.Header.secureErrorLogMessage = c.SecureErrorLogMessage
if c.IsTLS != req.URI().isHttps() {
if c.IsTLS != req.URI().isHTTPS() {
return false, ErrHostClientRedirectToDifferentScheme
}
@ -1519,6 +1534,7 @@ func (c *HostClient) acquireConn(reqTimeout time.Duration, connectionClose bool)
c.conns[n-1] = nil
c.conns = c.conns[:n-1]
default:
c.connsLock.Unlock()
return nil, ErrConnPoolStrategyNotImpl
}
}
@ -2003,11 +2019,11 @@ func AddMissingPort(addr string, isTLS bool) string {
return addr
}
isIp6 := addr[0] == '['
if isIp6 {
isIP6 := addr[0] == '['
if isIP6 {
// if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port
isIp6WithoutPort := addr[addrLen-1] == ']'
if !isIp6WithoutPort {
isIP6WithoutPort := addr[addrLen-1] == ']'
if !isIP6WithoutPort {
return addr
}
} else { // IPv4
@ -2139,7 +2155,7 @@ func (q *wantConnQueue) peekFront() *wantConn {
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.
func (q *wantConnQueue) clearFront() (cleaned bool) {
for {
@ -2165,7 +2181,7 @@ func (q *wantConnQueue) clearFront() (cleaned bool) {
// It is safe calling PipelineClient methods from concurrently running
// goroutines.
type PipelineClient struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Address of the host to connect to.
Addr string
@ -2279,7 +2295,7 @@ type PipelineClient struct {
}
type pipelineConnClient struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
Addr string
Name string

View File

@ -402,7 +402,7 @@ var (
func newCompressWriterPoolMap() []*sync.Pool {
// 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,
// so the fit [0..11].
var m []*sync.Pool
@ -413,7 +413,7 @@ func newCompressWriterPoolMap() []*sync.Pool {
}
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
// the given minCompressRatio.
b := bytebufferpool.Get()

View File

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

View File

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

View File

@ -223,7 +223,7 @@ func NewPathPrefixStripper(prefixSize int) PathRewriteFunc {
//
// It is prohibited copying FS values. Create new values instead.
type FS struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Path to the root directory to serve files from.
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.
// 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.
if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {
// The compressed file became stale. Re-create it.

View File

@ -24,7 +24,7 @@ const (
// ResponseHeader instance MUST NOT be used from concurrently running
// goroutines.
type ResponseHeader struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
disableNormalizing bool
noHTTP11 bool
@ -59,7 +59,7 @@ type ResponseHeader struct {
// RequestHeader instance MUST NOT be used from concurrently running
// goroutines.
type RequestHeader struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
disableNormalizing bool
noHTTP11 bool
@ -109,7 +109,7 @@ func (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) {
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 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.
//
// 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) {
h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue)
}
@ -3083,7 +3083,7 @@ func (s *headerScanner) next() bool {
n++
for len(s.b) > n && s.b[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
// to a minus value, it means it's invalid, and will find the newline again.
s.nextNewLine--

View File

@ -37,7 +37,7 @@ func SetBodySizePoolLimit(reqBodyLimit, respBodyLimit int) {
//
// Request instance MUST NOT be used from concurrently running goroutines.
type Request struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Request header
//
@ -81,7 +81,7 @@ type Request struct {
//
// Response instance MUST NOT be used from concurrently running goroutines.
type Response struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Response header
//
@ -771,7 +771,7 @@ func (req *Request) ResetBody() {
func (req *Request) CopyTo(dst *Request) {
req.copyToSkipBody(dst)
if req.bodyRaw != nil {
dst.bodyRaw = req.bodyRaw
dst.bodyRaw = append(dst.bodyRaw[:0], req.bodyRaw...)
if dst.body != nil {
dst.body.Reset()
}
@ -803,7 +803,7 @@ func (req *Request) copyToSkipBody(dst *Request) {
func (resp *Response) CopyTo(dst *Response) {
resp.copyToSkipBody(dst)
if resp.bodyRaw != nil {
dst.bodyRaw = resp.bodyRaw
dst.bodyRaw = append(dst.bodyRaw, resp.bodyRaw...)
if dst.body != nil {
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.
// Otherwise, you can just use SetRequestURI() and it will be parsed as new URI.
// The URI is copied and can be safely modified later.
func (req *Request) SetURI(newUri *URI) {
if newUri != nil {
newUri.CopyTo(&req.uri)
func (req *Request) SetURI(newURI *URI) {
if newURI != nil {
newURI.CopyTo(&req.uri)
req.parsedURI = true
return
}
@ -893,7 +893,7 @@ func (req *Request) parsePostArgs() {
// isn't 'multipart/form-data'.
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
// 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)
}
if _, err = copyZeroAlloc(vw, fh); err != nil {
_ = fh.Close()
return fmt.Errorf("error when copying form file %q (%q): %w", k, fv.Filename, err)
}
if err = fh.Close(); err != nil {

View File

@ -25,7 +25,7 @@ type BalancingClient interface {
//
// It is safe calling LBClient methods from concurrently running goroutines.
type LBClient struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Clients must contain non-zero clients list.
// 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)
}
// Do calls calculates deadline using LBClient.Timeout and calls DoDeadline
// Do calculates timeout using LBClient.Timeout and calls DoTimeout
// on the least loaded client.
func (cc *LBClient) Do(req *Request, resp *Response) error {
timeout := cc.Timeout

View File

@ -5,7 +5,7 @@ package fasthttp
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
// 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) Unlock() {} //nolint:unused
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

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

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

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

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

View File

@ -20,8 +20,7 @@ import (
var errNoCertOrKeyProvided = errors.New("cert or key has not provided")
var (
// ErrAlreadyServing is returned when calling Serve on a Server
// that is already serving connections.
// Deprecated: ErrAlreadyServing is never returned from Serve. See issue #633.
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 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
// must be limited.
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.
type Server struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Handler for processing incoming requests.
//
@ -377,12 +376,12 @@ type Server struct {
// which will close it when needed.
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
// StreamRequestBody enables request body streaming,
// and calls the handler sooner when given body is
// larger then the current limit.
// larger than the current limit.
StreamRequestBody bool
// 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.
//
// 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
// (for instance, ctx is passed to a separate goroutine and ctx lifetime cannot
// 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
// while other goroutines accessing RequestCtx.
type RequestCtx struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
// Incoming request.
//
@ -1003,7 +1002,7 @@ func (ctx *RequestCtx) PostArgs() *Args {
return ctx.Request.PostArgs()
}
// MultipartForm returns requests's multipart form.
// MultipartForm returns request's multipart form.
//
// Returns ErrNoMultipartForm if request's content-type
// isn't 'multipart/form-data'.
@ -1234,7 +1233,7 @@ func (ctx *RequestCtx) RemoteAddr() net.Addr {
// 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.
func (ctx *RequestCtx) SetRemoteAddr(remoteAddr net.Addr) {
ctx.remoteAddr = remoteAddr
@ -1480,7 +1479,7 @@ func (ctx *RequestCtx) SetBodyStream(bodyStream io.Reader, bodySize int) {
// SetBodyStreamWriter registers the given stream writer for populating
// 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:
//
@ -1864,7 +1863,7 @@ func (s *Server) Serve(ln net.Listener) error {
// 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.
//
// 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 {
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.
// 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) {
s.mu.Lock()
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 err := tc.SetKeepAlive(s.TCPKeepalive); err != nil {
tc.Close() //nolint:errcheck
_ = tc.Close()
return nil, err
}
if s.TCPKeepalivePeriod > 0 {
if err := tc.SetKeepAlivePeriod(s.TCPKeepalivePeriod); err != nil {
tc.Close() //nolint:errcheck
_ = tc.Close()
return nil, err
}
}
@ -2226,7 +2225,7 @@ func (s *Server) serveConn(c net.Conn) (err error) {
// 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.
// If we have to wait for the next request we flush the
// outgoing buffer first so it doesn't have to wait.

View File

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

View File

@ -18,7 +18,7 @@ func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
func stopTimer(t *time.Timer) {
if !t.Stop() {
// 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 {
case <-t.C:
default:
@ -44,7 +44,7 @@ func AcquireTimer(timeout time.Duration) *time.Timer {
// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool
// 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.
func ReleaseTimer(t *time.Timer) {
stopTimer(t)

View File

@ -40,7 +40,7 @@ var uriPool = &sync.Pool{
//
// URI instance MUST NOT be used from concurrently running goroutines.
type URI struct {
noCopy noCopy //nolint:unused,structcheck
noCopy noCopy
pathOriginal []byte
scheme []byte
@ -217,11 +217,11 @@ func (u *URI) SetSchemeBytes(scheme []byte) {
lowercaseBytes(u.scheme)
}
func (u *URI) isHttps() bool {
func (u *URI) isHTTPS() bool {
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)
}
@ -504,7 +504,7 @@ func unescape(s []byte, mode encoding) ([]byte, error) {
// appearing in a URL string, according to RFC 3986.
//
// 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
func shouldEscape(c byte, mode encoding) bool {
@ -631,7 +631,6 @@ func normalizePath(dst, src []byte) []byte {
if filepath.Separator == '\\' {
// remove \.\ parts
b = dst
for {
n := bytes.Index(b, strBackSlashDotBackSlash)
if n < 0 {
@ -652,7 +651,8 @@ func normalizePath(dst, src []byte) []byte {
if nn < 0 {
nn = 0
}
n += len(strSlashDotDotBackSlash) - 1
nn++
n += len(strSlashDotDotBackSlash)
copy(b[nn:], b[n:])
b = b[:len(b)-n+nn]
}

View File

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

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

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

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

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

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

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

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

@ -26,7 +26,7 @@ and setting values. That's it.
[gh_ben]: https://github.com/benbjohnson
[bolt]: https://github.com/boltdb/bolt
[hyc_symas]: https://twitter.com/hyc_symas
[lmdb]: http://symas.com/mdb/
[lmdb]: https://www.symas.com/symas-embedded-database-lmdb
## Project Status
@ -78,14 +78,23 @@ New minor versions may add additional features to the API.
### Installing
To start using Bolt, install Go and run `go get`:
```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
your `$GOBIN` path.
This will retrieve the library and update your `go.mod` and `go.sum` files.
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
@ -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.
* [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.
* [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.
* [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.

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