vendored
This commit is contained in:
parent
ffa5cd9cd7
commit
f380dfe58d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.envrc
|
||||||
51
Makefile
51
Makefile
@ -1,4 +1,49 @@
|
|||||||
.PHONY: run
|
include .envrc
|
||||||
run:
|
|
||||||
|
## help: print this help message
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo 'Usage:'
|
||||||
|
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
|
||||||
|
|
||||||
|
.PHONY: confirm
|
||||||
|
confirm:
|
||||||
|
@echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]
|
||||||
|
|
||||||
|
current_time = $(shell date -Iseconds)
|
||||||
|
git_description = $(shell git describe --always --dirty)
|
||||||
|
linker_flags = '-s -X main.buildTime=${current_time} -X main.version=${git_description}'
|
||||||
|
|
||||||
|
.PHONY: build/api
|
||||||
|
build/api:
|
||||||
|
@echo 'Building cmd/api...'
|
||||||
|
go build -ldflags=${linker_flags} -o=./bin/api ./cmd/api
|
||||||
|
GOOS=linux GOARCH=amd64 go build -ldflags=${linker_flags} -o=./bin/linux_amd64/api ./cmd/api
|
||||||
|
|
||||||
|
## run/api: run the cmd/api application
|
||||||
|
.PHONY: run/api
|
||||||
|
run/api:
|
||||||
@echo "Running the API"
|
@echo "Running the API"
|
||||||
go run cmd/api/*.go
|
go run ./cmd/api
|
||||||
|
|
||||||
|
.PHONY: db/psql
|
||||||
|
db/psql:
|
||||||
|
psql ${PARTY_DB_DSN}
|
||||||
|
|
||||||
|
.PHONY: db/migrations/new
|
||||||
|
db/migrations/new:
|
||||||
|
@echo 'Creating migration files for ${name}...'
|
||||||
|
migrate create -seq -ext=.sql -dir=./migrations ${name}
|
||||||
|
|
||||||
|
.PHONY: db/migrations/up
|
||||||
|
db/migrations/up: confirm
|
||||||
|
@echo 'Running up migrations...'
|
||||||
|
migrate -path ./migrations -database ${PARTY_DB_DSN} up
|
||||||
|
|
||||||
|
.PHONY: vendor
|
||||||
|
vendor:
|
||||||
|
@echo 'Tidying and verifying module dependencies...'
|
||||||
|
go mod tidy
|
||||||
|
go mod verify
|
||||||
|
@echo 'Vendoring dependencies...'
|
||||||
|
go mod vendor
|
||||||
|
|||||||
BIN
bin/linux_amd64/api
Executable file
BIN
bin/linux_amd64/api
Executable file
Binary file not shown.
@ -4,10 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"expvar"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
//"html/template"
|
//"html/template"
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
@ -23,7 +26,8 @@ import (
|
|||||||
"party.at/party/internal/jsonlog"
|
"party.at/party/internal/jsonlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "1.0.0"
|
var version string
|
||||||
|
var buildTime string
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
port int
|
port int
|
||||||
@ -102,8 +106,16 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
displayVersion := flag.Bool("version", false, "Display version and exit")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *displayVersion {
|
||||||
|
fmt.Printf("Version:\t%s\n", version)
|
||||||
|
fmt.Printf("Build time:\t%s\n", buildTime)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)
|
logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)
|
||||||
|
|
||||||
log.Printf("%s\n", cfg.db.dsn)
|
log.Printf("%s\n", cfg.db.dsn)
|
||||||
@ -114,6 +126,20 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
|
expvar.NewString("version").Set(version)
|
||||||
|
|
||||||
|
expvar.Publish("goroutines", expvar.Func(func() interface{} {
|
||||||
|
return runtime.NumGoroutine()
|
||||||
|
}))
|
||||||
|
|
||||||
|
expvar.Publish("database", expvar.Func(func() interface{} {
|
||||||
|
return db.Stats()
|
||||||
|
}))
|
||||||
|
|
||||||
|
expvar.Publish("timestamp", expvar.Func(func() interface{} {
|
||||||
|
return time.Now().Unix()
|
||||||
|
}))
|
||||||
|
|
||||||
app := &application{
|
app := &application{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"strings"
|
"strings"
|
||||||
"errors"
|
"errors"
|
||||||
|
"expvar"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/felixge/httpsnoop"
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"party.at/party/internal/data"
|
"party.at/party/internal/data"
|
||||||
@ -213,3 +217,64 @@ func (app *application) requirePermission(code string, next http.HandlerFunc) ht
|
|||||||
// Wrap this with the requireActivatedUser() middleware before returning it.
|
// Wrap this with the requireActivatedUser() middleware before returning it.
|
||||||
return app.requireActivatedUser(fn)
|
return app.requireActivatedUser(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) enableCORS(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Vary", "Origin")
|
||||||
|
|
||||||
|
w.Header().Add("Vary", "Access-Control-Request-Method")
|
||||||
|
|
||||||
|
origin := r.Header.Get("Origin")
|
||||||
|
|
||||||
|
if origin != "" && len(app.config.cors.trustedOrigins) != 0 {
|
||||||
|
// Loop through the list of trusted origins, checking to see if the request
|
||||||
|
// origin exactly matches one of them.
|
||||||
|
for i := range app.config.cors.trustedOrigins {
|
||||||
|
if origin == app.config.cors.trustedOrigins[i] {
|
||||||
|
// If there is a match, then set a "Access-Control-Allow-Origin"
|
||||||
|
// response header with the request origin as the value.
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
|
|
||||||
|
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||||
|
// Set the necessary preflight response headers, as discussed
|
||||||
|
// previously.
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, PUT, PATCH, DELETE")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers","Authorization, Content-Type")
|
||||||
|
|
||||||
|
// Write the headers along with a 200 OK status and return from
|
||||||
|
// the middleware with no further action.
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) metrics(next http.Handler) http.Handler {
|
||||||
|
// Initialize the new expvar variables when the middleware chain is first built.
|
||||||
|
totalRequestsReceived := expvar.NewInt("total_requests_received")
|
||||||
|
totalResponsesSent := expvar.NewInt("total_responses_sent")
|
||||||
|
totalProcessingTimeMicroseconds := expvar.NewInt("total_processing_time_μs")
|
||||||
|
|
||||||
|
totalResponsesSentByStatus := expvar.NewMap("total_responses_sent_by_status")
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
totalRequestsReceived.Add(1)
|
||||||
|
|
||||||
|
// Call the next handler in the chain.
|
||||||
|
metrics := httpsnoop.CaptureMetrics(next, w, r)
|
||||||
|
|
||||||
|
// On the way back up the middleware chain, increment the number of responses
|
||||||
|
// sent by 1.
|
||||||
|
totalResponsesSent.Add(1)
|
||||||
|
|
||||||
|
totalProcessingTimeMicroseconds.Add(metrics.Duration.Microseconds())
|
||||||
|
|
||||||
|
totalResponsesSentByStatus.Add(strconv.Itoa(metrics.Code), 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"expvar"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,5 +35,7 @@ func (app *application) routes() http.Handler {
|
|||||||
|
|
||||||
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication", app.createAuthenticationTokenHandler)
|
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication", app.createAuthenticationTokenHandler)
|
||||||
|
|
||||||
return app.recoverPanic(app.enableCORS(app.rateLimit(app.authenticate(router))))
|
router.Handler(http.MethodGet, "/debug/vars", expvar.Handler())
|
||||||
|
|
||||||
|
return app.metrics(app.recoverPanic(app.enableCORS(app.rateLimit(app.authenticate(router)))))
|
||||||
}
|
}
|
||||||
|
|||||||
15
go.mod
15
go.mod
@ -3,16 +3,13 @@ module party.at/party
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/felixge/httpsnoop v1.0.4
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
github.com/lib/pq v1.12.0
|
||||||
|
github.com/wneessen/go-mail v0.7.2
|
||||||
|
golang.org/x/crypto v0.37.0
|
||||||
|
golang.org/x/time v0.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/text v0.29.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/lib/pq v1.12.0 // indirect
|
|
||||||
github.com/wneessen/go-mail v0.7.2 // indirect
|
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
|
||||||
golang.org/x/text v0.29.0 // indirect
|
|
||||||
golang.org/x/time v0.15.0 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
29
go.sum
29
go.sum
@ -1,41 +1,16 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
|
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
|
||||||
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|||||||
0
vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
0
vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
19
vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
19
vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
10
vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
10
vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.PHONY: ci generate clean
|
||||||
|
|
||||||
|
ci: clean generate
|
||||||
|
go test -race -v ./...
|
||||||
|
|
||||||
|
generate:
|
||||||
|
go generate .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *_generated*.go
|
||||||
95
vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
95
vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# httpsnoop
|
||||||
|
|
||||||
|
Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||||
|
response time, bytes written, and http status code) from your application's
|
||||||
|
http.Handlers.
|
||||||
|
|
||||||
|
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
|
||||||
|
which is also exposed for users interested in a more low-level API.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/felixge/httpsnoop)
|
||||||
|
[](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml)
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
// myH is your app's http handler, perhaps a http.ServeMux or similar.
|
||||||
|
var myH http.Handler
|
||||||
|
// wrappedH wraps myH in order to log every request.
|
||||||
|
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
m := httpsnoop.CaptureMetrics(myH, w, r)
|
||||||
|
log.Printf(
|
||||||
|
"%s %s (code=%d dt=%s written=%d)",
|
||||||
|
r.Method,
|
||||||
|
r.URL,
|
||||||
|
m.Code,
|
||||||
|
m.Duration,
|
||||||
|
m.Written,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
http.ListenAndServe(":8080", wrappedH)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why this package exists
|
||||||
|
|
||||||
|
Instrumenting an application's http.Handler is surprisingly difficult.
|
||||||
|
|
||||||
|
However if you google for e.g. "capture ResponseWriter status code" you'll find
|
||||||
|
lots of advise and code examples that suggest it to be a fairly trivial
|
||||||
|
undertaking. Unfortunately everything I've seen so far has a high chance of
|
||||||
|
breaking your application.
|
||||||
|
|
||||||
|
The main problem is that a `http.ResponseWriter` often implements additional
|
||||||
|
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
|
||||||
|
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
|
||||||
|
in your own struct that also implements the `http.ResponseWriter` interface
|
||||||
|
will hide the additional interfaces mentioned above. This has a high change of
|
||||||
|
introducing subtle bugs into any non-trivial application.
|
||||||
|
|
||||||
|
Another approach I've seen people take is to return a struct that implements
|
||||||
|
all of the interfaces above. However, that's also problematic, because it's
|
||||||
|
difficult to fake some of these interfaces behaviors when the underlying
|
||||||
|
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
|
||||||
|
because an application may choose to operate differently, merely because it
|
||||||
|
detects the presence of these additional interfaces.
|
||||||
|
|
||||||
|
This package solves this problem by checking which additional interfaces a
|
||||||
|
`http.ResponseWriter` implements, returning a wrapped version implementing the
|
||||||
|
exact same set of interfaces.
|
||||||
|
|
||||||
|
Additionally this package properly handles edge cases such as `WriteHeader` not
|
||||||
|
being called, or called more than once, as well as concurrent calls to
|
||||||
|
`http.ResponseWriter` methods, and even calls happening after the wrapped
|
||||||
|
`ServeHTTP` has already returned.
|
||||||
|
|
||||||
|
Unfortunately this package is not perfect either. It's possible that it is
|
||||||
|
still missing some interfaces provided by the go core (let me know if you find
|
||||||
|
one), and it won't work for applications adding their own interfaces into the
|
||||||
|
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
|
||||||
|
`http.ResponseWriter` and type-assert the result to its other interfaces.
|
||||||
|
|
||||||
|
However, hopefully the explanation above has sufficiently scared you of rolling
|
||||||
|
your own solution to this problem. httpsnoop may still break your application,
|
||||||
|
but at least it tries to avoid it as much as possible.
|
||||||
|
|
||||||
|
Anyway, the real problem here is that smuggling additional interfaces inside
|
||||||
|
`http.ResponseWriter` is a problematic design choice, but it probably goes as
|
||||||
|
deep as the Go language specification itself. But that's okay, I still prefer
|
||||||
|
Go over the alternatives ;).
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkBaseline-8 20000 94912 ns/op
|
||||||
|
BenchmarkCaptureMetrics-8 20000 95461 ns/op
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
|
||||||
|
overhead of ~500 ns per http request on my machine. However, the margin of
|
||||||
|
error appears to be larger than that, therefor it should be reasonable to
|
||||||
|
assume that the overhead introduced by `CaptureMetrics` is absolutely
|
||||||
|
negligible.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
86
vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
86
vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics holds metrics captured from CaptureMetrics.
|
||||||
|
type Metrics struct {
|
||||||
|
// Code is the first http response code passed to the WriteHeader func of
|
||||||
|
// the ResponseWriter. If no such call is made, a default code of 200 is
|
||||||
|
// assumed instead.
|
||||||
|
Code int
|
||||||
|
// Duration is the time it took to execute the handler.
|
||||||
|
Duration time.Duration
|
||||||
|
// Written is the number of bytes successfully written by the Write or
|
||||||
|
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
|
||||||
|
// data to their underlaying connection directly (e.g. headers), but those
|
||||||
|
// are not tracked. Therefor the number of Written bytes will usually match
|
||||||
|
// the size of the response body.
|
||||||
|
Written int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
|
||||||
|
// returns the metrics it captured from it.
|
||||||
|
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
|
||||||
|
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
|
||||||
|
hnd.ServeHTTP(ww, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
|
||||||
|
// resulting metrics. This is very similar to CaptureMetrics (which is just
|
||||||
|
// sugar on top of this func), but is a more usable interface if your
|
||||||
|
// application doesn't use the Go http.Handler interface.
|
||||||
|
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
|
||||||
|
m := Metrics{Code: http.StatusOK}
|
||||||
|
m.CaptureMetrics(w, fn)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
|
||||||
|
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
|
||||||
|
// but allows one to customize starting Metrics object.
|
||||||
|
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
|
||||||
|
var (
|
||||||
|
start = time.Now()
|
||||||
|
headerWritten bool
|
||||||
|
hooks = Hooks{
|
||||||
|
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
|
||||||
|
return func(code int) {
|
||||||
|
next(code)
|
||||||
|
|
||||||
|
if !(code >= 100 && code <= 199) && !headerWritten {
|
||||||
|
m.Code = code
|
||||||
|
headerWritten = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Write: func(next WriteFunc) WriteFunc {
|
||||||
|
return func(p []byte) (int, error) {
|
||||||
|
n, err := next(p)
|
||||||
|
|
||||||
|
m.Written += int64(n)
|
||||||
|
headerWritten = true
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
|
||||||
|
return func(src io.Reader) (int64, error) {
|
||||||
|
n, err := next(src)
|
||||||
|
|
||||||
|
headerWritten = true
|
||||||
|
m.Written += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fn(Wrap(w, hooks))
|
||||||
|
m.Duration += time.Since(start)
|
||||||
|
}
|
||||||
10
vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
10
vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||||
|
// response time, bytes written, and http status code) from your application's
|
||||||
|
// http.Handlers.
|
||||||
|
//
|
||||||
|
// Doing this requires non-trivial wrapping of the http.ResponseWriter
|
||||||
|
// interface, which is also exposed for users interested in a more low-level
|
||||||
|
// API.
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
//go:generate go run codegen/main.go
|
||||||
436
vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
436
vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
// +build go1.8
|
||||||
|
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type HeaderFunc func() http.Header
|
||||||
|
|
||||||
|
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteHeaderFunc func(code int)
|
||||||
|
|
||||||
|
// WriteFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteFunc func(b []byte) (int, error)
|
||||||
|
|
||||||
|
// FlushFunc is part of the http.Flusher interface.
|
||||||
|
type FlushFunc func()
|
||||||
|
|
||||||
|
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||||
|
type CloseNotifyFunc func() <-chan bool
|
||||||
|
|
||||||
|
// HijackFunc is part of the http.Hijacker interface.
|
||||||
|
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||||
|
|
||||||
|
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||||
|
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||||
|
|
||||||
|
// PushFunc is part of the http.Pusher interface.
|
||||||
|
type PushFunc func(target string, opts *http.PushOptions) error
|
||||||
|
|
||||||
|
// Hooks defines a set of method interceptors for methods included in
|
||||||
|
// http.ResponseWriter as well as some others. You can think of them as
|
||||||
|
// middleware for the function calls they target. See Wrap for more details.
|
||||||
|
type Hooks struct {
|
||||||
|
Header func(HeaderFunc) HeaderFunc
|
||||||
|
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||||
|
Write func(WriteFunc) WriteFunc
|
||||||
|
Flush func(FlushFunc) FlushFunc
|
||||||
|
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||||
|
Hijack func(HijackFunc) HijackFunc
|
||||||
|
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||||
|
Push func(PushFunc) PushFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||||
|
// as w. Specifically if w implements any combination of:
|
||||||
|
//
|
||||||
|
// - http.Flusher
|
||||||
|
// - http.CloseNotifier
|
||||||
|
// - http.Hijacker
|
||||||
|
// - io.ReaderFrom
|
||||||
|
// - http.Pusher
|
||||||
|
//
|
||||||
|
// The wrapped version will implement the exact same combination. If no hooks
|
||||||
|
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||||
|
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||||
|
// method they target and may modify the call's arguments and/or return values.
|
||||||
|
// The CaptureMetrics implementation serves as a working example for how the
|
||||||
|
// hooks can be used.
|
||||||
|
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||||
|
rw := &rw{w: w, h: hooks}
|
||||||
|
_, i0 := w.(http.Flusher)
|
||||||
|
_, i1 := w.(http.CloseNotifier)
|
||||||
|
_, i2 := w.(http.Hijacker)
|
||||||
|
_, i3 := w.(io.ReaderFrom)
|
||||||
|
_, i4 := w.(http.Pusher)
|
||||||
|
switch {
|
||||||
|
// combination 1/32
|
||||||
|
case !i0 && !i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
}{rw, rw}
|
||||||
|
// combination 2/32
|
||||||
|
case !i0 && !i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 3/32
|
||||||
|
case !i0 && !i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 4/32
|
||||||
|
case !i0 && !i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 5/32
|
||||||
|
case !i0 && !i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 6/32
|
||||||
|
case !i0 && !i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 7/32
|
||||||
|
case !i0 && !i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 8/32
|
||||||
|
case !i0 && !i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 9/32
|
||||||
|
case !i0 && i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 10/32
|
||||||
|
case !i0 && i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 11/32
|
||||||
|
case !i0 && i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 12/32
|
||||||
|
case !i0 && i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 13/32
|
||||||
|
case !i0 && i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 14/32
|
||||||
|
case !i0 && i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 15/32
|
||||||
|
case !i0 && i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 16/32
|
||||||
|
case !i0 && i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 17/32
|
||||||
|
case i0 && !i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 18/32
|
||||||
|
case i0 && !i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 19/32
|
||||||
|
case i0 && !i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 20/32
|
||||||
|
case i0 && !i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 21/32
|
||||||
|
case i0 && !i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 22/32
|
||||||
|
case i0 && !i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 23/32
|
||||||
|
case i0 && !i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 24/32
|
||||||
|
case i0 && !i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 25/32
|
||||||
|
case i0 && i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 26/32
|
||||||
|
case i0 && i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 27/32
|
||||||
|
case i0 && i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 28/32
|
||||||
|
case i0 && i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 29/32
|
||||||
|
case i0 && i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 30/32
|
||||||
|
case i0 && i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 31/32
|
||||||
|
case i0 && i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 32/32
|
||||||
|
case i0 && i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw, rw}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
type rw struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
h Hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Unwrap() http.ResponseWriter {
|
||||||
|
return w.w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Header() http.Header {
|
||||||
|
f := w.w.(http.ResponseWriter).Header
|
||||||
|
if w.h.Header != nil {
|
||||||
|
f = w.h.Header(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) WriteHeader(code int) {
|
||||||
|
f := w.w.(http.ResponseWriter).WriteHeader
|
||||||
|
if w.h.WriteHeader != nil {
|
||||||
|
f = w.h.WriteHeader(f)
|
||||||
|
}
|
||||||
|
f(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Write(b []byte) (int, error) {
|
||||||
|
f := w.w.(http.ResponseWriter).Write
|
||||||
|
if w.h.Write != nil {
|
||||||
|
f = w.h.Write(f)
|
||||||
|
}
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Flush() {
|
||||||
|
f := w.w.(http.Flusher).Flush
|
||||||
|
if w.h.Flush != nil {
|
||||||
|
f = w.h.Flush(f)
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) CloseNotify() <-chan bool {
|
||||||
|
f := w.w.(http.CloseNotifier).CloseNotify
|
||||||
|
if w.h.CloseNotify != nil {
|
||||||
|
f = w.h.CloseNotify(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
f := w.w.(http.Hijacker).Hijack
|
||||||
|
if w.h.Hijack != nil {
|
||||||
|
f = w.h.Hijack(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||||
|
f := w.w.(io.ReaderFrom).ReadFrom
|
||||||
|
if w.h.ReadFrom != nil {
|
||||||
|
f = w.h.ReadFrom(f)
|
||||||
|
}
|
||||||
|
return f(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Push(target string, opts *http.PushOptions) error {
|
||||||
|
f := w.w.(http.Pusher).Push
|
||||||
|
if w.h.Push != nil {
|
||||||
|
f = w.h.Push(f)
|
||||||
|
}
|
||||||
|
return f(target, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unwrapper interface {
|
||||||
|
Unwrap() http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||||
|
// layers of httpsnoop wrappers.
|
||||||
|
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
if rw, ok := w.(Unwrapper); ok {
|
||||||
|
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||||
|
return Unwrap(rw.Unwrap())
|
||||||
|
} else {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
}
|
||||||
278
vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
278
vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
// +build !go1.8
|
||||||
|
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type HeaderFunc func() http.Header
|
||||||
|
|
||||||
|
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteHeaderFunc func(code int)
|
||||||
|
|
||||||
|
// WriteFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteFunc func(b []byte) (int, error)
|
||||||
|
|
||||||
|
// FlushFunc is part of the http.Flusher interface.
|
||||||
|
type FlushFunc func()
|
||||||
|
|
||||||
|
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||||
|
type CloseNotifyFunc func() <-chan bool
|
||||||
|
|
||||||
|
// HijackFunc is part of the http.Hijacker interface.
|
||||||
|
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||||
|
|
||||||
|
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||||
|
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||||
|
|
||||||
|
// Hooks defines a set of method interceptors for methods included in
|
||||||
|
// http.ResponseWriter as well as some others. You can think of them as
|
||||||
|
// middleware for the function calls they target. See Wrap for more details.
|
||||||
|
type Hooks struct {
|
||||||
|
Header func(HeaderFunc) HeaderFunc
|
||||||
|
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||||
|
Write func(WriteFunc) WriteFunc
|
||||||
|
Flush func(FlushFunc) FlushFunc
|
||||||
|
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||||
|
Hijack func(HijackFunc) HijackFunc
|
||||||
|
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||||
|
// as w. Specifically if w implements any combination of:
|
||||||
|
//
|
||||||
|
// - http.Flusher
|
||||||
|
// - http.CloseNotifier
|
||||||
|
// - http.Hijacker
|
||||||
|
// - io.ReaderFrom
|
||||||
|
//
|
||||||
|
// The wrapped version will implement the exact same combination. If no hooks
|
||||||
|
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||||
|
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||||
|
// method they target and may modify the call's arguments and/or return values.
|
||||||
|
// The CaptureMetrics implementation serves as a working example for how the
|
||||||
|
// hooks can be used.
|
||||||
|
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||||
|
rw := &rw{w: w, h: hooks}
|
||||||
|
_, i0 := w.(http.Flusher)
|
||||||
|
_, i1 := w.(http.CloseNotifier)
|
||||||
|
_, i2 := w.(http.Hijacker)
|
||||||
|
_, i3 := w.(io.ReaderFrom)
|
||||||
|
switch {
|
||||||
|
// combination 1/16
|
||||||
|
case !i0 && !i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
}{rw, rw}
|
||||||
|
// combination 2/16
|
||||||
|
case !i0 && !i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 3/16
|
||||||
|
case !i0 && !i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 4/16
|
||||||
|
case !i0 && !i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 5/16
|
||||||
|
case !i0 && i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 6/16
|
||||||
|
case !i0 && i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 7/16
|
||||||
|
case !i0 && i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 8/16
|
||||||
|
case !i0 && i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 9/16
|
||||||
|
case i0 && !i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 10/16
|
||||||
|
case i0 && !i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 11/16
|
||||||
|
case i0 && !i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 12/16
|
||||||
|
case i0 && !i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 13/16
|
||||||
|
case i0 && i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 14/16
|
||||||
|
case i0 && i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 15/16
|
||||||
|
case i0 && i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 16/16
|
||||||
|
case i0 && i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
type rw struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
h Hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Unwrap() http.ResponseWriter {
|
||||||
|
return w.w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Header() http.Header {
|
||||||
|
f := w.w.(http.ResponseWriter).Header
|
||||||
|
if w.h.Header != nil {
|
||||||
|
f = w.h.Header(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) WriteHeader(code int) {
|
||||||
|
f := w.w.(http.ResponseWriter).WriteHeader
|
||||||
|
if w.h.WriteHeader != nil {
|
||||||
|
f = w.h.WriteHeader(f)
|
||||||
|
}
|
||||||
|
f(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Write(b []byte) (int, error) {
|
||||||
|
f := w.w.(http.ResponseWriter).Write
|
||||||
|
if w.h.Write != nil {
|
||||||
|
f = w.h.Write(f)
|
||||||
|
}
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Flush() {
|
||||||
|
f := w.w.(http.Flusher).Flush
|
||||||
|
if w.h.Flush != nil {
|
||||||
|
f = w.h.Flush(f)
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) CloseNotify() <-chan bool {
|
||||||
|
f := w.w.(http.CloseNotifier).CloseNotify
|
||||||
|
if w.h.CloseNotify != nil {
|
||||||
|
f = w.h.CloseNotify(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
f := w.w.(http.Hijacker).Hijack
|
||||||
|
if w.h.Hijack != nil {
|
||||||
|
f = w.h.Hijack(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||||
|
f := w.w.(io.ReaderFrom).ReadFrom
|
||||||
|
if w.h.ReadFrom != nil {
|
||||||
|
f = w.h.ReadFrom(f)
|
||||||
|
}
|
||||||
|
return f(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unwrapper interface {
|
||||||
|
Unwrap() http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||||
|
// layers of httpsnoop wrappers.
|
||||||
|
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
if rw, ok := w.(Unwrapper); ok {
|
||||||
|
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||||
|
return Unwrap(rw.Unwrap())
|
||||||
|
} else {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
}
|
||||||
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
9
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
9
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# This is the official list of Gorilla WebSocket authors for copyright
|
||||||
|
# purposes.
|
||||||
|
#
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Gary Burd <gary@beagledreams.com>
|
||||||
|
Google LLC (https://opensource.google.com/)
|
||||||
|
Joachim Bauch <mail@joachim-bauch.de>
|
||||||
|
|
||||||
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
33
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
33
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Gorilla WebSocket
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/gorilla/websocket)
|
||||||
|
[](https://circleci.com/gh/gorilla/websocket)
|
||||||
|
|
||||||
|
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||||
|
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
||||||
|
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||||
|
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||||
|
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||||
|
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
The Gorilla WebSocket package provides a complete and tested implementation of
|
||||||
|
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
|
||||||
|
package API is stable.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
go get github.com/gorilla/websocket
|
||||||
|
|
||||||
|
### Protocol Compliance
|
||||||
|
|
||||||
|
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||||
|
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||||
|
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||||
|
|
||||||
434
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
434
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||||
|
// invalid.
|
||||||
|
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||||
|
|
||||||
|
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||||
|
|
||||||
|
// NewClient creates a new client connection using the given net connection.
|
||||||
|
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||||
|
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||||
|
// (Cookie). Use the response.Header to get the selected subprotocol
|
||||||
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
|
//
|
||||||
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
|
// etc.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Dialer instead.
|
||||||
|
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||||
|
d := Dialer{
|
||||||
|
ReadBufferSize: readBufSize,
|
||||||
|
WriteBufferSize: writeBufSize,
|
||||||
|
NetDial: func(net, addr string) (net.Conn, error) {
|
||||||
|
return netConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return d.Dial(u.String(), requestHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer contains options for connecting to WebSocket server.
|
||||||
|
//
|
||||||
|
// It is safe to call Dialer's methods concurrently.
|
||||||
|
type Dialer struct {
|
||||||
|
// NetDial specifies the dial function for creating TCP connections. If
|
||||||
|
// NetDial is nil, net.Dial is used.
|
||||||
|
NetDial func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||||
|
// NetDialContext is nil, NetDial is used.
|
||||||
|
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
|
||||||
|
// NetDialTLSContext is nil, NetDialContext is used.
|
||||||
|
// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
|
||||||
|
// TLSClientConfig is ignored.
|
||||||
|
NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Proxy specifies a function to return a proxy for a given
|
||||||
|
// Request. If the function returns a non-nil error, the
|
||||||
|
// request is aborted with the provided error.
|
||||||
|
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||||
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
|
|
||||||
|
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||||
|
// If nil, the default configuration is used.
|
||||||
|
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
|
||||||
|
// is done there and TLSClientConfig is ignored.
|
||||||
|
TLSClientConfig *tls.Config
|
||||||
|
|
||||||
|
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||||
|
HandshakeTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||||
|
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||||
|
// do not limit the size of the messages that can be sent or received.
|
||||||
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
|
// Subprotocols specifies the client's requested subprotocols.
|
||||||
|
Subprotocols []string
|
||||||
|
|
||||||
|
// EnableCompression specifies if the client should attempt to negotiate
|
||||||
|
// per message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
|
|
||||||
|
// Jar specifies the cookie jar.
|
||||||
|
// If Jar is nil, cookies are not sent in requests and ignored
|
||||||
|
// in responses.
|
||||||
|
Jar http.CookieJar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial creates a new client connection by calling DialContext with a background context.
|
||||||
|
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
|
|
||||||
|
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||||
|
hostPort = u.Host
|
||||||
|
hostNoPort = u.Host
|
||||||
|
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||||
|
hostNoPort = hostNoPort[:i]
|
||||||
|
} else {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "wss":
|
||||||
|
hostPort += ":443"
|
||||||
|
case "https":
|
||||||
|
hostPort += ":443"
|
||||||
|
default:
|
||||||
|
hostPort += ":80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostPort, hostNoPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDialer is a dialer with all fields set to the default values.
|
||||||
|
var DefaultDialer = &Dialer{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
HandshakeTimeout: 45 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// nilDialer is dialer to use when receiver is nil.
|
||||||
|
var nilDialer = *DefaultDialer
|
||||||
|
|
||||||
|
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||||
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||||
|
// Use the response.Header to get the selected subprotocol
|
||||||
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
|
//
|
||||||
|
// The context will be used in the request and in the Dialer.
|
||||||
|
//
|
||||||
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
|
// etcetera. The response body may not contain the entire response and does not
|
||||||
|
// need to be closed by the application.
|
||||||
|
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
if d == nil {
|
||||||
|
d = &nilDialer
|
||||||
|
}
|
||||||
|
|
||||||
|
challengeKey, err := generateChallengeKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "ws":
|
||||||
|
u.Scheme = "http"
|
||||||
|
case "wss":
|
||||||
|
u.Scheme = "https"
|
||||||
|
default:
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
// User name and password are not allowed in websocket URIs.
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: u,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Host: u.Host,
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
// Set the cookies present in the cookie jar of the dialer
|
||||||
|
if d.Jar != nil {
|
||||||
|
for _, cookie := range d.Jar.Cookies(u) {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the request headers using the capitalization for names and values in
|
||||||
|
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||||
|
// servers that depend on it. The Header.Set method is not used because the
|
||||||
|
// method canonicalizes the header names.
|
||||||
|
req.Header["Upgrade"] = []string{"websocket"}
|
||||||
|
req.Header["Connection"] = []string{"Upgrade"}
|
||||||
|
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||||
|
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||||
|
if len(d.Subprotocols) > 0 {
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||||
|
}
|
||||||
|
for k, vs := range requestHeader {
|
||||||
|
switch {
|
||||||
|
case k == "Host":
|
||||||
|
if len(vs) > 0 {
|
||||||
|
req.Host = vs[0]
|
||||||
|
}
|
||||||
|
case k == "Upgrade" ||
|
||||||
|
k == "Connection" ||
|
||||||
|
k == "Sec-Websocket-Key" ||
|
||||||
|
k == "Sec-Websocket-Version" ||
|
||||||
|
k == "Sec-Websocket-Extensions" ||
|
||||||
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
|
case k == "Sec-Websocket-Protocol":
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||||
|
default:
|
||||||
|
req.Header[k] = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.EnableCompression {
|
||||||
|
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HandshakeTimeout != 0 {
|
||||||
|
var cancel func()
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get network dial function.
|
||||||
|
var netDial func(network, add string) (net.Conn, error)
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "http":
|
||||||
|
if d.NetDialContext != nil {
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return d.NetDialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
} else if d.NetDial != nil {
|
||||||
|
netDial = d.NetDial
|
||||||
|
}
|
||||||
|
case "https":
|
||||||
|
if d.NetDialTLSContext != nil {
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return d.NetDialTLSContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
} else if d.NetDialContext != nil {
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return d.NetDialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
} else if d.NetDial != nil {
|
||||||
|
netDial = d.NetDial
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if netDial == nil {
|
||||||
|
netDialer := &net.Dialer{}
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return netDialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to set the connection deadline.
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
forwardDial := netDial
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := forwardDial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = c.SetDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to connect through a proxy.
|
||||||
|
if d.Proxy != nil {
|
||||||
|
proxyURL, err := d.Proxy(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if proxyURL != nil {
|
||||||
|
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
netDial = dialer.Dial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
trace := httptrace.ContextClientTrace(ctx)
|
||||||
|
if trace != nil && trace.GetConn != nil {
|
||||||
|
trace.GetConn(hostPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn, err := netDial("tcp", hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if trace != nil && trace.GotConn != nil {
|
||||||
|
trace.GotConn(httptrace.GotConnInfo{
|
||||||
|
Conn: netConn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if netConn != nil {
|
||||||
|
netConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if u.Scheme == "https" && d.NetDialTLSContext == nil {
|
||||||
|
// If NetDialTLSContext is set, assume that the TLS handshake has already been done
|
||||||
|
|
||||||
|
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||||
|
if cfg.ServerName == "" {
|
||||||
|
cfg.ServerName = hostNoPort
|
||||||
|
}
|
||||||
|
tlsConn := tls.Client(netConn, cfg)
|
||||||
|
netConn = tlsConn
|
||||||
|
|
||||||
|
if trace != nil && trace.TLSHandshakeStart != nil {
|
||||||
|
trace.TLSHandshakeStart()
|
||||||
|
}
|
||||||
|
err := doHandshake(ctx, tlsConn, cfg)
|
||||||
|
if trace != nil && trace.TLSHandshakeDone != nil {
|
||||||
|
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||||
|
|
||||||
|
if err := req.Write(netConn); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||||
|
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||||
|
trace.GotFirstResponseByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.ReadResponse(conn.br, req)
|
||||||
|
if err != nil {
|
||||||
|
if d.TLSClientConfig != nil {
|
||||||
|
for _, proto := range d.TLSClientConfig.NextProtos {
|
||||||
|
if proto != "http/1.1" {
|
||||||
|
return nil, nil, fmt.Errorf(
|
||||||
|
"websocket: protocol %q was given but is not supported;"+
|
||||||
|
"sharing tls.Config with net/http Transport can cause this error: %w",
|
||||||
|
proto, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Jar != nil {
|
||||||
|
if rc := resp.Cookies(); len(rc) > 0 {
|
||||||
|
d.Jar.SetCookies(u, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 101 ||
|
||||||
|
!tokenListContainsValue(resp.Header, "Upgrade", "websocket") ||
|
||||||
|
!tokenListContainsValue(resp.Header, "Connection", "upgrade") ||
|
||||||
|
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||||
|
// Before closing the network connection on return from this
|
||||||
|
// function, slurp up some of the response to aid application
|
||||||
|
// debugging.
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
n, _ := io.ReadFull(resp.Body, buf)
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||||
|
return nil, resp, ErrBadHandshake
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range parseExtensions(resp.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, snct := ext["server_no_context_takeover"]
|
||||||
|
_, cnct := ext["client_no_context_takeover"]
|
||||||
|
if !snct || !cnct {
|
||||||
|
return nil, resp, errInvalidCompression
|
||||||
|
}
|
||||||
|
conn.newCompressionWriter = compressNoContextTakeover
|
||||||
|
conn.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
|
||||||
|
netConn.SetDeadline(time.Time{})
|
||||||
|
netConn = nil // to avoid close in defer.
|
||||||
|
return conn, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
if cfg == nil {
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
return cfg.Clone()
|
||||||
|
}
|
||||||
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
|
||||||
|
maxCompressionLevel = flate.BestCompression
|
||||||
|
defaultCompressionLevel = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
|
||||||
|
flateReaderPool = sync.Pool{New: func() interface{} {
|
||||||
|
return flate.NewReader(nil)
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||||
|
const tail =
|
||||||
|
// Add four bytes as specified in RFC
|
||||||
|
"\x00\x00\xff\xff" +
|
||||||
|
// Add final block to squelch unexpected EOF error from flate reader.
|
||||||
|
"\x01\x00\x00\xff\xff"
|
||||||
|
|
||||||
|
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||||
|
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||||
|
return &flateReadWrapper{fr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidCompressionLevel(level int) bool {
|
||||||
|
return minCompressionLevel <= level && level <= maxCompressionLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
|
||||||
|
p := &flateWriterPools[level-minCompressionLevel]
|
||||||
|
tw := &truncWriter{w: w}
|
||||||
|
fw, _ := p.Get().(*flate.Writer)
|
||||||
|
if fw == nil {
|
||||||
|
fw, _ = flate.NewWriter(tw, level)
|
||||||
|
} else {
|
||||||
|
fw.Reset(tw)
|
||||||
|
}
|
||||||
|
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||||
|
// stream to another io.Writer.
|
||||||
|
type truncWriter struct {
|
||||||
|
w io.WriteCloser
|
||||||
|
n int
|
||||||
|
p [4]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||||
|
n := 0
|
||||||
|
|
||||||
|
// fill buffer first for simplicity.
|
||||||
|
if w.n < len(w.p) {
|
||||||
|
n = copy(w.p[w.n:], p)
|
||||||
|
p = p[n:]
|
||||||
|
w.n += n
|
||||||
|
if len(p) == 0 {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := len(p)
|
||||||
|
if m > len(w.p) {
|
||||||
|
m = len(w.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(w.p[:], w.p[m:])
|
||||||
|
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||||
|
nn, err := w.w.Write(p[:len(p)-m])
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type flateWriteWrapper struct {
|
||||||
|
fw *flate.Writer
|
||||||
|
tw *truncWriter
|
||||||
|
p *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
|
||||||
|
if w.fw == nil {
|
||||||
|
return 0, errWriteClosed
|
||||||
|
}
|
||||||
|
return w.fw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWriteWrapper) Close() error {
|
||||||
|
if w.fw == nil {
|
||||||
|
return errWriteClosed
|
||||||
|
}
|
||||||
|
err1 := w.fw.Flush()
|
||||||
|
w.p.Put(w.fw)
|
||||||
|
w.fw = nil
|
||||||
|
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
|
||||||
|
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
|
||||||
|
}
|
||||||
|
err2 := w.tw.w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
type flateReadWrapper struct {
|
||||||
|
fr io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||||
|
if r.fr == nil {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
n, err := r.fr.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
// Preemptively place the reader back in the pool. This helps with
|
||||||
|
// scenarios where the application does not call NextReader() soon after
|
||||||
|
// this final read.
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *flateReadWrapper) Close() error {
|
||||||
|
if r.fr == nil {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
err := r.fr.Close()
|
||||||
|
flateReaderPool.Put(r.fr)
|
||||||
|
r.fr = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
1238
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
1238
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
227
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
227
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package websocket implements the WebSocket protocol defined in RFC 6455.
|
||||||
|
//
|
||||||
|
// Overview
|
||||||
|
//
|
||||||
|
// The Conn type represents a WebSocket connection. A server application calls
|
||||||
|
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||||
|
//
|
||||||
|
// var upgrader = websocket.Upgrader{
|
||||||
|
// ReadBufferSize: 1024,
|
||||||
|
// WriteBufferSize: 1024,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// ... Use conn to send and receive messages.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||||
|
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||||
|
// messages using these methods:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// messageType, p, err := conn.ReadMessage()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In above snippet of code, p is a []byte and messageType is an int with value
|
||||||
|
// websocket.BinaryMessage or websocket.TextMessage.
|
||||||
|
//
|
||||||
|
// An application can also send and receive messages using the io.WriteCloser
|
||||||
|
// and io.Reader interfaces. To send a message, call the connection NextWriter
|
||||||
|
// method to get an io.WriteCloser, write the message to the writer and close
|
||||||
|
// the writer when done. To receive a message, call the connection NextReader
|
||||||
|
// method to get an io.Reader and read until io.EOF is returned. This snippet
|
||||||
|
// shows how to echo messages using the NextWriter and NextReader methods:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// messageType, r, err := conn.NextReader()
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// w, err := conn.NextWriter(messageType)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if _, err := io.Copy(w, r); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if err := w.Close(); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Data Messages
|
||||||
|
//
|
||||||
|
// The WebSocket protocol distinguishes between text and binary data messages.
|
||||||
|
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
|
||||||
|
// binary messages is left to the application.
|
||||||
|
//
|
||||||
|
// This package uses the TextMessage and BinaryMessage integer constants to
|
||||||
|
// identify the two data message types. The ReadMessage and NextReader methods
|
||||||
|
// return the type of the received message. The messageType argument to the
|
||||||
|
// WriteMessage and NextWriter methods specifies the type of a sent message.
|
||||||
|
//
|
||||||
|
// It is the application's responsibility to ensure that text messages are
|
||||||
|
// valid UTF-8 encoded text.
|
||||||
|
//
|
||||||
|
// Control Messages
|
||||||
|
//
|
||||||
|
// The WebSocket protocol defines three types of control messages: close, ping
|
||||||
|
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||||
|
// methods to send a control message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received close messages by calling the handler function
|
||||||
|
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||||
|
// NextReader, ReadMessage or the message Read method. The default close
|
||||||
|
// handler sends a close message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received ping messages by calling the handler function
|
||||||
|
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||||
|
// message to the peer.
|
||||||
|
//
|
||||||
|
// Connections handle received pong messages by calling the handler function
|
||||||
|
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||||
|
// If an application sends ping messages, then the application should set a
|
||||||
|
// pong handler to receive the corresponding pong.
|
||||||
|
//
|
||||||
|
// The control message handler functions are called from the NextReader,
|
||||||
|
// ReadMessage and message reader Read methods. The default close and ping
|
||||||
|
// handlers can block these methods for a short time when the handler writes to
|
||||||
|
// the connection.
|
||||||
|
//
|
||||||
|
// The application must read the connection to process close, ping and pong
|
||||||
|
// messages sent from the peer. If the application is not otherwise interested
|
||||||
|
// in messages from the peer, then the application should start a goroutine to
|
||||||
|
// read and discard messages from the peer. A simple example is:
|
||||||
|
//
|
||||||
|
// func readLoop(c *websocket.Conn) {
|
||||||
|
// for {
|
||||||
|
// if _, _, err := c.NextReader(); err != nil {
|
||||||
|
// c.Close()
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Concurrency
|
||||||
|
//
|
||||||
|
// Connections support one concurrent reader and one concurrent writer.
|
||||||
|
//
|
||||||
|
// Applications are responsible for ensuring that no more than one goroutine
|
||||||
|
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||||
|
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
|
||||||
|
// that no more than one goroutine calls the read methods (NextReader,
|
||||||
|
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
|
||||||
|
// concurrently.
|
||||||
|
//
|
||||||
|
// The Close and WriteControl methods can be called concurrently with all other
|
||||||
|
// methods.
|
||||||
|
//
|
||||||
|
// Origin Considerations
|
||||||
|
//
|
||||||
|
// Web browsers allow Javascript applications to open a WebSocket connection to
|
||||||
|
// any host. It's up to the server to enforce an origin policy using the Origin
|
||||||
|
// request header sent by the browser.
|
||||||
|
//
|
||||||
|
// The Upgrader calls the function specified in the CheckOrigin field to check
|
||||||
|
// the origin. If the CheckOrigin function returns false, then the Upgrade
|
||||||
|
// method fails the WebSocket handshake with HTTP status 403.
|
||||||
|
//
|
||||||
|
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||||
|
// the handshake if the Origin request header is present and the Origin host is
|
||||||
|
// not equal to the Host request header.
|
||||||
|
//
|
||||||
|
// The deprecated package-level Upgrade function does not perform origin
|
||||||
|
// checking. The application is responsible for checking the Origin header
|
||||||
|
// before calling the Upgrade function.
|
||||||
|
//
|
||||||
|
// Buffers
|
||||||
|
//
|
||||||
|
// Connections buffer network input and output to reduce the number
|
||||||
|
// of system calls when reading or writing messages.
|
||||||
|
//
|
||||||
|
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
|
||||||
|
// Section 5 for a discussion of message framing. A WebSocket frame header is
|
||||||
|
// written to the network each time a write buffer is flushed to the network.
|
||||||
|
// Decreasing the size of the write buffer can increase the amount of framing
|
||||||
|
// overhead on the connection.
|
||||||
|
//
|
||||||
|
// The buffer sizes in bytes are specified by the ReadBufferSize and
|
||||||
|
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
|
||||||
|
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
|
||||||
|
// buffers created by the HTTP server when a buffer size field is set to zero.
|
||||||
|
// The HTTP server buffers have a size of 4096 at the time of this writing.
|
||||||
|
//
|
||||||
|
// The buffer sizes do not limit the size of a message that can be read or
|
||||||
|
// written by a connection.
|
||||||
|
//
|
||||||
|
// Buffers are held for the lifetime of the connection by default. If the
|
||||||
|
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
|
||||||
|
// write buffer only when writing a message.
|
||||||
|
//
|
||||||
|
// Applications should tune the buffer sizes to balance memory use and
|
||||||
|
// performance. Increasing the buffer size uses more memory, but can reduce the
|
||||||
|
// number of system calls to read or write the network. In the case of writing,
|
||||||
|
// increasing the buffer size can reduce the number of frame headers written to
|
||||||
|
// the network.
|
||||||
|
//
|
||||||
|
// Some guidelines for setting buffer parameters are:
|
||||||
|
//
|
||||||
|
// Limit the buffer sizes to the maximum expected message size. Buffers larger
|
||||||
|
// than the largest message do not provide any benefit.
|
||||||
|
//
|
||||||
|
// Depending on the distribution of message sizes, setting the buffer size to
|
||||||
|
// a value less than the maximum expected message size can greatly reduce memory
|
||||||
|
// use with a small impact on performance. Here's an example: If 99% of the
|
||||||
|
// messages are smaller than 256 bytes and the maximum message size is 512
|
||||||
|
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
|
||||||
|
// than a buffer size of 512 bytes. The memory savings is 50%.
|
||||||
|
//
|
||||||
|
// A write buffer pool is useful when the application has a modest number
|
||||||
|
// writes over a large number of connections. when buffers are pooled, a larger
|
||||||
|
// buffer size has a reduced impact on total memory use and has the benefit of
|
||||||
|
// reducing system calls and frame overhead.
|
||||||
|
//
|
||||||
|
// Compression EXPERIMENTAL
|
||||||
|
//
|
||||||
|
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||||
|
// by this package in a limited capacity. Setting the EnableCompression option
|
||||||
|
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
|
||||||
|
// support.
|
||||||
|
//
|
||||||
|
// var upgrader = websocket.Upgrader{
|
||||||
|
// EnableCompression: true,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If compression was successfully negotiated with the connection's peer, any
|
||||||
|
// message received in compressed form will be automatically decompressed.
|
||||||
|
// All Read methods will return uncompressed bytes.
|
||||||
|
//
|
||||||
|
// Per message compression of messages written to a connection can be enabled
|
||||||
|
// or disabled by calling the corresponding Conn method:
|
||||||
|
//
|
||||||
|
// conn.EnableWriteCompression(false)
|
||||||
|
//
|
||||||
|
// Currently this package does not support compression with "context takeover".
|
||||||
|
// This means that messages must be compressed and decompressed in isolation,
|
||||||
|
// without retaining sliding window or dictionary state across messages. For
|
||||||
|
// more details refer to RFC 7692.
|
||||||
|
//
|
||||||
|
// Use of compression is experimental and may result in decreased performance.
|
||||||
|
package websocket
|
||||||
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JoinMessages concatenates received messages to create a single io.Reader.
|
||||||
|
// The string term is appended to each message. The returned reader does not
|
||||||
|
// support concurrent calls to the Read method.
|
||||||
|
func JoinMessages(c *Conn, term string) io.Reader {
|
||||||
|
return &joinReader{c: c, term: term}
|
||||||
|
}
|
||||||
|
|
||||||
|
type joinReader struct {
|
||||||
|
c *Conn
|
||||||
|
term string
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *joinReader) Read(p []byte) (int, error) {
|
||||||
|
if r.r == nil {
|
||||||
|
var err error
|
||||||
|
_, r.r, err = r.c.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if r.term != "" {
|
||||||
|
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
r.r = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.WriteJSON instead.
|
||||||
|
func WriteJSON(c *Conn, v interface{}) error {
|
||||||
|
return c.WriteJSON(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// See the documentation for encoding/json Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
func (c *Conn) WriteJSON(v interface{}) error {
|
||||||
|
w, err := c.NextWriter(TextMessage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err1 := json.NewEncoder(w).Encode(v)
|
||||||
|
err2 := w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.ReadJSON instead.
|
||||||
|
func ReadJSON(c *Conn, v interface{}) error {
|
||||||
|
return c.ReadJSON(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for the encoding/json Unmarshal function for details
|
||||||
|
// about the conversion of JSON to a Go value.
|
||||||
|
func (c *Conn) ReadJSON(v interface{}) error {
|
||||||
|
_, r, err := c.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(r).Decode(v)
|
||||||
|
if err == io.EOF {
|
||||||
|
// One value is expected in the message.
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
55
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
55
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
|
||||||
|
//go:build !appengine
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
// Mask one byte at a time for small buffers.
|
||||||
|
if len(b) < 2*wordSize {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time to word boundary.
|
||||||
|
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||||
|
n = wordSize - n
|
||||||
|
for i := range b[:n] {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create aligned word size key.
|
||||||
|
var k [wordSize]byte
|
||||||
|
for i := range k {
|
||||||
|
k[i] = key[(pos+i)&3]
|
||||||
|
}
|
||||||
|
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||||
|
|
||||||
|
// Mask one word at a time.
|
||||||
|
n := (len(b) / wordSize) * wordSize
|
||||||
|
for i := 0; i < n; i += wordSize {
|
||||||
|
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time for remaining bytes.
|
||||||
|
b = b[n:]
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
16
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
16
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||||
|
// this source code is governed by a BSD-style license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
|
||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
102
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
102
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreparedMessage caches on the wire representations of a message payload.
|
||||||
|
// Use PreparedMessage to efficiently send a message payload to multiple
|
||||||
|
// connections. PreparedMessage is especially useful when compression is used
|
||||||
|
// because the CPU and memory expensive compression operation can be executed
|
||||||
|
// once for a given set of compression options.
|
||||||
|
type PreparedMessage struct {
|
||||||
|
messageType int
|
||||||
|
data []byte
|
||||||
|
mu sync.Mutex
|
||||||
|
frames map[prepareKey]*preparedFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
|
||||||
|
type prepareKey struct {
|
||||||
|
isServer bool
|
||||||
|
compress bool
|
||||||
|
compressionLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
// preparedFrame contains data in wire representation.
|
||||||
|
type preparedFrame struct {
|
||||||
|
once sync.Once
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
|
||||||
|
// it to connection using WritePreparedMessage method. Valid wire
|
||||||
|
// representation will be calculated lazily only once for a set of current
|
||||||
|
// connection options.
|
||||||
|
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
|
||||||
|
pm := &PreparedMessage{
|
||||||
|
messageType: messageType,
|
||||||
|
frames: make(map[prepareKey]*preparedFrame),
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare a plain server frame.
|
||||||
|
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// To protect against caller modifying the data argument, remember the data
|
||||||
|
// copied to the plain server frame.
|
||||||
|
pm.data = frameData[len(frameData)-len(data):]
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
frame, ok := pm.frames[key]
|
||||||
|
if !ok {
|
||||||
|
frame = &preparedFrame{}
|
||||||
|
pm.frames[key] = frame
|
||||||
|
}
|
||||||
|
pm.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
frame.once.Do(func() {
|
||||||
|
// Prepare a frame using a 'fake' connection.
|
||||||
|
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||||
|
// the frame.
|
||||||
|
mu := make(chan struct{}, 1)
|
||||||
|
mu <- struct{}{}
|
||||||
|
var nc prepareConn
|
||||||
|
c := &Conn{
|
||||||
|
conn: &nc,
|
||||||
|
mu: mu,
|
||||||
|
isServer: key.isServer,
|
||||||
|
compressionLevel: key.compressionLevel,
|
||||||
|
enableWriteCompression: true,
|
||||||
|
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
|
||||||
|
}
|
||||||
|
if key.compress {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
}
|
||||||
|
err = c.WriteMessage(pm.messageType, pm.data)
|
||||||
|
frame.data = nc.buf.Bytes()
|
||||||
|
})
|
||||||
|
return pm.messageType, frame.data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type prepareConn struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
|
||||||
|
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netDialerFunc func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return fn(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProxyDialer struct {
|
||||||
|
proxyURL *url.URL
|
||||||
|
forwardDial func(network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||||
|
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||||
|
conn, err := hpd.forwardDial(network, hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectHeader := make(http.Header)
|
||||||
|
if user := hpd.proxyURL.User; user != nil {
|
||||||
|
proxyUser := user.Username()
|
||||||
|
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||||
|
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||||
|
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: http.MethodConnect,
|
||||||
|
URL: &url.URL{Opaque: addr},
|
||||||
|
Host: addr,
|
||||||
|
Header: connectHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := connectReq.Write(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response. It's OK to use and discard buffered reader here becaue
|
||||||
|
// the remote server does not speak until spoken to.
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
resp, err := http.ReadResponse(br, connectReq)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
conn.Close()
|
||||||
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
|
return nil, errors.New(f[1])
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
365
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
365
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandshakeError describes an error with the handshake from the peer.
|
||||||
|
type HandshakeError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e HandshakeError) Error() string { return e.message }
|
||||||
|
|
||||||
|
// Upgrader specifies parameters for upgrading an HTTP connection to a
|
||||||
|
// WebSocket connection.
|
||||||
|
//
|
||||||
|
// It is safe to call Upgrader's methods concurrently.
|
||||||
|
type Upgrader struct {
|
||||||
|
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||||
|
HandshakeTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||||
|
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||||
|
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||||
|
// or received.
|
||||||
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
|
// Subprotocols specifies the server's supported protocols in order of
|
||||||
|
// preference. If this field is not nil, then the Upgrade method negotiates a
|
||||||
|
// subprotocol by selecting the first match in this list with a protocol
|
||||||
|
// requested by the client. If there's no match, then no protocol is
|
||||||
|
// negotiated (the Sec-Websocket-Protocol header is not included in the
|
||||||
|
// handshake response).
|
||||||
|
Subprotocols []string
|
||||||
|
|
||||||
|
// Error specifies the function for generating HTTP error responses. If Error
|
||||||
|
// is nil, then http.Error is used to generate the HTTP response.
|
||||||
|
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||||
|
|
||||||
|
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
|
// CheckOrigin is nil, then a safe default is used: return false if the
|
||||||
|
// Origin request header is present and the origin host is not equal to
|
||||||
|
// request Host header.
|
||||||
|
//
|
||||||
|
// A CheckOrigin function should carefully validate the request origin to
|
||||||
|
// prevent cross-site request forgery.
|
||||||
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
|
||||||
|
// EnableCompression specify if the server should attempt to negotiate per
|
||||||
|
// message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
||||||
|
err := HandshakeError{reason}
|
||||||
|
if u.Error != nil {
|
||||||
|
u.Error(w, r, status, err)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Sec-Websocket-Version", "13")
|
||||||
|
http.Error(w, http.StatusText(status), status)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
|
||||||
|
func checkSameOrigin(r *http.Request) bool {
|
||||||
|
origin := r.Header["Origin"]
|
||||||
|
if len(origin) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
u, err := url.Parse(origin[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalASCIIFold(u.Host, r.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||||
|
if u.Subprotocols != nil {
|
||||||
|
clientProtocols := Subprotocols(r)
|
||||||
|
for _, serverProtocol := range u.Subprotocols {
|
||||||
|
for _, clientProtocol := range clientProtocols {
|
||||||
|
if clientProtocol == serverProtocol {
|
||||||
|
return clientProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if responseHeader != nil {
|
||||||
|
return responseHeader.Get("Sec-Websocket-Protocol")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
|
//
|
||||||
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
|
// request. Use the responseHeader to specify cookies (Set-Cookie). To specify
|
||||||
|
// subprotocols supported by the server, set Upgrader.Subprotocols directly.
|
||||||
|
//
|
||||||
|
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||||
|
// response.
|
||||||
|
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||||
|
const badHandshake = "websocket: the client is not using the websocket protocol: "
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOrigin := u.CheckOrigin
|
||||||
|
if checkOrigin == nil {
|
||||||
|
checkOrigin = checkSameOrigin
|
||||||
|
}
|
||||||
|
if !checkOrigin(r) {
|
||||||
|
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
|
||||||
|
}
|
||||||
|
|
||||||
|
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||||
|
if !isValidChallengeKey(challengeKey) {
|
||||||
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header must be Base64 encoded value of 16-byte in length")
|
||||||
|
}
|
||||||
|
|
||||||
|
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||||
|
|
||||||
|
// Negotiate PMCE
|
||||||
|
var compress bool
|
||||||
|
if u.EnableCompression {
|
||||||
|
for _, ext := range parseExtensions(r.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
compress = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||||
|
}
|
||||||
|
var brw *bufio.ReadWriter
|
||||||
|
netConn, brw, err := h.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if brw.Reader.Buffered() > 0 {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var br *bufio.Reader
|
||||||
|
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {
|
||||||
|
// Reuse hijacked buffered reader as connection reader.
|
||||||
|
br = brw.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufioWriterBuffer(netConn, brw.Writer)
|
||||||
|
|
||||||
|
var writeBuf []byte
|
||||||
|
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
|
||||||
|
// Reuse hijacked write buffer as connection buffer.
|
||||||
|
writeBuf = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)
|
||||||
|
c.subprotocol = subprotocol
|
||||||
|
|
||||||
|
if compress {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
c.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use larger of hijacked buffer and connection write buffer for header.
|
||||||
|
p := buf
|
||||||
|
if len(c.writeBuf) > len(p) {
|
||||||
|
p = c.writeBuf
|
||||||
|
}
|
||||||
|
p = p[:0]
|
||||||
|
|
||||||
|
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||||
|
p = append(p, computeAcceptKey(challengeKey)...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
if c.subprotocol != "" {
|
||||||
|
p = append(p, "Sec-WebSocket-Protocol: "...)
|
||||||
|
p = append(p, c.subprotocol...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
}
|
||||||
|
if compress {
|
||||||
|
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||||
|
}
|
||||||
|
for k, vs := range responseHeader {
|
||||||
|
if k == "Sec-Websocket-Protocol" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v := range vs {
|
||||||
|
p = append(p, k...)
|
||||||
|
p = append(p, ": "...)
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
b := v[i]
|
||||||
|
if b <= 31 {
|
||||||
|
// prevent response splitting.
|
||||||
|
b = ' '
|
||||||
|
}
|
||||||
|
p = append(p, b)
|
||||||
|
}
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
|
||||||
|
// Clear deadlines set by HTTP server.
|
||||||
|
netConn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
if u.HandshakeTimeout > 0 {
|
||||||
|
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
|
||||||
|
}
|
||||||
|
if _, err = netConn.Write(p); err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.HandshakeTimeout > 0 {
|
||||||
|
netConn.SetWriteDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
|
//
|
||||||
|
// Deprecated: Use websocket.Upgrader instead.
|
||||||
|
//
|
||||||
|
// Upgrade does not perform origin checking. The application is responsible for
|
||||||
|
// checking the Origin header before calling Upgrade. An example implementation
|
||||||
|
// of the same origin policy check is:
|
||||||
|
//
|
||||||
|
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||||
|
// http.Error(w, "Origin not allowed", http.StatusForbidden)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the endpoint supports subprotocols, then the application is responsible
|
||||||
|
// for negotiating the protocol used on the connection. Use the Subprotocols()
|
||||||
|
// function to get the subprotocols requested by the client. Use the
|
||||||
|
// Sec-Websocket-Protocol response header to specify the subprotocol selected
|
||||||
|
// by the application.
|
||||||
|
//
|
||||||
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
|
// negotiated subprotocol (Sec-Websocket-Protocol).
|
||||||
|
//
|
||||||
|
// The connection buffers IO to the underlying network connection. The
|
||||||
|
// readBufSize and writeBufSize parameters specify the size of the buffers to
|
||||||
|
// use. Messages can be larger than the buffers.
|
||||||
|
//
|
||||||
|
// If the request is not a valid WebSocket handshake, then Upgrade returns an
|
||||||
|
// error of type HandshakeError. Applications should handle this error by
|
||||||
|
// replying to the client with an HTTP error response.
|
||||||
|
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
|
||||||
|
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
|
||||||
|
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||||
|
// don't return errors to maintain backwards compatibility
|
||||||
|
}
|
||||||
|
u.CheckOrigin = func(r *http.Request) bool {
|
||||||
|
// allow all connections by default
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return u.Upgrade(w, r, responseHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subprotocols returns the subprotocols requested by the client in the
|
||||||
|
// Sec-Websocket-Protocol header.
|
||||||
|
func Subprotocols(r *http.Request) []string {
|
||||||
|
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
|
||||||
|
if h == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
protocols := strings.Split(h, ",")
|
||||||
|
for i := range protocols {
|
||||||
|
protocols[i] = strings.TrimSpace(protocols[i])
|
||||||
|
}
|
||||||
|
return protocols
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWebSocketUpgrade returns true if the client requested upgrade to the
|
||||||
|
// WebSocket protocol.
|
||||||
|
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||||
|
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||||
|
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufioReaderSize size returns the size of a bufio.Reader.
|
||||||
|
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
|
||||||
|
// This code assumes that peek on a reset reader returns
|
||||||
|
// bufio.Reader.buf[:0].
|
||||||
|
// TODO: Use bufio.Reader.Size() after Go 1.10
|
||||||
|
br.Reset(originalReader)
|
||||||
|
if p, err := br.Peek(0); err == nil {
|
||||||
|
return cap(p)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHook is an io.Writer that records the last slice passed to it vio
|
||||||
|
// io.Writer.Write.
|
||||||
|
type writeHook struct {
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||||
|
wh.p = p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
|
||||||
|
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
|
||||||
|
// This code assumes that bufio.Writer.buf[:1] is passed to the
|
||||||
|
// bufio.Writer's underlying writer.
|
||||||
|
var wh writeHook
|
||||||
|
bw.Reset(&wh)
|
||||||
|
bw.WriteByte(0)
|
||||||
|
bw.Flush()
|
||||||
|
|
||||||
|
bw.Reset(originalWriter)
|
||||||
|
|
||||||
|
return wh.p[:cap(wh.p)]
|
||||||
|
}
|
||||||
21
vendor/github.com/gorilla/websocket/tls_handshake.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/tls_handshake.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//go:build go1.17
|
||||||
|
// +build go1.17
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !cfg.InsecureSkipVerify {
|
||||||
|
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
21
vendor/github.com/gorilla/websocket/tls_handshake_116.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/tls_handshake_116.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//go:build !go1.17
|
||||||
|
// +build !go1.17
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !cfg.InsecureSkipVerify {
|
||||||
|
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
298
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
298
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||||
|
|
||||||
|
func computeAcceptKey(challengeKey string) string {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte(challengeKey))
|
||||||
|
h.Write(keyGUID)
|
||||||
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateChallengeKey() (string, error) {
|
||||||
|
p := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, p); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token octets per RFC 2616.
|
||||||
|
var isTokenOctet = [256]bool{
|
||||||
|
'!': true,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': true,
|
||||||
|
'\'': true,
|
||||||
|
'*': true,
|
||||||
|
'+': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'W': true,
|
||||||
|
'V': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': true,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'|': true,
|
||||||
|
'~': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
|
||||||
|
// whitespace removed.
|
||||||
|
func skipSpace(s string) (rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if b := s[i]; b != ' ' && b != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextToken returns the leading RFC 2616 token of s and the string following
|
||||||
|
// the token.
|
||||||
|
func nextToken(s string) (token, rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if !isTokenOctet[s[i]] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:i], s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
|
||||||
|
// and the string following the token or quoted string.
|
||||||
|
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||||
|
if !strings.HasPrefix(s, "\"") {
|
||||||
|
return nextToken(s)
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '"':
|
||||||
|
return s[:i], s[i+1:]
|
||||||
|
case '\\':
|
||||||
|
p := make([]byte, len(s)-1)
|
||||||
|
j := copy(p, s[:i])
|
||||||
|
escape := true
|
||||||
|
for i = i + 1; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
switch {
|
||||||
|
case escape:
|
||||||
|
escape = false
|
||||||
|
p[j] = b
|
||||||
|
j++
|
||||||
|
case b == '\\':
|
||||||
|
escape = true
|
||||||
|
case b == '"':
|
||||||
|
return string(p[:j]), s[i+1:]
|
||||||
|
default:
|
||||||
|
p[j] = b
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding as
|
||||||
|
// defined in RFC 4790.
|
||||||
|
func equalASCIIFold(s, t string) bool {
|
||||||
|
for s != "" && t != "" {
|
||||||
|
sr, size := utf8.DecodeRuneInString(s)
|
||||||
|
s = s[size:]
|
||||||
|
tr, size := utf8.DecodeRuneInString(t)
|
||||||
|
t = t[size:]
|
||||||
|
if sr == tr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= sr && sr <= 'Z' {
|
||||||
|
sr = sr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if 'A' <= tr && tr <= 'Z' {
|
||||||
|
tr = tr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if sr != tr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s == t
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenListContainsValue returns true if the 1#token header with the given
|
||||||
|
// name contains a token equal to value with ASCII case folding.
|
||||||
|
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||||
|
headers:
|
||||||
|
for _, s := range header[name] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
if equalASCIIFold(t, value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtensions parses WebSocket extensions from a header.
|
||||||
|
func parseExtensions(header http.Header) []map[string]string {
|
||||||
|
// From RFC 6455:
|
||||||
|
//
|
||||||
|
// Sec-WebSocket-Extensions = extension-list
|
||||||
|
// extension-list = 1#extension
|
||||||
|
// extension = extension-token *( ";" extension-param )
|
||||||
|
// extension-token = registered-token
|
||||||
|
// registered-token = token
|
||||||
|
// extension-param = token [ "=" (token | quoted-string) ]
|
||||||
|
// ;When using the quoted-string syntax variant, the value
|
||||||
|
// ;after quoted-string unescaping MUST conform to the
|
||||||
|
// ;'token' ABNF.
|
||||||
|
|
||||||
|
var result []map[string]string
|
||||||
|
headers:
|
||||||
|
for _, s := range header["Sec-Websocket-Extensions"] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext := map[string]string{"": t}
|
||||||
|
for {
|
||||||
|
s = skipSpace(s)
|
||||||
|
if !strings.HasPrefix(s, ";") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var k string
|
||||||
|
k, s = nextToken(skipSpace(s[1:]))
|
||||||
|
if k == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
var v string
|
||||||
|
if strings.HasPrefix(s, "=") {
|
||||||
|
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
|
||||||
|
s = skipSpace(s)
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' && s[0] != ';' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext[k] = v
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
result = append(result, ext)
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidChallengeKey checks if the argument meets RFC6455 specification.
|
||||||
|
func isValidChallengeKey(s string) bool {
|
||||||
|
// From RFC6455:
|
||||||
|
//
|
||||||
|
// A |Sec-WebSocket-Key| header field with a base64-encoded (see
|
||||||
|
// Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
|
||||||
|
// length.
|
||||||
|
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
return err == nil && len(decoded) == 16
|
||||||
|
}
|
||||||
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
||||||
|
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
|
||||||
|
|
||||||
|
// Package proxy provides support for a variety of protocols to proxy network
|
||||||
|
// data.
|
||||||
|
//
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxy_direct struct{}
|
||||||
|
|
||||||
|
// Direct is a direct proxy: one that makes network connections directly.
|
||||||
|
var proxy_Direct = proxy_direct{}
|
||||||
|
|
||||||
|
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
|
// requested matches one of a number of exceptions.
|
||||||
|
type proxy_PerHost struct {
|
||||||
|
def, bypass proxy_Dialer
|
||||||
|
|
||||||
|
bypassNetworks []*net.IPNet
|
||||||
|
bypassIPs []net.IP
|
||||||
|
bypassZones []string
|
||||||
|
bypassHosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||||
|
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||||
|
// the configured rules.
|
||||||
|
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
|
||||||
|
return &proxy_PerHost{
|
||||||
|
def: defaultDialer,
|
||||||
|
bypass: bypass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.dialerForRequest(host).Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
for _, net := range p.bypassNetworks {
|
||||||
|
if net.Contains(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassIP := range p.bypassIPs {
|
||||||
|
if bypassIP.Equal(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range p.bypassZones {
|
||||||
|
if strings.HasSuffix(host, zone) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
if host == zone[1:] {
|
||||||
|
// For a zone ".example.com", we match "example.com"
|
||||||
|
// too.
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassHost := range p.bypassHosts {
|
||||||
|
if bypassHost == host {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromString parses a string that contains comma-separated values
|
||||||
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
|
// ignored.
|
||||||
|
func (p *proxy_PerHost) AddFromString(s string) {
|
||||||
|
hosts := strings.Split(s, ",")
|
||||||
|
for _, host := range hosts {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "/") {
|
||||||
|
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||||
|
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||||
|
p.AddNetwork(net)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
p.AddIP(ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
p.AddZone(host[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.AddHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match an IP.
|
||||||
|
func (p *proxy_PerHost) AddIP(ip net.IP) {
|
||||||
|
p.bypassIPs = append(p.bypassIPs, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match.
|
||||||
|
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
|
||||||
|
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||||
|
// "example.com" matches "example.com" and all of its subdomains.
|
||||||
|
func (p *proxy_PerHost) AddZone(zone string) {
|
||||||
|
if strings.HasSuffix(zone, ".") {
|
||||||
|
zone = zone[:len(zone)-1]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(zone, ".") {
|
||||||
|
zone = "." + zone
|
||||||
|
}
|
||||||
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
|
func (p *proxy_PerHost) AddHost(host string) {
|
||||||
|
if strings.HasSuffix(host, ".") {
|
||||||
|
host = host[:len(host)-1]
|
||||||
|
}
|
||||||
|
p.bypassHosts = append(p.bypassHosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer is a means to establish a connection.
|
||||||
|
type proxy_Dialer interface {
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth contains authentication parameters that specific Dialers may require.
|
||||||
|
type proxy_Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||||
|
// the environment.
|
||||||
|
func proxy_FromEnvironment() proxy_Dialer {
|
||||||
|
allProxy := proxy_allProxyEnv.Get()
|
||||||
|
if len(allProxy) == 0 {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(allProxy)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxy := proxy_noProxyEnv.Get()
|
||||||
|
if len(noProxy) == 0 {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
perHost := proxy_NewPerHost(proxy, proxy_Direct)
|
||||||
|
perHost.AddFromString(noProxy)
|
||||||
|
return perHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||||
|
// from a URL with such a scheme.
|
||||||
|
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
|
||||||
|
|
||||||
|
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||||
|
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||||
|
// by FromURL.
|
||||||
|
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
|
||||||
|
if proxy_proxySchemes == nil {
|
||||||
|
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
|
||||||
|
}
|
||||||
|
proxy_proxySchemes[scheme] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromURL returns a Dialer given a URL specification and an underlying
|
||||||
|
// Dialer for it to make network requests.
|
||||||
|
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
var auth *proxy_Auth
|
||||||
|
if u.User != nil {
|
||||||
|
auth = new(proxy_Auth)
|
||||||
|
auth.User = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
auth.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "socks5":
|
||||||
|
return proxy_SOCKS5("tcp", u.Host, auth, forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||||
|
// was registered by another package.
|
||||||
|
if proxy_proxySchemes != nil {
|
||||||
|
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
|
||||||
|
return f(u, forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
proxy_allProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
proxy_noProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type proxy_envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
|
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
s := &proxy_socks5{
|
||||||
|
network: network,
|
||||||
|
addr: addr,
|
||||||
|
forward: forward,
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
s.user = auth.User
|
||||||
|
s.password = auth.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy_socks5 struct {
|
||||||
|
user, password string
|
||||||
|
network, addr string
|
||||||
|
forward proxy_Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy_socks5Version = 5
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5AuthNone = 0
|
||||||
|
proxy_socks5AuthPassword = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const proxy_socks5Connect = 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5IP4 = 1
|
||||||
|
proxy_socks5Domain = 3
|
||||||
|
proxy_socks5IP6 = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxy_socks5Errors = []string{
|
||||||
|
"",
|
||||||
|
"general failure",
|
||||||
|
"connection forbidden",
|
||||||
|
"network unreachable",
|
||||||
|
"host unreachable",
|
||||||
|
"connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"command not supported",
|
||||||
|
"address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
|
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.forward.Dial(s.network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.connect(conn, addr); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
|
||||||
|
host, portStr, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, proxy_socks5Version)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
|
if buf[1] == proxy_socks5AuthPassword {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, proxy_socks5IP4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, proxy_socks5IP6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, proxy_socks5Domain)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(proxy_socks5Errors) {
|
||||||
|
failure = proxy_socks5Errors[buf[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case proxy_socks5IP4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case proxy_socks5IP6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case proxy_socks5Domain:
|
||||||
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also need to discard the port number
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
vendor/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
Normal file
18
vendor/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
- master
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
- go vet ./...
|
||||||
|
- test -z "$(gofmt -d -s . | tee /dev/stderr)"
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
||||||
29
vendor/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
Normal file
29
vendor/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2013, Julien Schmidt
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
300
vendor/github.com/julienschmidt/httprouter/README.md
generated
vendored
Normal file
300
vendor/github.com/julienschmidt/httprouter/README.md
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
# HttpRouter [](https://travis-ci.org/julienschmidt/httprouter) [](https://coveralls.io/github/julienschmidt/httprouter?branch=master) [](http://godoc.org/github.com/julienschmidt/httprouter)
|
||||||
|
|
||||||
|
HttpRouter is a lightweight high performance HTTP request router (also called *multiplexer* or just *mux* for short) for [Go](https://golang.org/).
|
||||||
|
|
||||||
|
In contrast to the [default mux](https://golang.org/pkg/net/http/#ServeMux) of Go's `net/http` package, this router supports variables in the routing pattern and matches against the request method. It also scales better.
|
||||||
|
|
||||||
|
The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**Only explicit matches:** With other routers, like [`http.ServeMux`](https://golang.org/pkg/net/http/#ServeMux), a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like *longest match* or *first registered, first matched*. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO and improves the user experience.
|
||||||
|
|
||||||
|
**Stop caring about trailing slashes:** Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can [turn off this behavior](https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash).
|
||||||
|
|
||||||
|
**Path auto-correction:** Besides detecting the missing or additional trailing slash at no extra cost, the router can also fix wrong cases and remove superfluous path elements (like `../` or `//`). Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? HttpRouter can help him by making a case-insensitive look-up and redirecting him to the correct URL.
|
||||||
|
|
||||||
|
**Parameters in your routing pattern:** Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap.
|
||||||
|
|
||||||
|
**Zero Garbage:** The matching and dispatching process generates zero bytes of garbage. The only heap allocations that are made are building the slice of the key-value pairs for path parameters, and building new context and request objects (the latter only in the standard `Handler`/`HandlerFunc` API). In the 3-argument API, if the request path contains no parameters not a single heap allocation is necessary.
|
||||||
|
|
||||||
|
**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). See below for technical details of the implementation.
|
||||||
|
|
||||||
|
**No more server crashes:** You can set a [Panic handler](https://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics occurring during handling a HTTP request. The router then recovers and lets the `PanicHandler` log what happened and deliver a nice error page.
|
||||||
|
|
||||||
|
**Perfect for APIs:** The router design encourages to build sensible, hierarchical RESTful APIs. Moreover it has built-in native support for [OPTIONS requests](http://zacstewart.com/2012/04/14/http-options-method.html) and `405 Method Not Allowed` replies.
|
||||||
|
|
||||||
|
Of course you can also set **custom [`NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and [`MethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](https://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
|
||||||
|
|
||||||
|
Let's start with a trivial example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Welcome!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/hello/:name", Hello)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Named parameters
|
||||||
|
|
||||||
|
As you can see, `:name` is a *named parameter*. The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: `:name` can be retrieved by `ByName("name")`.
|
||||||
|
|
||||||
|
When using a `http.Handler` (using `router.Handler` or `http.HandlerFunc`) instead of HttpRouter's handle API using a 3rd function parameter, the named parameters are stored in the `request.Context`. See more below under [Why doesn't this work with http.Handler?](#why-doesnt-this-work-with-httphandler).
|
||||||
|
|
||||||
|
Named parameters only match a single path segment:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pattern: /user/:user
|
||||||
|
|
||||||
|
/user/gordon match
|
||||||
|
/user/you match
|
||||||
|
/user/gordon/profile no match
|
||||||
|
/user/ no match
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other.
|
||||||
|
|
||||||
|
### Catch-All parameters
|
||||||
|
|
||||||
|
The second type are *catch-all* parameters and have the form `*name`. Like the name suggests, they match everything. Therefore they must always be at the **end** of the pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pattern: /src/*filepath
|
||||||
|
|
||||||
|
/src/ match
|
||||||
|
/src/somefile.go match
|
||||||
|
/src/subdir/somefile.go match
|
||||||
|
```
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
The router relies on a tree structure which makes heavy use of *common prefixes*, it is basically a *compact* [*prefix tree*](https://en.wikipedia.org/wiki/Trie) (or just [*Radix tree*](https://en.wikipedia.org/wiki/Radix_tree)). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree for the `GET` request method could look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Priority Path Handle
|
||||||
|
9 \ *<1>
|
||||||
|
3 ├s nil
|
||||||
|
2 |├earch\ *<2>
|
||||||
|
1 |└upport\ *<3>
|
||||||
|
2 ├blog\ *<4>
|
||||||
|
1 | └:post nil
|
||||||
|
1 | └\ *<5>
|
||||||
|
2 ├about-us\ *<6>
|
||||||
|
1 | └team\ *<7>
|
||||||
|
1 └contact\ *<8>
|
||||||
|
```
|
||||||
|
|
||||||
|
Every `*<num>` represents the memory address of a handler function (a pointer). If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder ([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a tree structure also allows us to use dynamic parts like the `:post` parameter, since we actually match against the routing patterns instead of just comparing hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), this works very well and efficient.
|
||||||
|
|
||||||
|
Since URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, it also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree.
|
||||||
|
|
||||||
|
For even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..). This helps in two ways:
|
||||||
|
|
||||||
|
1. Nodes which are part of the most routing paths are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.
|
||||||
|
2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The following scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.
|
||||||
|
|
||||||
|
```
|
||||||
|
├------------
|
||||||
|
├---------
|
||||||
|
├-----
|
||||||
|
├----
|
||||||
|
├--
|
||||||
|
├--
|
||||||
|
└-
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why doesn't this work with `http.Handler`?
|
||||||
|
|
||||||
|
**It does!** The router itself implements the `http.Handler` interface. Moreover the router provides convenient [adapters for `http.Handler`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [`http.HandlerFunc`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s which allows them to be used as a [`httprouter.Handle`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route.
|
||||||
|
|
||||||
|
Named parameters can be accessed `request.Context`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Hello(w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := httprouter.ParamsFromContext(r.Context())
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, one can also use `params := r.Context().Value(httprouter.ParamsKey)` instead of the helper function.
|
||||||
|
|
||||||
|
Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
|
||||||
|
|
||||||
|
## Automatic OPTIONS responses and CORS
|
||||||
|
|
||||||
|
One might wish to modify automatic responses to OPTIONS requests, e.g. to support [CORS preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/preflight_request) or to set other headers.
|
||||||
|
This can be achieved using the [`Router.GlobalOPTIONS`](https://godoc.org/github.com/julienschmidt/httprouter#Router.GlobalOPTIONS) handler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Access-Control-Request-Method") != "" {
|
||||||
|
// Set CORS headers
|
||||||
|
header := w.Header()
|
||||||
|
header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
|
||||||
|
header.Set("Access-Control-Allow-Origin", "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust status code to 204
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Where can I find Middleware *X*?
|
||||||
|
|
||||||
|
This package just provides a very efficient request router with a few extra features. The router is just a [`http.Handler`](https://golang.org/pkg/net/http/#Handler), you can chain any http.Handler compatible middleware before the router, for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](https://justinas.org/writing-http-middleware-in-go/), it's very easy!
|
||||||
|
|
||||||
|
Alternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter).
|
||||||
|
|
||||||
|
### Multi-domain / Sub-domains
|
||||||
|
|
||||||
|
Here is a quick example: Does your server serve multiple domains / hosts?
|
||||||
|
You want to use sub-domains?
|
||||||
|
Define a router per host!
|
||||||
|
|
||||||
|
```go
|
||||||
|
// We need an object that implements the http.Handler interface.
|
||||||
|
// Therefore we need a type for which we implement the ServeHTTP method.
|
||||||
|
// We just use a map here, in which we map host names (with port) to http.Handlers
|
||||||
|
type HostSwitch map[string]http.Handler
|
||||||
|
|
||||||
|
// Implement the ServeHTTP method on our new type
|
||||||
|
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check if a http.Handler is registered for the given host.
|
||||||
|
// If yes, use it to handle the request.
|
||||||
|
if handler := hs[r.Host]; handler != nil {
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
// Handle host names for which no handler is registered
|
||||||
|
http.Error(w, "Forbidden", 403) // Or Redirect?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize a router as usual
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/hello/:name", Hello)
|
||||||
|
|
||||||
|
// Make a new HostSwitch and insert the router (our http handler)
|
||||||
|
// for example.com and port 12345
|
||||||
|
hs := make(HostSwitch)
|
||||||
|
hs["example.com:12345"] = router
|
||||||
|
|
||||||
|
// Use the HostSwitch to listen and serve on port 12345
|
||||||
|
log.Fatal(http.ListenAndServe(":12345", hs))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Authentication
|
||||||
|
|
||||||
|
Another quick example: Basic Authentication (RFC 2617) for handles:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// Get the Basic Authentication credentials
|
||||||
|
user, password, hasAuth := r.BasicAuth()
|
||||||
|
|
||||||
|
if hasAuth && user == requiredUser && password == requiredPassword {
|
||||||
|
// Delegate request to the given handle
|
||||||
|
h(w, r, ps)
|
||||||
|
} else {
|
||||||
|
// Request Basic Authentication otherwise
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Not protected!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
fmt.Fprint(w, "Protected!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
user := "gordon"
|
||||||
|
pass := "secret!"
|
||||||
|
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", Index)
|
||||||
|
router.GET("/protected/", BasicAuth(Protected, user, pass))
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chaining with the NotFound handler
|
||||||
|
|
||||||
|
**NOTE: It might be required to set [`Router.HandleMethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.**
|
||||||
|
|
||||||
|
You can use another [`http.Handler`](https://golang.org/pkg/net/http/#Handler), for example another router, to handle requests which could not be matched by this router by using the [`Router.NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining.
|
||||||
|
|
||||||
|
### Static files
|
||||||
|
|
||||||
|
The `NotFound` handler can for example be used to serve static files from the root path `/` (like an `index.html` file along with other assets):
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Serve static files from the ./public directory
|
||||||
|
router.NotFound = http.FileServer(http.Dir("public"))
|
||||||
|
```
|
||||||
|
|
||||||
|
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
|
||||||
|
|
||||||
|
## Web Frameworks based on HttpRouter
|
||||||
|
|
||||||
|
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
|
||||||
|
|
||||||
|
* [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework
|
||||||
|
* [api2go](https://github.com/manyminds/api2go): A JSON API Implementation for Go
|
||||||
|
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
|
||||||
|
* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go
|
||||||
|
* [goMiddlewareChain](https://github.com/TobiEiss/goMiddlewareChain): An express.js-like-middleware-chain
|
||||||
|
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
|
||||||
|
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow
|
||||||
|
* [httpway](https://github.com/corneldamian/httpway): Simple middleware extension with context for httprouter and a server with gracefully shutdown support
|
||||||
|
* [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context
|
||||||
|
* [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba
|
||||||
|
* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang
|
||||||
|
* [pbgo](https://github.com/chai2010/pbgo): pbgo is a mini RPC/REST framework based on Protobuf
|
||||||
|
* [River](https://github.com/abiosoft/river): River is a simple and lightweight REST server
|
||||||
|
* [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts
|
||||||
|
* [xmux](https://github.com/rs/xmux): xmux is a httprouter fork on top of xhandler (net/context aware)
|
||||||
123
vendor/github.com/julienschmidt/httprouter/path.go
generated
vendored
Normal file
123
vendor/github.com/julienschmidt/httprouter/path.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Based on the path package, Copyright 2009 The Go Authors.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||||
|
// for p, eliminating . and .. elements.
|
||||||
|
//
|
||||||
|
// The following rules are applied iteratively until no further processing can
|
||||||
|
// be done:
|
||||||
|
// 1. Replace multiple slashes with a single slash.
|
||||||
|
// 2. Eliminate each . path name element (the current directory).
|
||||||
|
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||||
|
// along with the non-.. element that precedes it.
|
||||||
|
// 4. Eliminate .. elements that begin a rooted path:
|
||||||
|
// that is, replace "/.." by "/" at the beginning of a path.
|
||||||
|
//
|
||||||
|
// If the result of this process is an empty string, "/" is returned
|
||||||
|
func CleanPath(p string) string {
|
||||||
|
// Turn empty string into "/"
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(p)
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// reading from path; r is index of next byte to process.
|
||||||
|
// writing to buf; w is index of next byte to write.
|
||||||
|
|
||||||
|
// path must start with '/'
|
||||||
|
r := 1
|
||||||
|
w := 1
|
||||||
|
|
||||||
|
if p[0] != '/' {
|
||||||
|
r = 0
|
||||||
|
buf = make([]byte, n+1)
|
||||||
|
buf[0] = '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing := n > 1 && p[n-1] == '/'
|
||||||
|
|
||||||
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||||
|
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||||
|
// loop has no expensive function calls (except 1x make)
|
||||||
|
|
||||||
|
for r < n {
|
||||||
|
switch {
|
||||||
|
case p[r] == '/':
|
||||||
|
// empty path element, trailing slash is added after the end
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && r+1 == n:
|
||||||
|
trailing = true
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
|
// . element
|
||||||
|
r += 2
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
|
// .. element: remove to last /
|
||||||
|
r += 3
|
||||||
|
|
||||||
|
if w > 1 {
|
||||||
|
// can backtrack
|
||||||
|
w--
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
for w > 1 && p[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for w > 1 && buf[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// real path element.
|
||||||
|
// add slash if needed
|
||||||
|
if w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy element
|
||||||
|
for r < n && p[r] != '/' {
|
||||||
|
bufApp(&buf, p, w, p[r])
|
||||||
|
w++
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-append trailing slash
|
||||||
|
if trailing && w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
return p[:w]
|
||||||
|
}
|
||||||
|
return string(buf[:w])
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helper to lazily create a buffer if necessary
|
||||||
|
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||||
|
if *buf == nil {
|
||||||
|
if s[w] == c {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*buf = make([]byte, len(s))
|
||||||
|
copy(*buf, s[:w])
|
||||||
|
}
|
||||||
|
(*buf)[w] = c
|
||||||
|
}
|
||||||
452
vendor/github.com/julienschmidt/httprouter/router.go
generated
vendored
Normal file
452
vendor/github.com/julienschmidt/httprouter/router.go
generated
vendored
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
// Package httprouter is a trie based high performance HTTP request router.
|
||||||
|
//
|
||||||
|
// A trivial example is:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
// "github.com/julienschmidt/httprouter"
|
||||||
|
// "net/http"
|
||||||
|
// "log"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
// fmt.Fprint(w, "Welcome!\n")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// router := httprouter.New()
|
||||||
|
// router.GET("/", Index)
|
||||||
|
// router.GET("/hello/:name", Hello)
|
||||||
|
//
|
||||||
|
// log.Fatal(http.ListenAndServe(":8080", router))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The router matches incoming requests by the request method and the path.
|
||||||
|
// If a handle is registered for this path and method, the router delegates the
|
||||||
|
// request to that function.
|
||||||
|
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
|
||||||
|
// register handles, for all other methods router.Handle can be used.
|
||||||
|
//
|
||||||
|
// The registered path, against which the router matches incoming requests, can
|
||||||
|
// contain two types of parameters:
|
||||||
|
// Syntax Type
|
||||||
|
// :name named parameter
|
||||||
|
// *name catch-all parameter
|
||||||
|
//
|
||||||
|
// Named parameters are dynamic path segments. They match anything until the
|
||||||
|
// next '/' or the path end:
|
||||||
|
// Path: /blog/:category/:post
|
||||||
|
//
|
||||||
|
// Requests:
|
||||||
|
// /blog/go/request-routers match: category="go", post="request-routers"
|
||||||
|
// /blog/go/request-routers/ no match, but the router would redirect
|
||||||
|
// /blog/go/ no match
|
||||||
|
// /blog/go/request-routers/comments no match
|
||||||
|
//
|
||||||
|
// Catch-all parameters match anything until the path end, including the
|
||||||
|
// directory index (the '/' before the catch-all). Since they match anything
|
||||||
|
// until the end, catch-all parameters must always be the final path element.
|
||||||
|
// Path: /files/*filepath
|
||||||
|
//
|
||||||
|
// Requests:
|
||||||
|
// /files/ match: filepath="/"
|
||||||
|
// /files/LICENSE match: filepath="/LICENSE"
|
||||||
|
// /files/templates/article.html match: filepath="/templates/article.html"
|
||||||
|
// /files no match, but the router would redirect
|
||||||
|
//
|
||||||
|
// The value of parameters is saved as a slice of the Param struct, consisting
|
||||||
|
// each of a key and a value. The slice is passed to the Handle func as a third
|
||||||
|
// parameter.
|
||||||
|
// There are two ways to retrieve the value of a parameter:
|
||||||
|
// // by the name of the parameter
|
||||||
|
// user := ps.ByName("user") // defined by :user or *user
|
||||||
|
//
|
||||||
|
// // by the index of the parameter. This way you can also get the name (key)
|
||||||
|
// thirdKey := ps[2].Key // the name of the 3rd parameter
|
||||||
|
// thirdValue := ps[2].Value // the value of the 3rd parameter
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle is a function that can be registered to a route to handle HTTP
|
||||||
|
// requests. Like http.HandlerFunc, but has a third parameter for the values of
|
||||||
|
// wildcards (variables).
|
||||||
|
type Handle func(http.ResponseWriter, *http.Request, Params)
|
||||||
|
|
||||||
|
// Param is a single URL parameter, consisting of a key and a value.
|
||||||
|
type Param struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params is a Param-slice, as returned by the router.
|
||||||
|
// The slice is ordered, the first URL parameter is also the first slice value.
|
||||||
|
// It is therefore safe to read values by the index.
|
||||||
|
type Params []Param
|
||||||
|
|
||||||
|
// ByName returns the value of the first Param which key matches the given name.
|
||||||
|
// If no matching Param is found, an empty string is returned.
|
||||||
|
func (ps Params) ByName(name string) string {
|
||||||
|
for i := range ps {
|
||||||
|
if ps[i].Key == name {
|
||||||
|
return ps[i].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramsKey struct{}
|
||||||
|
|
||||||
|
// ParamsKey is the request context key under which URL params are stored.
|
||||||
|
var ParamsKey = paramsKey{}
|
||||||
|
|
||||||
|
// ParamsFromContext pulls the URL parameters from a request context,
|
||||||
|
// or returns nil if none are present.
|
||||||
|
func ParamsFromContext(ctx context.Context) Params {
|
||||||
|
p, _ := ctx.Value(ParamsKey).(Params)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router is a http.Handler which can be used to dispatch requests to different
|
||||||
|
// handler functions via configurable routes
|
||||||
|
type Router struct {
|
||||||
|
trees map[string]*node
|
||||||
|
|
||||||
|
// Enables automatic redirection if the current route can't be matched but a
|
||||||
|
// handler for the path with (without) the trailing slash exists.
|
||||||
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
|
// client is redirected to /foo with http status code 301 for GET requests
|
||||||
|
// and 307 for all other request methods.
|
||||||
|
RedirectTrailingSlash bool
|
||||||
|
|
||||||
|
// If enabled, the router tries to fix the current request path, if no
|
||||||
|
// handle is registered for it.
|
||||||
|
// First superfluous path elements like ../ or // are removed.
|
||||||
|
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||||
|
// If a handle can be found for this route, the router makes a redirection
|
||||||
|
// to the corrected path with status code 301 for GET requests and 307 for
|
||||||
|
// all other request methods.
|
||||||
|
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||||
|
// RedirectTrailingSlash is independent of this option.
|
||||||
|
RedirectFixedPath bool
|
||||||
|
|
||||||
|
// If enabled, the router checks if another method is allowed for the
|
||||||
|
// current route, if the current request can not be routed.
|
||||||
|
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||||
|
// and HTTP status code 405.
|
||||||
|
// If no other Method is allowed, the request is delegated to the NotFound
|
||||||
|
// handler.
|
||||||
|
HandleMethodNotAllowed bool
|
||||||
|
|
||||||
|
// If enabled, the router automatically replies to OPTIONS requests.
|
||||||
|
// Custom OPTIONS handlers take priority over automatic replies.
|
||||||
|
HandleOPTIONS bool
|
||||||
|
|
||||||
|
// An optional http.Handler that is called on automatic OPTIONS requests.
|
||||||
|
// The handler is only called if HandleOPTIONS is true and no OPTIONS
|
||||||
|
// handler for the specific path was set.
|
||||||
|
// The "Allowed" header is set before calling the handler.
|
||||||
|
GlobalOPTIONS http.Handler
|
||||||
|
|
||||||
|
// Cached value of global (*) allowed methods
|
||||||
|
globalAllowed string
|
||||||
|
|
||||||
|
// Configurable http.Handler which is called when no matching route is
|
||||||
|
// found. If it is not set, http.NotFound is used.
|
||||||
|
NotFound http.Handler
|
||||||
|
|
||||||
|
// Configurable http.Handler which is called when a request
|
||||||
|
// cannot be routed and HandleMethodNotAllowed is true.
|
||||||
|
// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
|
||||||
|
// The "Allow" header with allowed request methods is set before the handler
|
||||||
|
// is called.
|
||||||
|
MethodNotAllowed http.Handler
|
||||||
|
|
||||||
|
// Function to handle panics recovered from http handlers.
|
||||||
|
// It should be used to generate a error page and return the http error code
|
||||||
|
// 500 (Internal Server Error).
|
||||||
|
// The handler can be used to keep your server from crashing because of
|
||||||
|
// unrecovered panics.
|
||||||
|
PanicHandler func(http.ResponseWriter, *http.Request, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the Router conforms with the http.Handler interface
|
||||||
|
var _ http.Handler = New()
|
||||||
|
|
||||||
|
// New returns a new initialized Router.
|
||||||
|
// Path auto-correction, including trailing slashes, is enabled by default.
|
||||||
|
func New() *Router {
|
||||||
|
return &Router{
|
||||||
|
RedirectTrailingSlash: true,
|
||||||
|
RedirectFixedPath: true,
|
||||||
|
HandleMethodNotAllowed: true,
|
||||||
|
HandleOPTIONS: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
|
||||||
|
func (r *Router) GET(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodGet, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
|
||||||
|
func (r *Router) HEAD(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodHead, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
|
||||||
|
func (r *Router) OPTIONS(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodOptions, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST is a shortcut for router.Handle(http.MethodPost, path, handle)
|
||||||
|
func (r *Router) POST(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPost, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
|
||||||
|
func (r *Router) PUT(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPut, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
|
||||||
|
func (r *Router) PATCH(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodPatch, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
|
||||||
|
func (r *Router) DELETE(path string, handle Handle) {
|
||||||
|
r.Handle(http.MethodDelete, path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new request handle with the given path and method.
|
||||||
|
//
|
||||||
|
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||||
|
// functions can be used.
|
||||||
|
//
|
||||||
|
// This function is intended for bulk loading and to allow the usage of less
|
||||||
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
|
// communication with a proxy).
|
||||||
|
func (r *Router) Handle(method, path string, handle Handle) {
|
||||||
|
if len(path) < 1 || path[0] != '/' {
|
||||||
|
panic("path must begin with '/' in path '" + path + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.trees == nil {
|
||||||
|
r.trees = make(map[string]*node)
|
||||||
|
}
|
||||||
|
|
||||||
|
root := r.trees[method]
|
||||||
|
if root == nil {
|
||||||
|
root = new(node)
|
||||||
|
r.trees[method] = root
|
||||||
|
|
||||||
|
r.globalAllowed = r.allowed("*", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
root.addRoute(path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is an adapter which allows the usage of an http.Handler as a
|
||||||
|
// request handle.
|
||||||
|
// The Params are available in the request context under ParamsKey.
|
||||||
|
func (r *Router) Handler(method, path string, handler http.Handler) {
|
||||||
|
r.Handle(method, path,
|
||||||
|
func(w http.ResponseWriter, req *http.Request, p Params) {
|
||||||
|
if len(p) > 0 {
|
||||||
|
ctx := req.Context()
|
||||||
|
ctx = context.WithValue(ctx, ParamsKey, p)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
|
||||||
|
// request handle.
|
||||||
|
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
||||||
|
r.Handler(method, path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFiles serves files from the given file system root.
|
||||||
|
// The path must end with "/*filepath", files are then served from the local
|
||||||
|
// path /defined/root/dir/*filepath.
|
||||||
|
// For example if root is "/etc" and *filepath is "passwd", the local file
|
||||||
|
// "/etc/passwd" would be served.
|
||||||
|
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
||||||
|
// of the Router's NotFound handler.
|
||||||
|
// To use the operating system's file system implementation,
|
||||||
|
// use http.Dir:
|
||||||
|
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
||||||
|
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
||||||
|
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
|
||||||
|
panic("path must end with /*filepath in path '" + path + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileServer := http.FileServer(root)
|
||||||
|
|
||||||
|
r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
|
||||||
|
req.URL.Path = ps.ByName("filepath")
|
||||||
|
fileServer.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if rcv := recover(); rcv != nil {
|
||||||
|
r.PanicHandler(w, req, rcv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup allows the manual lookup of a method + path combo.
|
||||||
|
// This is e.g. useful to build a framework around this router.
|
||||||
|
// If the path was found, it returns the handle function and the path parameter
|
||||||
|
// values. Otherwise the third return value indicates whether a redirection to
|
||||||
|
// the same path with an extra / without the trailing slash should be performed.
|
||||||
|
func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
|
||||||
|
if root := r.trees[method]; root != nil {
|
||||||
|
return root.getValue(path)
|
||||||
|
}
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) allowed(path, reqMethod string) (allow string) {
|
||||||
|
allowed := make([]string, 0, 9)
|
||||||
|
|
||||||
|
if path == "*" { // server-wide
|
||||||
|
// empty method is used for internal calls to refresh the cache
|
||||||
|
if reqMethod == "" {
|
||||||
|
for method := range r.trees {
|
||||||
|
if method == http.MethodOptions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, method)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return r.globalAllowed
|
||||||
|
}
|
||||||
|
} else { // specific path
|
||||||
|
for method := range r.trees {
|
||||||
|
// Skip the requested method - we already tried this one
|
||||||
|
if method == reqMethod || method == http.MethodOptions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, _, _ := r.trees[method].getValue(path)
|
||||||
|
if handle != nil {
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowed) > 0 {
|
||||||
|
// Add request method to list of allowed methods
|
||||||
|
allowed = append(allowed, http.MethodOptions)
|
||||||
|
|
||||||
|
// Sort allowed methods.
|
||||||
|
// sort.Strings(allowed) unfortunately causes unnecessary allocations
|
||||||
|
// due to allowed being moved to the heap and interface conversion
|
||||||
|
for i, l := 1, len(allowed); i < l; i++ {
|
||||||
|
for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
|
||||||
|
allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return as comma separated list
|
||||||
|
return strings.Join(allowed, ", ")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP makes the router implement the http.Handler interface.
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if r.PanicHandler != nil {
|
||||||
|
defer r.recv(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := req.URL.Path
|
||||||
|
|
||||||
|
if root := r.trees[req.Method]; root != nil {
|
||||||
|
if handle, ps, tsr := root.getValue(path); handle != nil {
|
||||||
|
handle(w, req, ps)
|
||||||
|
return
|
||||||
|
} else if req.Method != http.MethodConnect && path != "/" {
|
||||||
|
code := 301 // Permanent redirect, request with GET method
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
// Temporary redirect, request with same method
|
||||||
|
// As of Go 1.3, Go does not support status code 308.
|
||||||
|
code = 307
|
||||||
|
}
|
||||||
|
|
||||||
|
if tsr && r.RedirectTrailingSlash {
|
||||||
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||||
|
req.URL.Path = path[:len(path)-1]
|
||||||
|
} else {
|
||||||
|
req.URL.Path = path + "/"
|
||||||
|
}
|
||||||
|
http.Redirect(w, req, req.URL.String(), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to fix the request path
|
||||||
|
if r.RedirectFixedPath {
|
||||||
|
fixedPath, found := root.findCaseInsensitivePath(
|
||||||
|
CleanPath(path),
|
||||||
|
r.RedirectTrailingSlash,
|
||||||
|
)
|
||||||
|
if found {
|
||||||
|
req.URL.Path = string(fixedPath)
|
||||||
|
http.Redirect(w, req, req.URL.String(), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == http.MethodOptions && r.HandleOPTIONS {
|
||||||
|
// Handle OPTIONS requests
|
||||||
|
if allow := r.allowed(path, http.MethodOptions); allow != "" {
|
||||||
|
w.Header().Set("Allow", allow)
|
||||||
|
if r.GlobalOPTIONS != nil {
|
||||||
|
r.GlobalOPTIONS.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if r.HandleMethodNotAllowed { // Handle 405
|
||||||
|
if allow := r.allowed(path, req.Method); allow != "" {
|
||||||
|
w.Header().Set("Allow", allow)
|
||||||
|
if r.MethodNotAllowed != nil {
|
||||||
|
r.MethodNotAllowed.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
http.Error(w,
|
||||||
|
http.StatusText(http.StatusMethodNotAllowed),
|
||||||
|
http.StatusMethodNotAllowed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 404
|
||||||
|
if r.NotFound != nil {
|
||||||
|
r.NotFound.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
666
vendor/github.com/julienschmidt/httprouter/tree.go
generated
vendored
Normal file
666
vendor/github.com/julienschmidt/httprouter/tree.go
generated
vendored
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package httprouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a <= b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxParamCount uint8 = ^uint8(0)
|
||||||
|
|
||||||
|
func countParams(path string) uint8 {
|
||||||
|
var n uint
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] != ':' && path[i] != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n >= uint(maxParamCount) {
|
||||||
|
return maxParamCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
static nodeType = iota // default
|
||||||
|
root
|
||||||
|
param
|
||||||
|
catchAll
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
path string
|
||||||
|
wildChild bool
|
||||||
|
nType nodeType
|
||||||
|
maxParams uint8
|
||||||
|
priority uint32
|
||||||
|
indices string
|
||||||
|
children []*node
|
||||||
|
handle Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// increments priority of the given child and reorders if necessary
|
||||||
|
func (n *node) incrementChildPrio(pos int) int {
|
||||||
|
n.children[pos].priority++
|
||||||
|
prio := n.children[pos].priority
|
||||||
|
|
||||||
|
// adjust position (move to front)
|
||||||
|
newPos := pos
|
||||||
|
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||||
|
// swap node positions
|
||||||
|
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||||
|
|
||||||
|
newPos--
|
||||||
|
}
|
||||||
|
|
||||||
|
// build new index char string
|
||||||
|
if newPos != pos {
|
||||||
|
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||||
|
n.indices[pos:pos+1] + // the index char we move
|
||||||
|
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoute adds a node with the given handle to the path.
|
||||||
|
// Not concurrency-safe!
|
||||||
|
func (n *node) addRoute(path string, handle Handle) {
|
||||||
|
fullPath := path
|
||||||
|
n.priority++
|
||||||
|
numParams := countParams(path)
|
||||||
|
|
||||||
|
// non-empty tree
|
||||||
|
if len(n.path) > 0 || len(n.children) > 0 {
|
||||||
|
walk:
|
||||||
|
for {
|
||||||
|
// Update maxParams of the current node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the longest common prefix.
|
||||||
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
|
// since the existing key can't contain those chars.
|
||||||
|
i := 0
|
||||||
|
max := min(len(path), len(n.path))
|
||||||
|
for i < max && path[i] == n.path[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split edge
|
||||||
|
if i < len(n.path) {
|
||||||
|
child := node{
|
||||||
|
path: n.path[i:],
|
||||||
|
wildChild: n.wildChild,
|
||||||
|
nType: static,
|
||||||
|
indices: n.indices,
|
||||||
|
children: n.children,
|
||||||
|
handle: n.handle,
|
||||||
|
priority: n.priority - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update maxParams (max of all children)
|
||||||
|
for i := range child.children {
|
||||||
|
if child.children[i].maxParams > child.maxParams {
|
||||||
|
child.maxParams = child.children[i].maxParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.children = []*node{&child}
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices = string([]byte{n.path[i]})
|
||||||
|
n.path = path[:i]
|
||||||
|
n.handle = nil
|
||||||
|
n.wildChild = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make new node a child of this node
|
||||||
|
if i < len(path) {
|
||||||
|
path = path[i:]
|
||||||
|
|
||||||
|
if n.wildChild {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// Update maxParams of the child node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// Check if the wildcard matches
|
||||||
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||||
|
// Adding a child to a catchAll is not possible
|
||||||
|
n.nType != catchAll &&
|
||||||
|
// Check for longer wildcard, e.g. :name and :names
|
||||||
|
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||||
|
continue walk
|
||||||
|
} else {
|
||||||
|
// Wildcard conflict
|
||||||
|
var pathSeg string
|
||||||
|
if n.nType == catchAll {
|
||||||
|
pathSeg = path
|
||||||
|
} else {
|
||||||
|
pathSeg = strings.SplitN(path, "/", 2)[0]
|
||||||
|
}
|
||||||
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
|
panic("'" + pathSeg +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in existing prefix '" + prefix +
|
||||||
|
"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := path[0]
|
||||||
|
|
||||||
|
// slash after param
|
||||||
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a child with the next path byte exists
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
i = n.incrementChildPrio(i)
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert it
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
|
if n.handle != nil {
|
||||||
|
panic("a handle is already registered for path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
n.handle = handle
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else { // Empty tree
|
||||||
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
|
n.nType = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
|
||||||
|
var offset int // already handled bytes of the path
|
||||||
|
|
||||||
|
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||||
|
for i, max := 0, len(path); numParams > 0; i++ {
|
||||||
|
c := path[i]
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// find wildcard end (either '/' or path end)
|
||||||
|
end := i + 1
|
||||||
|
for end < max && path[end] != '/' {
|
||||||
|
switch path[end] {
|
||||||
|
// the wildcard name must not contain ':' and '*'
|
||||||
|
case ':', '*':
|
||||||
|
panic("only one wildcard per path segment is allowed, has: '" +
|
||||||
|
path[i:] + "' in path '" + fullPath + "'")
|
||||||
|
default:
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this Node existing children which would be
|
||||||
|
// unreachable if we insert the wildcard here
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
panic("wildcard route '" + path[i:end] +
|
||||||
|
"' conflicts with existing children in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the wildcard has a name
|
||||||
|
if end-i < 2 {
|
||||||
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == ':' { // param
|
||||||
|
// split path at the beginning of the wildcard
|
||||||
|
if i > 0 {
|
||||||
|
n.path = path[offset:i]
|
||||||
|
offset = i
|
||||||
|
}
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
nType: param,
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.wildChild = true
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// if the path doesn't end with the wildcard, then there
|
||||||
|
// will be another non-wildcard subpath starting with '/'
|
||||||
|
if end < max {
|
||||||
|
n.path = path[offset:end]
|
||||||
|
offset = end
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // catchAll
|
||||||
|
if end != max || numParams > 1 {
|
||||||
|
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
|
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently fixed width 1 for '/'
|
||||||
|
i--
|
||||||
|
if path[i] != '/' {
|
||||||
|
panic("no / before catch-all in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
n.path = path[offset:i]
|
||||||
|
|
||||||
|
// first node: catchAll node with empty path
|
||||||
|
child := &node{
|
||||||
|
wildChild: true,
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
}
|
||||||
|
// update maxParams of the parent node
|
||||||
|
if n.maxParams < 1 {
|
||||||
|
n.maxParams = 1
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.indices = string(path[i])
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// second node: node holding the variable
|
||||||
|
child = &node{
|
||||||
|
path: path[i:],
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
handle: handle,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert remaining path part and handle to the leaf
|
||||||
|
n.path = path[offset:]
|
||||||
|
n.handle = handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the handle registered with the given path (key). The values of
|
||||||
|
// wildcards are saved to a map.
|
||||||
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||||
|
// made if a handle exists with an extra (without the) trailing slash for the
|
||||||
|
// given path.
|
||||||
|
func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
|
||||||
|
walk: // outer loop for walking the tree
|
||||||
|
for {
|
||||||
|
if len(path) > len(n.path) {
|
||||||
|
if path[:len(n.path)] == n.path {
|
||||||
|
path = path[len(n.path):]
|
||||||
|
// If this node does not have a wildcard (param or catchAll)
|
||||||
|
// child, we can just look up the next child node and continue
|
||||||
|
// to walk down the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
c := path[0]
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// We can recommend to redirect to the same URL without a
|
||||||
|
// trailing slash if a leaf exists for that path.
|
||||||
|
tsr = (path == "/" && n.handle != nil)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle wildcard child
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
end := 0
|
||||||
|
for end < len(path) && path[end] != '/' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// save param value
|
||||||
|
if p == nil {
|
||||||
|
// lazy allocation
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[1:]
|
||||||
|
p[i].Value = path[:end]
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if end < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
path = path[end:]
|
||||||
|
n = n.children[0]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
tsr = (len(path) == end+1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle = n.handle; handle != nil {
|
||||||
|
return
|
||||||
|
} else if len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for TSR recommendation
|
||||||
|
n = n.children[0]
|
||||||
|
tsr = (n.path == "/" && n.handle != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
// save param value
|
||||||
|
if p == nil {
|
||||||
|
// lazy allocation
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[2:]
|
||||||
|
p[i].Value = path
|
||||||
|
|
||||||
|
handle = n.handle
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if path == n.path {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if handle = n.handle; handle != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
|
tsr = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for trailing slash recommendation
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handle != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
|
// extra trailing slash if a leaf exists for that path
|
||||||
|
tsr = (path == "/") ||
|
||||||
|
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||||
|
path == n.path[:len(n.path)-1] && n.handle != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||||
|
// It can optionally also fix trailing slashes.
|
||||||
|
// It returns the case-corrected path and a bool indicating whether the lookup
|
||||||
|
// was successful.
|
||||||
|
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
||||||
|
return n.findCaseInsensitivePathRec(
|
||||||
|
path,
|
||||||
|
make([]byte, 0, len(path)+1), // preallocate enough memory for new path
|
||||||
|
[4]byte{}, // empty rune buffer
|
||||||
|
fixTrailingSlash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift bytes in array by n bytes left
|
||||||
|
func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return rb
|
||||||
|
case 1:
|
||||||
|
return [4]byte{rb[1], rb[2], rb[3], 0}
|
||||||
|
case 2:
|
||||||
|
return [4]byte{rb[2], rb[3]}
|
||||||
|
case 3:
|
||||||
|
return [4]byte{rb[3]}
|
||||||
|
default:
|
||||||
|
return [4]byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive case-insensitive lookup function used by n.findCaseInsensitivePath
|
||||||
|
func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) {
|
||||||
|
npLen := len(n.path)
|
||||||
|
|
||||||
|
walk: // outer loop for walking the tree
|
||||||
|
for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
|
||||||
|
// add common prefix to result
|
||||||
|
|
||||||
|
oldPath := path
|
||||||
|
path = path[npLen:]
|
||||||
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
// If this node does not have a wildcard (param or catchAll) child,
|
||||||
|
// we can just look up the next child node and continue to walk down
|
||||||
|
// the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
// skip rune bytes already processed
|
||||||
|
rb = shiftNRuneBytes(rb, npLen)
|
||||||
|
|
||||||
|
if rb[0] != 0 {
|
||||||
|
// old rune not finished
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == rb[0] {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// process a new rune
|
||||||
|
var rv rune
|
||||||
|
|
||||||
|
// find rune start
|
||||||
|
// runes are up to 4 byte long,
|
||||||
|
// -4 would definitely be another rune
|
||||||
|
var off int
|
||||||
|
for max := min(npLen, 3); off < max; off++ {
|
||||||
|
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
||||||
|
// read rune from cached path
|
||||||
|
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate lowercase bytes of current rune
|
||||||
|
lo := unicode.ToLower(rv)
|
||||||
|
utf8.EncodeRune(rb[:], lo)
|
||||||
|
|
||||||
|
// skip already processed bytes
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
// lowercase matches
|
||||||
|
if n.indices[i] == rb[0] {
|
||||||
|
// must use a recursive approach since both the
|
||||||
|
// uppercase byte and the lowercase byte might exist
|
||||||
|
// as an index
|
||||||
|
if out, found := n.children[i].findCaseInsensitivePathRec(
|
||||||
|
path, ciPath, rb, fixTrailingSlash,
|
||||||
|
); found {
|
||||||
|
return out, true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found no match, the same for the uppercase rune,
|
||||||
|
// if it differs
|
||||||
|
if up := unicode.ToUpper(rv); up != lo {
|
||||||
|
utf8.EncodeRune(rb[:], up)
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
for i, c := 0, rb[0]; i < len(n.indices); i++ {
|
||||||
|
// uppercase matches
|
||||||
|
if n.indices[i] == c {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL
|
||||||
|
// without a trailing slash if a leaf exists for that path
|
||||||
|
return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
k := 0
|
||||||
|
for k < len(path) && path[k] != '/' {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
// add param value to case insensitive path
|
||||||
|
ciPath = append(ciPath, path[:k]...)
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if k < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[0]
|
||||||
|
npLen = len(n.path)
|
||||||
|
path = path[k:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == k+1 {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.handle != nil {
|
||||||
|
return ciPath, true
|
||||||
|
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists
|
||||||
|
n = n.children[0]
|
||||||
|
if n.path == "/" && n.handle != nil {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...), true
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if n.handle != nil {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found.
|
||||||
|
// Try to fix the path by adding a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
if (len(n.path) == 1 && n.handle != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handle != nil) {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// Try to fix the path by adding / removing a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
if path == "/" {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
if len(path)+1 == npLen && n.path[len(path)] == '/' &&
|
||||||
|
strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handle != nil {
|
||||||
|
return append(ciPath, n.path...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ciPath, false
|
||||||
|
}
|
||||||
1
vendor/github.com/lib/pq/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/lib/pq/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.db
|
||||||
|
*.test
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
242
vendor/github.com/lib/pq/CHANGELOG.md
generated
vendored
Normal file
242
vendor/github.com/lib/pq/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
unreleased
|
||||||
|
----------
|
||||||
|
|
||||||
|
- This release changes the default `sslmode` from `require` to `prefer`, which
|
||||||
|
is the default used by libpq and the rest of the PostgreSQL ecosystem. See
|
||||||
|
[#1271] for some background.
|
||||||
|
|
||||||
|
v1.12.0 (2026-03-18)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
- The next release may change the default sslmode from `require` to `prefer`.
|
||||||
|
See [#1271] for details.
|
||||||
|
|
||||||
|
- `CopyIn()` and `CopyInToSchema()` have been marked as deprecated. These are
|
||||||
|
simple query builders and not needed for `COPY [..] FROM STDIN` support (which
|
||||||
|
is *not* deprecated). ([#1279])
|
||||||
|
|
||||||
|
// Old
|
||||||
|
tx.Prepare(CopyIn("temp", "num", "text", "blob", "nothing"))
|
||||||
|
|
||||||
|
// Replacement
|
||||||
|
tx.Prepare(`copy temp (num, text, blob, nothing) from stdin`)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Support protocol 3.2, and the `min_protocol_version` and
|
||||||
|
`max_protocol_version` DSN parameters ([#1258]).
|
||||||
|
|
||||||
|
- Support `sslmode=prefer` and `sslmode=allow` ([#1270]).
|
||||||
|
|
||||||
|
- Support `ssl_min_protocol_version` and `ssl_max_protocol_version` ([#1277]).
|
||||||
|
|
||||||
|
- Support connection service file to load connection details ([#1285]).
|
||||||
|
|
||||||
|
- Support `sslrootcert=system` and use `~/.postgresql/root.crt` as the default
|
||||||
|
value of sslrootcert ([#1280], [#1281]).
|
||||||
|
|
||||||
|
- Add a new `pqerror` package with PostgreSQL error codes ([#1275]).
|
||||||
|
|
||||||
|
For example, to test if an error is a UNIQUE constraint violation:
|
||||||
|
|
||||||
|
if pqErr, ok := errors.AsType[*pq.Error](err); ok && pqErr.Code == pqerror.UniqueViolation {
|
||||||
|
log.Fatalf("email %q already exsts", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
To make this a bit more convenient, it also adds a `pq.As()` function:
|
||||||
|
|
||||||
|
pqErr := pq.As(err, pqerror.UniqueViolation)
|
||||||
|
if pqErr != nil {
|
||||||
|
log.Fatalf("email %q already exsts", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fix SSL key permission check to allow modes stricter than 0600/0640#1265 ([#1265]).
|
||||||
|
|
||||||
|
- Fix Hstore to work with binary parameters ([#1278]).
|
||||||
|
|
||||||
|
- Clearer error when starting a new query while pq is still processing another
|
||||||
|
query ([#1272]).
|
||||||
|
|
||||||
|
- Send intermediate CAs with client certificates, so they can be signed by an
|
||||||
|
intermediate CA ([#1267]).
|
||||||
|
|
||||||
|
- Use `time.UTC` for UTC aliases such as `Etc/UTC` ([#1282]).
|
||||||
|
|
||||||
|
[#1258]: https://github.com/lib/pq/pull/1258
|
||||||
|
[#1265]: https://github.com/lib/pq/pull/1265
|
||||||
|
[#1267]: https://github.com/lib/pq/pull/1267
|
||||||
|
[#1270]: https://github.com/lib/pq/pull/1270
|
||||||
|
[#1271]: https://github.com/lib/pq/pull/1271
|
||||||
|
[#1272]: https://github.com/lib/pq/pull/1272
|
||||||
|
[#1275]: https://github.com/lib/pq/pull/1275
|
||||||
|
[#1277]: https://github.com/lib/pq/pull/1277
|
||||||
|
[#1278]: https://github.com/lib/pq/pull/1278
|
||||||
|
[#1279]: https://github.com/lib/pq/pull/1279
|
||||||
|
[#1280]: https://github.com/lib/pq/pull/1280
|
||||||
|
[#1281]: https://github.com/lib/pq/pull/1281
|
||||||
|
[#1282]: https://github.com/lib/pq/pull/1282
|
||||||
|
[#1283]: https://github.com/lib/pq/pull/1283
|
||||||
|
[#1285]: https://github.com/lib/pq/pull/1285
|
||||||
|
|
||||||
|
v1.11.2 (2026-02-10)
|
||||||
|
--------------------
|
||||||
|
This fixes two regressions:
|
||||||
|
|
||||||
|
- Don't send startup parameters if there is no value, improving compatibility
|
||||||
|
with Supavisor ([#1260]).
|
||||||
|
|
||||||
|
- Don't send `dbname` as a startup parameter if `database=[..]` is used in the
|
||||||
|
connection string. It's recommended to use dbname=, as database= is not a
|
||||||
|
libpq option, and only worked by accident previously. ([#1261])
|
||||||
|
|
||||||
|
[#1260]: https://github.com/lib/pq/pull/1260
|
||||||
|
[#1261]: https://github.com/lib/pq/pull/1261
|
||||||
|
|
||||||
|
v1.11.1 (2026-01-29)
|
||||||
|
--------------------
|
||||||
|
This fixes two regressions present in the v1.11.0 release:
|
||||||
|
|
||||||
|
- Fix build on 32bit systems, Windows, and Plan 9 ([#1253]).
|
||||||
|
|
||||||
|
- Named []byte types and pointers to []byte (e.g. `*[]byte`, `json.RawMessage`)
|
||||||
|
would be treated as an array instead of bytea ([#1252]).
|
||||||
|
|
||||||
|
[#1252]: https://github.com/lib/pq/pull/1252
|
||||||
|
[#1253]: https://github.com/lib/pq/pull/1253
|
||||||
|
|
||||||
|
v1.11.0 (2026-01-28)
|
||||||
|
--------------------
|
||||||
|
This version of pq requires Go 1.21 or newer.
|
||||||
|
|
||||||
|
pq now supports only maintained PostgreSQL releases, which is PostgreSQL 14 and
|
||||||
|
newer. Previously PostgreSQL 8.4 and newer were supported.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- The `pq.Error.Error()` text includes the position of the error (if reported
|
||||||
|
by PostgreSQL) and SQLSTATE code ([#1219], [#1224]):
|
||||||
|
|
||||||
|
pq: column "columndoesntexist" does not exist at column 8 (42703)
|
||||||
|
pq: syntax error at or near ")" at position 2:71 (42601)
|
||||||
|
|
||||||
|
- The `pq.Error.ErrorWithDetail()` method prints a more detailed multiline
|
||||||
|
message, with the Detail, Hint, and error position (if any) ([#1219]):
|
||||||
|
|
||||||
|
ERROR: syntax error at or near ")" (42601)
|
||||||
|
CONTEXT: line 12, column 1:
|
||||||
|
|
||||||
|
10 | name varchar,
|
||||||
|
11 | version varchar,
|
||||||
|
12 | );
|
||||||
|
^
|
||||||
|
|
||||||
|
- Add `Config`, `NewConfig()`, and `NewConnectorConfig()` to supply connection
|
||||||
|
details in a more structured way ([#1240]).
|
||||||
|
|
||||||
|
- Support `hostaddr` and `$PGHOSTADDR` ([#1243]).
|
||||||
|
|
||||||
|
- Support multiple values in `host`, `port`, and `hostaddr`, which are each
|
||||||
|
tried in order, or randomly if `load_balance_hosts=random` is set ([#1246]).
|
||||||
|
|
||||||
|
- Support `target_session_attrs` connection parameter ([#1246]).
|
||||||
|
|
||||||
|
- Support [`sslnegotiation`] to use SSL without negotiation ([#1180]).
|
||||||
|
|
||||||
|
- Allow using a custom `tls.Config`, for example for encrypted keys ([#1228]).
|
||||||
|
|
||||||
|
- Add `PQGO_DEBUG=1` print the communication with PostgreSQL to stderr, to aid
|
||||||
|
in debugging, testing, and bug reports ([#1223]).
|
||||||
|
|
||||||
|
- Add support for NamedValueChecker interface ([#1125], [#1238]).
|
||||||
|
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Match HOME directory lookup logic with libpq: prefer $HOME over /etc/passwd,
|
||||||
|
ignore ENOTDIR errors, and use APPDATA on Windows ([#1214]).
|
||||||
|
|
||||||
|
- Fix `sslmode=verify-ca` verifying the hostname anyway when connecting to a DNS
|
||||||
|
name (rather than IP) ([#1226]).
|
||||||
|
|
||||||
|
- Correctly detect pre-protocol errors such as the server not being able to fork
|
||||||
|
or running out of memory ([#1248]).
|
||||||
|
|
||||||
|
- Fix build with wasm ([#1184]), appengine ([#745]), and Plan 9 ([#1133]).
|
||||||
|
|
||||||
|
- Deprecate and type alias `pq.NullTime` to `sql.NullTime` ([#1211]).
|
||||||
|
|
||||||
|
- Enforce integer limits of the Postgres wire protocol ([#1161]).
|
||||||
|
|
||||||
|
- Accept the `passfile` connection parameter to override `PGPASSFILE` ([#1129]).
|
||||||
|
|
||||||
|
- Fix connecting to socket on Windows systems ([#1179]).
|
||||||
|
|
||||||
|
- Don't perform a permission check on the .pgpass file on Windows ([#595]).
|
||||||
|
|
||||||
|
- Warn about incorrect .pgpass permissions ([#595]).
|
||||||
|
|
||||||
|
- Don't set extra_float_digits ([#1212]).
|
||||||
|
|
||||||
|
- Decode bpchar into a string ([#949]).
|
||||||
|
|
||||||
|
- Fix panic in Ping() by not requiring CommandComplete or EmptyQueryResponse in
|
||||||
|
simpleQuery() ([#1234])
|
||||||
|
|
||||||
|
- Recognize bit/varbit ([#743]) and float types ([#1166]) in ColumnTypeScanType().
|
||||||
|
|
||||||
|
- Accept `PGGSSLIB` and `PGKRBSRVNAME` environment variables ([#1143]).
|
||||||
|
|
||||||
|
- Handle ErrorResponse in readReadyForQuery and return proper error ([#1136]).
|
||||||
|
|
||||||
|
- Detect COPY even if the query starts with whitespace or comments ([#1198]).
|
||||||
|
|
||||||
|
- CopyIn() and CopyInSchema() now work if the list of columns is empty, in which
|
||||||
|
case it will copy all columns ([#1239]).
|
||||||
|
|
||||||
|
- Treat nil []byte in query parameters as nil/NULL rather than `""` ([#838]).
|
||||||
|
|
||||||
|
- Accept multiple authentication methods before checking AuthOk, which improves
|
||||||
|
compatibility with PgPool-II ([#1188]).
|
||||||
|
|
||||||
|
[`sslnegotiation`]: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLNEGOTIATION
|
||||||
|
[#595]: https://github.com/lib/pq/pull/595
|
||||||
|
[#745]: https://github.com/lib/pq/pull/745
|
||||||
|
[#743]: https://github.com/lib/pq/pull/743
|
||||||
|
[#838]: https://github.com/lib/pq/pull/838
|
||||||
|
[#949]: https://github.com/lib/pq/pull/949
|
||||||
|
[#1125]: https://github.com/lib/pq/pull/1125
|
||||||
|
[#1129]: https://github.com/lib/pq/pull/1129
|
||||||
|
[#1133]: https://github.com/lib/pq/pull/1133
|
||||||
|
[#1136]: https://github.com/lib/pq/pull/1136
|
||||||
|
[#1143]: https://github.com/lib/pq/pull/1143
|
||||||
|
[#1161]: https://github.com/lib/pq/pull/1161
|
||||||
|
[#1166]: https://github.com/lib/pq/pull/1166
|
||||||
|
[#1179]: https://github.com/lib/pq/pull/1179
|
||||||
|
[#1180]: https://github.com/lib/pq/pull/1180
|
||||||
|
[#1184]: https://github.com/lib/pq/pull/1184
|
||||||
|
[#1188]: https://github.com/lib/pq/pull/1188
|
||||||
|
[#1198]: https://github.com/lib/pq/pull/1198
|
||||||
|
[#1211]: https://github.com/lib/pq/pull/1211
|
||||||
|
[#1212]: https://github.com/lib/pq/pull/1212
|
||||||
|
[#1214]: https://github.com/lib/pq/pull/1214
|
||||||
|
[#1219]: https://github.com/lib/pq/pull/1219
|
||||||
|
[#1223]: https://github.com/lib/pq/pull/1223
|
||||||
|
[#1224]: https://github.com/lib/pq/pull/1224
|
||||||
|
[#1226]: https://github.com/lib/pq/pull/1226
|
||||||
|
[#1228]: https://github.com/lib/pq/pull/1228
|
||||||
|
[#1234]: https://github.com/lib/pq/pull/1234
|
||||||
|
[#1238]: https://github.com/lib/pq/pull/1238
|
||||||
|
[#1239]: https://github.com/lib/pq/pull/1239
|
||||||
|
[#1240]: https://github.com/lib/pq/pull/1240
|
||||||
|
[#1243]: https://github.com/lib/pq/pull/1243
|
||||||
|
[#1246]: https://github.com/lib/pq/pull/1246
|
||||||
|
[#1248]: https://github.com/lib/pq/pull/1248
|
||||||
|
|
||||||
|
|
||||||
|
v1.10.9 (2023-04-26)
|
||||||
|
--------------------
|
||||||
|
- Fixes backwards incompat bug with 1.13.
|
||||||
|
|
||||||
|
- Fixes pgpass issue
|
||||||
21
vendor/github.com/lib/pq/LICENSE
generated
vendored
Normal file
21
vendor/github.com/lib/pq/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2011-2013, 'pq' Contributors. Portions Copyright (c) 2011 Blake Mizerany
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
312
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
312
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
pq is a Go PostgreSQL driver for database/sql.
|
||||||
|
|
||||||
|
All [maintained versions of PostgreSQL] are supported. Older versions may work,
|
||||||
|
but this is not tested. [API docs].
|
||||||
|
|
||||||
|
[maintained versions of PostgreSQL]: https://www.postgresql.org/support/versioning
|
||||||
|
[API docs]: https://pkg.go.dev/github.com/lib/pq
|
||||||
|
|
||||||
|
Connecting
|
||||||
|
----------
|
||||||
|
Use the `postgres` driver name in the `sql.Open()` call:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq" // To register the driver.
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Or as URL: postgresql://localhost/pqgo
|
||||||
|
db, err := sql.Open("postgres", "host=localhost dbname=pqgo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// db.Open() only creates a connection pool, and doesn't actually establish
|
||||||
|
// a connection. To ensure the connection works you need to do *something*
|
||||||
|
// with a connection.
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the `pq.Config` struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
cfg := pq.Config{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 5432,
|
||||||
|
User: "pqgo",
|
||||||
|
}
|
||||||
|
// Or: create a new Config from the defaults, environment, and DSN.
|
||||||
|
// cfg, err := pq.NewConfig("host=postgres dbname=pqgo")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
c, err := pq.NewConnectorConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create connection pool.
|
||||||
|
db := sql.OpenDB(c)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Make sure it works.
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The DSN is identical to PostgreSQL's libpq; most parameters are supported and
|
||||||
|
should behave identical. Both key=value and postgres:// URL-style connection
|
||||||
|
strings are supported. See the doc comments on the [Config struct] for the full
|
||||||
|
list and documentation.
|
||||||
|
|
||||||
|
The most notable difference is that you can use any [run-time parameter] such as
|
||||||
|
`search_path` or `work_mem` in the connection string. This is different from
|
||||||
|
libpq, which uses the `options` parameter for this (which also works in pq).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
sql.Open("postgres", "dbname=pqgo work_mem=100kB search_path=xyz")
|
||||||
|
|
||||||
|
The libpq way (which also works in pq) is to use `options='-c k=v'` like so:
|
||||||
|
|
||||||
|
sql.Open("postgres", "dbname=pqgo options='-c work_mem=100kB -c search_path=xyz'")
|
||||||
|
|
||||||
|
[Config struct]: https://pkg.go.dev/github.com/lib/pq#Config
|
||||||
|
[run-time parameter]: http://www.postgresql.org/docs/current/static/runtime-config.html
|
||||||
|
|
||||||
|
Errors
|
||||||
|
------
|
||||||
|
Errors from PostgreSQL are returned as [pq.Error]; [pq.As] can be used to
|
||||||
|
convert an error to `pq.Error`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
pqErr := pq.As(err, pqerror.UniqueViolation)
|
||||||
|
if pqErr != nil {
|
||||||
|
return fmt.Errorf("email %q already exsts", email)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
the Error() string contains the error message and code:
|
||||||
|
|
||||||
|
pq: duplicate key value violates unique constraint "users_lower_idx" (23505)
|
||||||
|
|
||||||
|
The ErrorWithDetail() string also contains the DETAIL and CONTEXT fields, if
|
||||||
|
present. For example for the above error this helpfully contains the duplicate
|
||||||
|
value:
|
||||||
|
|
||||||
|
ERROR: duplicate key value violates unique constraint "users_lower_idx" (23505)
|
||||||
|
DETAIL: Key (lower(email))=(a@example.com) already exists.
|
||||||
|
|
||||||
|
Or for an invalid syntax error like this:
|
||||||
|
|
||||||
|
pq: invalid input syntax for type json (22P02)
|
||||||
|
|
||||||
|
It contains the context where this error occurred:
|
||||||
|
|
||||||
|
ERROR: invalid input syntax for type json (22P02)
|
||||||
|
DETAIL: Token "asd" is invalid.
|
||||||
|
CONTEXT: line 5, column 8:
|
||||||
|
|
||||||
|
3 | 'def',
|
||||||
|
4 | 123,
|
||||||
|
5 | 'foo', 'asd'::jsonb
|
||||||
|
^
|
||||||
|
|
||||||
|
[pq.Error]: https://pkg.go.dev/github.com/lib/pq#Error
|
||||||
|
[pq.As]: https://pkg.go.dev/github.com/lib/pq#As
|
||||||
|
|
||||||
|
PostgreSQL features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
pq supports PASSWORD, MD5, and SCRAM-SHA256 authentication out of the box. If
|
||||||
|
you need GSS/Kerberos authentication you'll need to import the `auth/kerberos`
|
||||||
|
module: package:
|
||||||
|
|
||||||
|
import "github.com/lib/pq/auth/kerberos"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pq.RegisterGSSProvider(func() (pq.Gss, error) { return kerberos.NewGSS() })
|
||||||
|
}
|
||||||
|
|
||||||
|
This is in a separate module so that users who don't need Kerberos (i.e. most
|
||||||
|
users) don't have to add unnecessary dependencies.
|
||||||
|
|
||||||
|
Reading a [password file] (pgpass) is also supported.
|
||||||
|
|
||||||
|
[password file]: http://www.postgresql.org/docs/current/static/libpq-pgpass.html
|
||||||
|
|
||||||
|
### Bulk imports with `COPY [..] FROM STDIN`
|
||||||
|
You can perform bulk imports by preparing a `COPY [..] FROM STDIN` statement
|
||||||
|
inside a transaction. The returned `sql.Stmt` can then be repeatedly executed to
|
||||||
|
copy data. After all data has been processed you should call Exec() once with no
|
||||||
|
arguments to flush all buffered data.
|
||||||
|
|
||||||
|
[Further documentation][copy-doc] and [example][copy-ex].
|
||||||
|
|
||||||
|
[copy-doc]: https://pkg.go.dev/github.com/lib/pq#hdr-Bulk_imports
|
||||||
|
[copy-ex]: https://pkg.go.dev/github.com/lib/pq#example-package-CopyFromStdin
|
||||||
|
|
||||||
|
### NOTICE errors
|
||||||
|
PostgreSQL has "NOTICE" errors for informational messages. For example from the
|
||||||
|
psql CLI:
|
||||||
|
|
||||||
|
pqgo=# drop table if exists doesnotexist;
|
||||||
|
NOTICE: table "doesnotexist" does not exist, skipping
|
||||||
|
DROP TABLE
|
||||||
|
|
||||||
|
These errors are not returned because they're not really errors but, well,
|
||||||
|
notices.
|
||||||
|
|
||||||
|
You can register a callback for these notices with [ConnectorWithNoticeHandler]
|
||||||
|
|
||||||
|
[ConnectorWithNoticeHandler]: https://pkg.go.dev/github.com/lib/pq#ConnectorWithNoticeHandler
|
||||||
|
|
||||||
|
### Using `LISTEN`/`NOTIFY`
|
||||||
|
With [pq.Listener] notifications are send on a channel. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
l := pq.NewListener("dbname=pqgo", time.Second, time.Minute, nil)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
err := l.Listen("coconut")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
n := <-l.Notify:
|
||||||
|
if n == nil {
|
||||||
|
fmt.Println("nil notify: closing Listener")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("notification on %q with data %q\n", n.Channel, n.Extra)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And you'll get a notification for every `notify coconut`.
|
||||||
|
|
||||||
|
See the API docs for a more complete example.
|
||||||
|
|
||||||
|
[pq.Listener]: https://pkg.go.dev/github.com/lib/pq#Listener
|
||||||
|
|
||||||
|
|
||||||
|
Caveats
|
||||||
|
-------
|
||||||
|
### LastInsertId
|
||||||
|
sql.Result.LastInsertId() is not supported, because the PostgreSQL protocol does
|
||||||
|
not have this facility. Use `insert [..] returning [cols]` instead:
|
||||||
|
|
||||||
|
db.QueryRow(`insert into tbl [..] returning id_col`).Scan(..)
|
||||||
|
// Or multiple rows:
|
||||||
|
db.Query(`insert into tbl (row1), (row2) returning id_col`)
|
||||||
|
|
||||||
|
This will also work in SQLite and MariaDB with the same syntax. MS-SQL and
|
||||||
|
Oracle have a similar facility (with a different syntax).
|
||||||
|
|
||||||
|
### timestamps
|
||||||
|
For timestamps with a timezone (`timestamptz`/`timestamp with time zone`), pq
|
||||||
|
uses the timezone configured in the server, as libpq. You can change this with
|
||||||
|
`timestamp=[..]` in the connection string. It's generally recommended to use
|
||||||
|
UTC.
|
||||||
|
|
||||||
|
For timestamps without a timezone (`timestamp`/`timestamp without time zone`),
|
||||||
|
pq always uses `time.FixedZone("", 0)` as the timezone; the timestamp parameter
|
||||||
|
has no effect here. This is intentionally not equal to time.UTC, as it's not a
|
||||||
|
UTC time: it's a time without a timezone. Go's time package does not really
|
||||||
|
support this concept, so this is the best we can do This will print `+0000`
|
||||||
|
twice (e.g. `2026-03-15 17:45:47 +0000 +0000`; having a clearer name would have
|
||||||
|
been better, but is not compatible change). See [this comment][ts] for some
|
||||||
|
options on how to deal with this.
|
||||||
|
|
||||||
|
Also see the examples for [timestamptz] and [timestamp]
|
||||||
|
|
||||||
|
[ts]: https://github.com/lib/pq/issues/329#issuecomment-4025733506
|
||||||
|
[timestamptz]: https://pkg.go.dev/github.com/lib/pq#example-package-TimestampWithTimezone
|
||||||
|
[timestamp]: https://pkg.go.dev/github.com/lib/pq#example-package-TimestampWithoutTimezone
|
||||||
|
|
||||||
|
### bytea with copy
|
||||||
|
All `[]byte` parameters are encoded as `bytea` when using `copy [..] from
|
||||||
|
stdin`, which may result in errors for e.g. `jsonb` columns. The solution is to
|
||||||
|
use a string instead of []byte. See #1023
|
||||||
|
|
||||||
|
Development
|
||||||
|
-----------
|
||||||
|
### Running tests
|
||||||
|
Tests need to be run against a PostgreSQL database; you can use Docker compose
|
||||||
|
to start one:
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
This starts the latest PostgreSQL; use `docker compose up -d pg«v»` to start a
|
||||||
|
different version.
|
||||||
|
|
||||||
|
In addition, your `/etc/hosts` needs an entry:
|
||||||
|
|
||||||
|
127.0.0.1 postgres postgres-invalid
|
||||||
|
|
||||||
|
Or you can use any other PostgreSQL instance; see
|
||||||
|
`testdata/init/docker-entrypoint-initdb.d` for the required setup. You can use
|
||||||
|
the standard `PG*` environment variables to control the connection details; it
|
||||||
|
uses the following defaults:
|
||||||
|
|
||||||
|
PGHOST=localhost
|
||||||
|
PGDATABASE=pqgo
|
||||||
|
PGUSER=pqgo
|
||||||
|
PGSSLMODE=disable
|
||||||
|
PGCONNECT_TIMEOUT=20
|
||||||
|
|
||||||
|
`PQTEST_BINARY_PARAMETERS` can be used to add `binary_parameters=yes` to all
|
||||||
|
connection strings:
|
||||||
|
|
||||||
|
PQTEST_BINARY_PARAMETERS=1 go test
|
||||||
|
|
||||||
|
Tests can be run against pgbouncer with:
|
||||||
|
|
||||||
|
docker compose up -d pgbouncer pg18
|
||||||
|
PGPORT=6432 go test ./...
|
||||||
|
|
||||||
|
and pgpool with:
|
||||||
|
|
||||||
|
docker compose up -d pgpool pg18
|
||||||
|
PGPORT=7432 go test ./...
|
||||||
|
|
||||||
|
### Protocol debug output
|
||||||
|
You can use PQGO_DEBUG=1 to make the driver print the communication with
|
||||||
|
PostgreSQL to stderr; this works anywhere (test or applications) and can be
|
||||||
|
useful to debug protocol problems.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
% PQGO_DEBUG=1 go test -run TestSimpleQuery
|
||||||
|
CLIENT → Startup 69 "\x00\x03\x00\x00database\x00pqgo\x00user [..]"
|
||||||
|
SERVER ← (R) AuthRequest 4 "\x00\x00\x00\x00"
|
||||||
|
SERVER ← (S) ParamStatus 19 "in_hot_standby\x00off\x00"
|
||||||
|
[..]
|
||||||
|
SERVER ← (Z) ReadyForQuery 1 "I"
|
||||||
|
START conn.query
|
||||||
|
START conn.simpleQuery
|
||||||
|
CLIENT → (Q) Query 9 "select 1\x00"
|
||||||
|
SERVER ← (T) RowDescription 29 "\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x04\xff\xff\xff\xff\x00\x00"
|
||||||
|
SERVER ← (D) DataRow 7 "\x00\x01\x00\x00\x00\x011"
|
||||||
|
END conn.simpleQuery
|
||||||
|
END conn.query
|
||||||
|
SERVER ← (C) CommandComplete 9 "SELECT 1\x00"
|
||||||
|
SERVER ← (Z) ReadyForQuery 1 "I"
|
||||||
|
CLIENT → (X) Terminate 0 ""
|
||||||
|
PASS
|
||||||
|
ok github.com/lib/pq 0.010s
|
||||||
903
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
903
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
@ -0,0 +1,903 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||||
|
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||||
|
|
||||||
|
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||||
|
// slice of any dimension.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||||
|
//
|
||||||
|
// var x []sql.NullInt64
|
||||||
|
// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x))
|
||||||
|
//
|
||||||
|
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||||
|
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||||
|
func Array(a any) interface {
|
||||||
|
driver.Valuer
|
||||||
|
sql.Scanner
|
||||||
|
} {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case []bool:
|
||||||
|
return (*BoolArray)(&a)
|
||||||
|
case []float64:
|
||||||
|
return (*Float64Array)(&a)
|
||||||
|
case []float32:
|
||||||
|
return (*Float32Array)(&a)
|
||||||
|
case []int64:
|
||||||
|
return (*Int64Array)(&a)
|
||||||
|
case []int32:
|
||||||
|
return (*Int32Array)(&a)
|
||||||
|
case []string:
|
||||||
|
return (*StringArray)(&a)
|
||||||
|
case [][]byte:
|
||||||
|
return (*ByteaArray)(&a)
|
||||||
|
|
||||||
|
case *[]bool:
|
||||||
|
return (*BoolArray)(a)
|
||||||
|
case *[]float64:
|
||||||
|
return (*Float64Array)(a)
|
||||||
|
case *[]float32:
|
||||||
|
return (*Float32Array)(a)
|
||||||
|
case *[]int64:
|
||||||
|
return (*Int64Array)(a)
|
||||||
|
case *[]int32:
|
||||||
|
return (*Int32Array)(a)
|
||||||
|
case *[]string:
|
||||||
|
return (*StringArray)(a)
|
||||||
|
case *[][]byte:
|
||||||
|
return (*ByteaArray)(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericArray{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||||
|
// to override the array delimiter used by GenericArray.
|
||||||
|
type ArrayDelimiter interface {
|
||||||
|
// ArrayDelimiter returns the delimiter character(s) for this element's type.
|
||||||
|
ArrayDelimiter() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolArray represents a one-dimensional array of the PostgreSQL boolean type.
|
||||||
|
type BoolArray []bool
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *BoolArray) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BoolArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "BoolArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(BoolArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if len(v) != 1 {
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case 't':
|
||||||
|
b[i] = true
|
||||||
|
case 'f':
|
||||||
|
b[i] = false
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a BoolArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be exactly two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1+2*n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[2*i] = ','
|
||||||
|
if a[i] {
|
||||||
|
b[1+2*i] = 't'
|
||||||
|
} else {
|
||||||
|
b[1+2*i] = 'f'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[2*n] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type.
|
||||||
|
type ByteaArray [][]byte
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *ByteaArray) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ByteaArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "ByteaArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(ByteaArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = parseBytea(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse bytea array index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||||
|
// is only supported on PostgreSQL 9.0 or newer.
|
||||||
|
func (a ByteaArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||||
|
size := 1 + 6*n
|
||||||
|
for _, x := range a {
|
||||||
|
size += hex.EncodedLen(len(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, size)
|
||||||
|
|
||||||
|
for i, s := 0, b; i < n; i++ {
|
||||||
|
o := copy(s, `,"\\x`)
|
||||||
|
o += hex.Encode(s[o:], a[i])
|
||||||
|
s[o] = '"'
|
||||||
|
s = s[o+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[size-1] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float64Array []float64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float64Array) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = strconv.ParseFloat(string(v), 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float32Array []float32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float32Array) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
x, err := strconv.ParseFloat(string(v), 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
b[i] = float32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||||
|
// an array or slice of any dimension.
|
||||||
|
type GenericArray struct{ A any }
|
||||||
|
|
||||||
|
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||||
|
var assign func([]byte, reflect.Value) error
|
||||||
|
var del = ","
|
||||||
|
|
||||||
|
// TODO calculate the assign function for other types
|
||||||
|
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||||
|
{
|
||||||
|
if reflect.PointerTo(rt).Implements(typeSQLScanner) {
|
||||||
|
// dest is always addressable because it is an element of a slice.
|
||||||
|
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||||
|
ss := dest.Addr().Interface().(sql.Scanner)
|
||||||
|
if src == nil {
|
||||||
|
err = ss.Scan(nil)
|
||||||
|
} else {
|
||||||
|
err = ss.Scan(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goto FoundType
|
||||||
|
}
|
||||||
|
|
||||||
|
assign = func([]byte, reflect.Value) error {
|
||||||
|
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoundType:
|
||||||
|
|
||||||
|
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt, assign, del
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a GenericArray) Scan(src any) error {
|
||||||
|
dpv := reflect.ValueOf(a.A)
|
||||||
|
switch {
|
||||||
|
case dpv.Kind() != reflect.Pointer:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
case dpv.IsNil():
|
||||||
|
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
dv := dpv.Elem()
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src, dv)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src), dv)
|
||||||
|
case nil:
|
||||||
|
if dv.Kind() == reflect.Slice {
|
||||||
|
dv.Set(reflect.Zero(dv.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||||
|
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||||
|
dims, elems, err := parseArray(src, []byte(del))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow multidimensional
|
||||||
|
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||||
|
if len(dims) == 0 {
|
||||||
|
dims = append(dims, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
if rt.Len() != dims[i] {
|
||||||
|
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO handle multidimensional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||||
|
for i, e := range elems {
|
||||||
|
err := assign(e, values.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle multidimensional
|
||||||
|
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
dv.Set(values.Slice(0, dims[0]))
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < dims[0]; i++ {
|
||||||
|
dv.Index(i).Set(values.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a GenericArray) Value() (driver.Value, error) {
|
||||||
|
if a.A == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(a.A)
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: unable to convert %T to array", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 0, 1+2*n)
|
||||||
|
|
||||||
|
b, _, err := appendArray(b, rv, n)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int64Array []int64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int64Array) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = strconv.ParseInt(string(v), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, a[0], 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, a[i], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int32Array []int32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int32Array) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
x, err := strconv.ParseInt(string(v), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %w", i, err)
|
||||||
|
}
|
||||||
|
b[i] = int32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, int64(a[0]), 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, int64(a[i]), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
||||||
|
type StringArray []string
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *StringArray) Scan(src any) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(StringArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i] = string(v); v == nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a StringArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+3*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArray appends rv to the buffer, returning the extended buffer and the
|
||||||
|
// delimiter used between elements.
|
||||||
|
//
|
||||||
|
// Returns an error when n <= 0 or rv is not a reflect.Array or reflect.Slice.
|
||||||
|
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||||
|
var del string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b = append(b, '{')
|
||||||
|
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, del...)
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(b, '}'), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||||
|
// and the delimiter to use before the next element.
|
||||||
|
//
|
||||||
|
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||||
|
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||||
|
// is double-quoted.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||||
|
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||||
|
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
return appendArray(b, rv, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = ","
|
||||||
|
var err error
|
||||||
|
var iv = rv.Interface()
|
||||||
|
|
||||||
|
if ad, ok := iv.(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := iv.(type) {
|
||||||
|
case nil:
|
||||||
|
return append(b, "NULL"...), del, nil
|
||||||
|
case []byte:
|
||||||
|
return appendArrayQuotedBytes(b, v), del, nil
|
||||||
|
case string:
|
||||||
|
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = appendValue(b, iv)
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||||
|
b = append(b, '"')
|
||||||
|
for {
|
||||||
|
i := bytes.IndexAny(v, `"\`)
|
||||||
|
if i < 0 {
|
||||||
|
b = append(b, v...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, v[:i]...)
|
||||||
|
}
|
||||||
|
b = append(b, '\\', v[i])
|
||||||
|
v = v[i+1:]
|
||||||
|
}
|
||||||
|
return append(b, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||||
|
enc, err := encode(v, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(b, enc...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArray extracts the dimensions and elements of an array represented in
|
||||||
|
// text format. Only representations emitted by the backend are supported.
|
||||||
|
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||||
|
// is case-sensitive.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||||
|
var depth, i int
|
||||||
|
|
||||||
|
if len(src) < 1 || src[0] != '{' {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Open:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
depth++
|
||||||
|
i++
|
||||||
|
case '}':
|
||||||
|
elems = make([][]byte, 0)
|
||||||
|
goto Close
|
||||||
|
default:
|
||||||
|
break Open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dims = make([]int, i)
|
||||||
|
|
||||||
|
Element:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
if depth == len(dims) {
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
depth++
|
||||||
|
dims[depth-1] = 0
|
||||||
|
i++
|
||||||
|
case '"':
|
||||||
|
var elem = []byte{}
|
||||||
|
var escape bool
|
||||||
|
for i++; i < len(src); i++ {
|
||||||
|
if escape {
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
switch src[i] {
|
||||||
|
default:
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
case '\\':
|
||||||
|
escape = true
|
||||||
|
case '"':
|
||||||
|
elems = append(elems, elem)
|
||||||
|
i++
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for start := i; i < len(src); i++ {
|
||||||
|
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||||
|
elem := src[start:i]
|
||||||
|
if len(elem) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
if bytes.Equal(elem, []byte("NULL")) {
|
||||||
|
elem = nil
|
||||||
|
}
|
||||||
|
elems = append(elems, elem)
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(src) {
|
||||||
|
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
i += len(del)
|
||||||
|
goto Element
|
||||||
|
} else if src[i] == '}' && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close:
|
||||||
|
for i < len(src) {
|
||||||
|
if src[i] == '}' && depth > 0 {
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth > 0 {
|
||||||
|
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, d := range dims {
|
||||||
|
if (len(elems) % d) != 0 {
|
||||||
|
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||||
|
dims, elems, err := parseArray(src, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||||
|
}
|
||||||
|
return elems, err
|
||||||
|
}
|
||||||
26
vendor/github.com/lib/pq/as.go
generated
vendored
Normal file
26
vendor/github.com/lib/pq/as.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//go:build !go1.26
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// As asserts that the given error is [pq.Error] and returns it, returning nil
|
||||||
|
// if it's not a pq.Error.
|
||||||
|
//
|
||||||
|
// It will return nil if the pq.Error is not one of the given error codes. If no
|
||||||
|
// codes are given it will always return the Error.
|
||||||
|
//
|
||||||
|
// This is safe to call with a nil error.
|
||||||
|
func As(err error, codes ...ErrorCode) *Error {
|
||||||
|
if err == nil { // Not strictly needed, but prevents alloc for nil errors.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pqErr := new(Error)
|
||||||
|
if errors.As(err, &pqErr) && (len(codes) == 0 || slices.Contains(codes, pqErr.Code)) {
|
||||||
|
return pqErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
23
vendor/github.com/lib/pq/as_go126.go
generated
vendored
Normal file
23
vendor/github.com/lib/pq/as_go126.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//go:build go1.26
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/lib/pq/pqerror"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// As asserts that the given error is [pq.Error] and returns it, returning nil
|
||||||
|
// if it's not a pq.Error.
|
||||||
|
//
|
||||||
|
// It will return nil if the pq.Error is not one of the given error codes. If no
|
||||||
|
// codes are given it will always return the Error.
|
||||||
|
//
|
||||||
|
// This is safe to call with a nil error.
|
||||||
|
func As(err error, codes ...pqerror.Code) *Error {
|
||||||
|
if pqErr, ok := errors.AsType[*Error](err); ok && (len(codes) == 0 || slices.Contains(codes, pqErr.Code)) {
|
||||||
|
return pqErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
100
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
100
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readBuf []byte
|
||||||
|
|
||||||
|
func (b *readBuf) int32() (n int) {
|
||||||
|
n = int(int32(binary.BigEndian.Uint32(*b)))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) oid() (n oid.Oid) {
|
||||||
|
n = oid.Oid(binary.BigEndian.Uint32(*b))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B: this is actually an unsigned 16-bit integer, unlike int32
|
||||||
|
func (b *readBuf) int16() (n int) {
|
||||||
|
n = int(binary.BigEndian.Uint16(*b))
|
||||||
|
*b = (*b)[2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) string() string {
|
||||||
|
i := bytes.IndexByte(*b, 0)
|
||||||
|
if i < 0 {
|
||||||
|
panic(errors.New("pq: invalid message format; expected string terminator"))
|
||||||
|
}
|
||||||
|
s := (*b)[:i]
|
||||||
|
*b = (*b)[i+1:]
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) next(n int) (v []byte) {
|
||||||
|
v = (*b)[:n]
|
||||||
|
*b = (*b)[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) byte() byte {
|
||||||
|
return b.next(1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeBuf struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int32(n int) {
|
||||||
|
x := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(x, uint32(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int16(n int) {
|
||||||
|
x := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(x, uint16(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) string(s string) {
|
||||||
|
b.buf = append(append(b.buf, s...), '\000')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) byte(c proto.RequestCode) {
|
||||||
|
b.buf = append(b.buf, byte(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) bytes(v []byte) {
|
||||||
|
b.buf = append(b.buf, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) wrap() []byte {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
if len(p) > proto.MaxUint32 {
|
||||||
|
panic(fmt.Errorf("pq: message too large (%d > math.MaxUint32)", len(p)))
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) next(c proto.RequestCode) {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
if len(p) > proto.MaxUint32 {
|
||||||
|
panic(fmt.Errorf("pq: message too large (%d > math.MaxUint32)", len(p)))
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
b.pos = len(b.buf) + 1
|
||||||
|
b.buf = append(b.buf, byte(c), 0, 0, 0, 0)
|
||||||
|
}
|
||||||
81
vendor/github.com/lib/pq/compose.yaml
generated
vendored
Normal file
81
vendor/github.com/lib/pq/compose.yaml
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
name: 'pqgo'
|
||||||
|
|
||||||
|
services:
|
||||||
|
pgbouncer:
|
||||||
|
profiles: ['pgbouncer']
|
||||||
|
image: 'cleanstart/pgbouncer:latest'
|
||||||
|
ports: ['127.0.0.1:6432:6432']
|
||||||
|
command: ['/init/pgbouncer.ini']
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
environment:
|
||||||
|
'PGBOUNCER_DATABASE': 'pqgo'
|
||||||
|
|
||||||
|
pgpool:
|
||||||
|
profiles: ['pgpool']
|
||||||
|
image: 'pgpool/pgpool:4.4.3'
|
||||||
|
ports: ['127.0.0.1:7432:7432']
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
entrypoint: '/init/entry-pgpool.sh'
|
||||||
|
environment:
|
||||||
|
'PGPOOL_PARAMS_PORT': '7432'
|
||||||
|
'PGPOOL_PARAMS_BACKEND_HOSTNAME0': 'pg18'
|
||||||
|
|
||||||
|
pg18:
|
||||||
|
image: 'postgres:18'
|
||||||
|
ports: ['127.0.0.1:5432:5432']
|
||||||
|
entrypoint: '/init/entry.sh'
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
shm_size: '128mb'
|
||||||
|
healthcheck: {test: ['CMD-SHELL', 'pg_isready', '-U', 'pqgo', '-d', 'pqgo'], start_period: '30s', start_interval: '1s'}
|
||||||
|
environment:
|
||||||
|
'POSTGRES_DATABASE': 'pqgo'
|
||||||
|
'POSTGRES_USER': 'pqgo'
|
||||||
|
'POSTGRES_PASSWORD': 'unused'
|
||||||
|
pg17:
|
||||||
|
profiles: ['pg17']
|
||||||
|
image: 'postgres:17'
|
||||||
|
ports: ['127.0.0.1:5432:5432']
|
||||||
|
entrypoint: '/init/entry.sh'
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
shm_size: '128mb'
|
||||||
|
healthcheck: {test: ['CMD-SHELL', 'pg_isready', '-U', 'pqgo', '-d', 'pqgo'], start_period: '30s', start_interval: '1s'}
|
||||||
|
environment:
|
||||||
|
'POSTGRES_DATABASE': 'pqgo'
|
||||||
|
'POSTGRES_USER': 'pqgo'
|
||||||
|
'POSTGRES_PASSWORD': 'unused'
|
||||||
|
pg16:
|
||||||
|
profiles: ['pg16']
|
||||||
|
image: 'postgres:16'
|
||||||
|
ports: ['127.0.0.1:5432:5432']
|
||||||
|
entrypoint: '/init/entry.sh'
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
shm_size: '128mb'
|
||||||
|
healthcheck: {test: ['CMD-SHELL', 'pg_isready', '-U', 'pqgo', '-d', 'pqgo'], start_period: '30s', start_interval: '1s'}
|
||||||
|
environment:
|
||||||
|
'POSTGRES_DATABASE': 'pqgo'
|
||||||
|
'POSTGRES_USER': 'pqgo'
|
||||||
|
'POSTGRES_PASSWORD': 'unused'
|
||||||
|
pg15:
|
||||||
|
profiles: ['pg15']
|
||||||
|
image: 'postgres:15'
|
||||||
|
ports: ['127.0.0.1:5432:5432']
|
||||||
|
entrypoint: '/init/entry.sh'
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
shm_size: '128mb'
|
||||||
|
healthcheck: {test: ['CMD-SHELL', 'pg_isready', '-U', 'pqgo', '-d', 'pqgo'], start_period: '30s', start_interval: '1s'}
|
||||||
|
environment:
|
||||||
|
'POSTGRES_DATABASE': 'pqgo'
|
||||||
|
'POSTGRES_USER': 'pqgo'
|
||||||
|
'POSTGRES_PASSWORD': 'unused'
|
||||||
|
pg14:
|
||||||
|
profiles: ['pg14']
|
||||||
|
image: 'postgres:14'
|
||||||
|
ports: ['127.0.0.1:5432:5432']
|
||||||
|
entrypoint: '/init/entry.sh'
|
||||||
|
volumes: ['./testdata/init:/init']
|
||||||
|
shm_size: '128mb'
|
||||||
|
healthcheck: {test: ['CMD-SHELL', 'pg_isready', '-U', 'pqgo', '-d', 'pqgo'], start_period: '30s', start_interval: '1s'}
|
||||||
|
environment:
|
||||||
|
'POSTGRES_DATABASE': 'pqgo'
|
||||||
|
'POSTGRES_USER': 'pqgo'
|
||||||
|
'POSTGRES_PASSWORD': 'unused'
|
||||||
1817
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
1817
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
226
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
226
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const watchCancelDialContextTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
// Implement the "QueryerContext" interface
|
||||||
|
func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
finish := cn.watchCancel(ctx)
|
||||||
|
r, err := cn.query(query, args)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ExecerContext" interface
|
||||||
|
func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn.Exec(query, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnPrepareContext" interface
|
||||||
|
func (cn *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
return cn.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnBeginTx" interface
|
||||||
|
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
var mode string
|
||||||
|
switch sql.IsolationLevel(opts.Isolation) {
|
||||||
|
case sql.LevelDefault:
|
||||||
|
// Don't touch mode: use the server's default
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ UNCOMMITTED"
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ COMMITTED"
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
mode = " ISOLATION LEVEL REPEATABLE READ"
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
mode = " ISOLATION LEVEL SERIALIZABLE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation)
|
||||||
|
}
|
||||||
|
if opts.ReadOnly {
|
||||||
|
mode += " READ ONLY"
|
||||||
|
} else {
|
||||||
|
mode += " READ WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := cn.begin(mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn.txnFinish = cn.watchCancel(ctx)
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) Ping(ctx context.Context) error {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
rows, err := cn.simpleQuery(";")
|
||||||
|
if err != nil {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
_ = rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
select {
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// We raced with the finish func, let the next query handle this with the
|
||||||
|
// context.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the connection state to bad so it does not get reused.
|
||||||
|
cn.err.set(ctx.Err())
|
||||||
|
|
||||||
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = cn.cancel(ctxCancel)
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
cn.err.set(ctx.Err())
|
||||||
|
_ = cn.Close()
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) cancel(ctx context.Context) error {
|
||||||
|
// Use a copy since a new connection is created here. This is necessary
|
||||||
|
// because cancel is called from a goroutine in watchCancel.
|
||||||
|
cfg := cn.cfg.Clone()
|
||||||
|
|
||||||
|
c, err := dial(ctx, cn.dialer, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = c.Close() }()
|
||||||
|
|
||||||
|
cn2 := conn{c: c}
|
||||||
|
err = cn2.ssl(cfg, cfg.SSLMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := cn2.writeBuf(0)
|
||||||
|
w.int32(proto.CancelRequestCode)
|
||||||
|
w.int32(cn.pid)
|
||||||
|
w.bytes(cn.secretKey)
|
||||||
|
if err := cn2.sendStartupPacket(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read until EOF to ensure that the server received the cancel.
|
||||||
|
_, err = io.Copy(io.Discard, c)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "StmtQueryContext" interface
|
||||||
|
func (st *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
finish := st.watchCancel(ctx)
|
||||||
|
r, err := st.query(args)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "StmtExecContext" interface
|
||||||
|
func (st *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
if finish := st.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
if err := st.cn.err.get(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := st.exec(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
res, _, err := st.cn.readExecuteResponse("simple query")
|
||||||
|
return res, st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchCancel is implemented on stmt in order to not mark the parent conn as bad
|
||||||
|
func (st *stmt) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// At this point the function level context is canceled, so it
|
||||||
|
// must not be used for the additional network request to cancel
|
||||||
|
// the query. Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = st.cancel(ctxCancel)
|
||||||
|
finished <- struct{}{}
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) cancel(ctx context.Context) error {
|
||||||
|
return st.cn.cancel(ctx)
|
||||||
|
}
|
||||||
1157
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
1157
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
337
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
337
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCopyInClosed = errors.New("pq: copyin statement has already been closed")
|
||||||
|
errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
|
||||||
|
errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
|
||||||
|
errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
|
||||||
|
)
|
||||||
|
|
||||||
|
type copyin struct {
|
||||||
|
cn *conn
|
||||||
|
buffer []byte
|
||||||
|
rowData chan []byte
|
||||||
|
done chan bool
|
||||||
|
closed bool
|
||||||
|
mu struct {
|
||||||
|
sync.Mutex
|
||||||
|
err error
|
||||||
|
driver.Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ciBufferSize = 64 * 1024
|
||||||
|
// flush buffer before the buffer is filled up and needs reallocation
|
||||||
|
ciBufferFlushSize = 63 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, resErr error) {
|
||||||
|
if !cn.isInTransaction() {
|
||||||
|
return nil, errCopyNotSupportedOutsideTxn
|
||||||
|
}
|
||||||
|
|
||||||
|
ci := ©in{
|
||||||
|
cn: cn,
|
||||||
|
buffer: make([]byte, 0, ciBufferSize),
|
||||||
|
rowData: make(chan []byte),
|
||||||
|
done: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
// add CopyData identifier + 4 bytes for message length
|
||||||
|
ci.buffer = append(ci.buffer, byte(proto.CopyDataRequest), 0, 0, 0, 0)
|
||||||
|
|
||||||
|
b := cn.writeBuf(proto.Query)
|
||||||
|
b.string(q)
|
||||||
|
err := cn.send(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitCopyInResponse:
|
||||||
|
for {
|
||||||
|
t, r, err := cn.recv1()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case proto.CopyInResponse:
|
||||||
|
if r.byte() != 0 {
|
||||||
|
resErr = errBinaryCopyNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
}
|
||||||
|
go ci.resploop()
|
||||||
|
return ci, nil
|
||||||
|
case proto.CopyOutResponse:
|
||||||
|
resErr = errCopyToNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
case proto.ErrorResponse:
|
||||||
|
resErr = parseError(r, q)
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
if resErr == nil {
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
return nil, fmt.Errorf("pq: unexpected ReadyForQuery in response to COPY")
|
||||||
|
}
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, resErr
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
return nil, fmt.Errorf("pq: unknown response for copy query: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// something went wrong, abort COPY before we return
|
||||||
|
b = cn.writeBuf(proto.CopyFail)
|
||||||
|
b.string(resErr.Error())
|
||||||
|
err = cn.send(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, r, err := cn.recv1()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case proto.CopyDoneResponse, proto.CommandComplete, proto.ErrorResponse:
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
// correctly aborted, we're done
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, resErr
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
return nil, fmt.Errorf("pq: unknown response for CopyFail: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) flush(buf []byte) error {
|
||||||
|
if len(buf)-1 > proto.MaxUint32 {
|
||||||
|
return errors.New("pq: too many columns")
|
||||||
|
}
|
||||||
|
// set message length (without message identifier)
|
||||||
|
binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
|
||||||
|
|
||||||
|
_, err := ci.cn.c.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) resploop() {
|
||||||
|
for {
|
||||||
|
var r readBuf
|
||||||
|
t, err := ci.cn.recvMessage(&r)
|
||||||
|
if err != nil {
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
ci.setError(err)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case proto.CommandComplete:
|
||||||
|
// complete
|
||||||
|
res, _, err := ci.cn.parseComplete(r.string())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ci.setResult(res)
|
||||||
|
case proto.NoticeResponse:
|
||||||
|
if n := ci.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(&r, ""))
|
||||||
|
}
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
ci.cn.processReadyForQuery(&r)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
case proto.ErrorResponse:
|
||||||
|
err := parseError(&r, "")
|
||||||
|
ci.setError(err)
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setBad(err error) {
|
||||||
|
ci.cn.err.set(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) getBad() error {
|
||||||
|
return ci.cn.err.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) err() error {
|
||||||
|
ci.mu.Lock()
|
||||||
|
err := ci.mu.err
|
||||||
|
ci.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setError() sets ci.err if one has not been set already. Caller must not be
|
||||||
|
// holding ci.Mutex.
|
||||||
|
func (ci *copyin) setError(err error) {
|
||||||
|
ci.mu.Lock()
|
||||||
|
if ci.mu.err == nil {
|
||||||
|
ci.mu.err = err
|
||||||
|
}
|
||||||
|
ci.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setResult(result driver.Result) {
|
||||||
|
ci.mu.Lock()
|
||||||
|
ci.mu.Result = result
|
||||||
|
ci.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) getResult() driver.Result {
|
||||||
|
ci.mu.Lock()
|
||||||
|
result := ci.mu.Result
|
||||||
|
ci.mu.Unlock()
|
||||||
|
if result == nil {
|
||||||
|
return driver.RowsAffected(0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) NumInput() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec inserts values into the COPY stream. The insert is asynchronous
|
||||||
|
// and Exec can return errors from previous Exec calls to the same
|
||||||
|
// COPY stmt.
|
||||||
|
//
|
||||||
|
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||||
|
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||||
|
// to the user.
|
||||||
|
func (ci *copyin) Exec(v []driver.Value) (driver.Result, error) {
|
||||||
|
if ci.closed {
|
||||||
|
return nil, errCopyInClosed
|
||||||
|
}
|
||||||
|
if err := ci.getBad(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ci.err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) == 0 {
|
||||||
|
if err := ci.Close(); err != nil {
|
||||||
|
return driver.RowsAffected(0), err
|
||||||
|
}
|
||||||
|
return ci.getResult(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
numValues = len(v)
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for i, value := range v {
|
||||||
|
ci.buffer, err = appendEncodedText(ci.buffer, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ci.cn.handleError(err)
|
||||||
|
}
|
||||||
|
if i < numValues-1 {
|
||||||
|
ci.buffer = append(ci.buffer, '\t')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.buffer = append(ci.buffer, '\n')
|
||||||
|
|
||||||
|
if len(ci.buffer) > ciBufferFlushSize {
|
||||||
|
err := ci.flush(ci.buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ci.cn.handleError(err)
|
||||||
|
}
|
||||||
|
// reset buffer, keep bytes for message identifier and length
|
||||||
|
ci.buffer = ci.buffer[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.RowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyData inserts a raw string into the COPY stream. The insert is
|
||||||
|
// asynchronous and CopyData can return errors from previous CopyData calls to
|
||||||
|
// the same COPY stmt.
|
||||||
|
//
|
||||||
|
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||||
|
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||||
|
// to the user.
|
||||||
|
func (ci *copyin) CopyData(ctx context.Context, line string) (driver.Result, error) {
|
||||||
|
if ci.closed {
|
||||||
|
return nil, errCopyInClosed
|
||||||
|
}
|
||||||
|
if finish := ci.cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
if err := ci.getBad(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ci.err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.buffer = append(ci.buffer, []byte(line)...)
|
||||||
|
ci.buffer = append(ci.buffer, '\n')
|
||||||
|
|
||||||
|
if len(ci.buffer) > ciBufferFlushSize {
|
||||||
|
err := ci.flush(ci.buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ci.cn.handleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset buffer, keep bytes for message identifier and length
|
||||||
|
ci.buffer = ci.buffer[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.RowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Close() error {
|
||||||
|
if ci.closed { // Don't do anything, we're already closed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ci.closed = true
|
||||||
|
|
||||||
|
if err := ci.getBad(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ci.buffer) > 0 {
|
||||||
|
err := ci.flush(ci.buffer)
|
||||||
|
if err != nil {
|
||||||
|
return ci.cn.handleError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Avoid touching the scratch buffer as resploop could be using it.
|
||||||
|
err := ci.cn.sendSimpleMessage(proto.CopyDoneRequest)
|
||||||
|
if err != nil {
|
||||||
|
return ci.cn.handleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ci.done
|
||||||
|
ci.cn.inProgress.Store(false)
|
||||||
|
|
||||||
|
if err := ci.err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
133
vendor/github.com/lib/pq/deprecated.go
generated
vendored
Normal file
133
vendor/github.com/lib/pq/deprecated.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/lib/pq/pqerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [pq.Error.Severity] values.
|
||||||
|
//
|
||||||
|
// Deprecated: use pqerror.Severity[..] values.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
|
const (
|
||||||
|
Efatal = pqerror.SeverityFatal
|
||||||
|
Epanic = pqerror.SeverityPanic
|
||||||
|
Ewarning = pqerror.SeverityWarning
|
||||||
|
Enotice = pqerror.SeverityNotice
|
||||||
|
Edebug = pqerror.SeverityDebug
|
||||||
|
Einfo = pqerror.SeverityInfo
|
||||||
|
Elog = pqerror.SeverityLog
|
||||||
|
)
|
||||||
|
|
||||||
|
// PGError is an interface used by previous versions of pq.
|
||||||
|
//
|
||||||
|
// Deprecated: use the Error type. This is never used.
|
||||||
|
type PGError interface {
|
||||||
|
Error() string
|
||||||
|
Fatal() bool
|
||||||
|
Get(k byte) (v string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the legacy PGError interface.
|
||||||
|
//
|
||||||
|
// Deprecated: new code should use the fields of the Error struct directly.
|
||||||
|
func (e *Error) Get(k byte) (v string) {
|
||||||
|
switch k {
|
||||||
|
case 'S':
|
||||||
|
return e.Severity
|
||||||
|
case 'C':
|
||||||
|
return string(e.Code)
|
||||||
|
case 'M':
|
||||||
|
return e.Message
|
||||||
|
case 'D':
|
||||||
|
return e.Detail
|
||||||
|
case 'H':
|
||||||
|
return e.Hint
|
||||||
|
case 'P':
|
||||||
|
return e.Position
|
||||||
|
case 'p':
|
||||||
|
return e.InternalPosition
|
||||||
|
case 'q':
|
||||||
|
return e.InternalQuery
|
||||||
|
case 'W':
|
||||||
|
return e.Where
|
||||||
|
case 's':
|
||||||
|
return e.Schema
|
||||||
|
case 't':
|
||||||
|
return e.Table
|
||||||
|
case 'c':
|
||||||
|
return e.Column
|
||||||
|
case 'd':
|
||||||
|
return e.DataTypeName
|
||||||
|
case 'n':
|
||||||
|
return e.Constraint
|
||||||
|
case 'F':
|
||||||
|
return e.File
|
||||||
|
case 'L':
|
||||||
|
return e.Line
|
||||||
|
case 'R':
|
||||||
|
return e.Routine
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseURL converts a url to a connection string for driver.Open.
|
||||||
|
//
|
||||||
|
// Deprecated: directly passing an URL to sql.Open("postgres", "postgres://...")
|
||||||
|
// now works, and calling this manually is no longer required.
|
||||||
|
func ParseURL(url string) (string, error) { return convertURL(url) }
|
||||||
|
|
||||||
|
// NullTime represents a [time.Time] that may be null.
|
||||||
|
//
|
||||||
|
// Deprecated: this is an alias for [sql.NullTime].
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
|
type NullTime = sql.NullTime
|
||||||
|
|
||||||
|
// CopyIn creates a COPY FROM statement which can be prepared with Tx.Prepare().
|
||||||
|
// The target table should be visible in search_path.
|
||||||
|
//
|
||||||
|
// It copies all columns if the list of columns is empty.
|
||||||
|
//
|
||||||
|
// Deprecated: there is no need to use this query builder, you can use:
|
||||||
|
//
|
||||||
|
// tx.Prepare("copy tbl (col1, col2) from stdin")
|
||||||
|
func CopyIn(table string, columns ...string) string {
|
||||||
|
b := bytes.NewBufferString("COPY ")
|
||||||
|
BufferQuoteIdentifier(table, b)
|
||||||
|
makeStmt(b, columns...)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInSchema creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare().
|
||||||
|
//
|
||||||
|
// Deprecated: there is no need to use this query builder, you can use:
|
||||||
|
//
|
||||||
|
// tx.Prepare("copy schema.tbl (col1, col2) from stdin")
|
||||||
|
func CopyInSchema(schema, table string, columns ...string) string {
|
||||||
|
b := bytes.NewBufferString("COPY ")
|
||||||
|
BufferQuoteIdentifier(schema, b)
|
||||||
|
b.WriteRune('.')
|
||||||
|
BufferQuoteIdentifier(table, b)
|
||||||
|
makeStmt(b, columns...)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStmt(b *bytes.Buffer, columns ...string) {
|
||||||
|
if len(columns) == 0 {
|
||||||
|
b.WriteString(" FROM STDIN")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.WriteString(" (")
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
BufferQuoteIdentifier(col, b)
|
||||||
|
}
|
||||||
|
b.WriteString(") FROM STDIN")
|
||||||
|
}
|
||||||
137
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
137
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Package pq is a Go PostgreSQL driver for database/sql.
|
||||||
|
|
||||||
|
Most clients will use the database/sql package instead of using this package
|
||||||
|
directly. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsn := "user=pqgo dbname=pqgo sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := 21
|
||||||
|
rows, err := db.Query("select name from users where age = $1", age)
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also connect with an URL:
|
||||||
|
|
||||||
|
dsn := "postgres://pqgo:password@localhost/pqgo?sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
|
||||||
|
# Connection String Parameters
|
||||||
|
|
||||||
|
See [NewConfig].
|
||||||
|
|
||||||
|
# Queries
|
||||||
|
|
||||||
|
database/sql does not dictate any specific format for parameter placeholders,
|
||||||
|
and pq uses the PostgreSQL-native ordinal markers ($1, $2, etc.). The same
|
||||||
|
placeholder can be used more than once:
|
||||||
|
|
||||||
|
rows, err := db.Query(
|
||||||
|
`select * from users where name = $1 or age between $2 and $2 + 3`,
|
||||||
|
"Duck", 64)
|
||||||
|
|
||||||
|
pq does not support [sql.Result.LastInsertId]. Use the RETURNING clause with a
|
||||||
|
Query or QueryRow call instead to return the identifier:
|
||||||
|
|
||||||
|
row := db.QueryRow(`insert into users(name, age) values('Scrooge McDuck', 93) returning id`)
|
||||||
|
|
||||||
|
var userid int
|
||||||
|
err := row.Scan(&userid)
|
||||||
|
|
||||||
|
# Data Types
|
||||||
|
|
||||||
|
Parameters pass through [driver.DefaultParameterConverter] before they are handled
|
||||||
|
by this package. When the binary_parameters connection option is enabled, []byte
|
||||||
|
values are sent directly to the backend as data in binary format.
|
||||||
|
|
||||||
|
This package returns the following types for values from the PostgreSQL backend:
|
||||||
|
|
||||||
|
- integer types smallint, integer, and bigint are returned as int64
|
||||||
|
- floating-point types real and double precision are returned as float64
|
||||||
|
- character types char, varchar, and text are returned as string
|
||||||
|
- temporal types date, time, timetz, timestamp, and timestamptz are
|
||||||
|
returned as time.Time
|
||||||
|
- the boolean type is returned as bool
|
||||||
|
- the bytea type is returned as []byte
|
||||||
|
|
||||||
|
All other types are returned directly from the backend as []byte values in text format.
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
pq may return errors of type [*pq.Error] which contain error details:
|
||||||
|
|
||||||
|
pqErr := new(pq.Error)
|
||||||
|
if errors.As(err, &pqErr) {
|
||||||
|
fmt.Println("pq error:", pqErr.Code.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
# Bulk imports
|
||||||
|
|
||||||
|
You can perform bulk imports by preparing a "COPY [..] FROM STDIN" statement in
|
||||||
|
a transaction ([sql.Tx]). The returned [sql.Stmt] handle can then be repeatedly
|
||||||
|
"executed" to copy data into the target table. After all data has been processed
|
||||||
|
you should call Exec() once with no arguments to flush all buffered data. Any
|
||||||
|
call to Exec() might return an error which should be handled appropriately, but
|
||||||
|
because of the internal buffering an error returned by Exec() might not be
|
||||||
|
related to the data passed in the call that failed.
|
||||||
|
|
||||||
|
It is not possible to COPY outside of an explicit transaction in pq.
|
||||||
|
|
||||||
|
Use nil for NULL, or explicitly add WITH NULL 'SOME STRING' (the default of \N
|
||||||
|
doesn't work).
|
||||||
|
|
||||||
|
# Notifications
|
||||||
|
|
||||||
|
PostgreSQL supports a simple publish/subscribe model using PostgreSQL's [NOTIFY] mechanism.
|
||||||
|
|
||||||
|
To start listening for notifications, you first have to open a new connection to
|
||||||
|
the database by calling [NewListener]. This connection can not be used for
|
||||||
|
anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
|
||||||
|
channel"; once a notification channel is open, a notification generated on that
|
||||||
|
channel will effect a send on the Listener.Notify channel. A notification
|
||||||
|
channel will remain open until Unlisten is called, though connection loss might
|
||||||
|
result in some notifications being lost. To solve this problem, Listener sends a
|
||||||
|
nil pointer over the Notify channel any time the connection is re-established
|
||||||
|
following a connection loss. The application can get information about the state
|
||||||
|
of the underlying connection by setting an event callback in the call to
|
||||||
|
NewListener.
|
||||||
|
|
||||||
|
A single [Listener] can safely be used from concurrent goroutines, which means
|
||||||
|
that there is often no need to create more than one Listener in your
|
||||||
|
application. However, a Listener is always connected to a single database, so
|
||||||
|
you will need to create a new Listener instance for every database you want to
|
||||||
|
receive notifications in.
|
||||||
|
|
||||||
|
The channel name in both Listen and Unlisten is case sensitive, and can contain
|
||||||
|
any characters legal in an [identifier]. Note that the channel name will be
|
||||||
|
truncated to 63 bytes by the PostgreSQL server.
|
||||||
|
|
||||||
|
# Kerberos Support
|
||||||
|
|
||||||
|
If you need support for Kerberos authentication, add the following to your main
|
||||||
|
package:
|
||||||
|
|
||||||
|
import "github.com/lib/pq/auth/kerberos"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pq.RegisterGSSProvider(func() (pq.Gss, error) { return kerberos.NewGSS() })
|
||||||
|
}
|
||||||
|
|
||||||
|
This package is in a separate module so that users who don't need Kerberos don't
|
||||||
|
have to add unnecessary dependencies.
|
||||||
|
|
||||||
|
[identifier]: http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||||
|
[NOTIFY]: http://www.postgresql.org/docs/current/static/sql-notify.html
|
||||||
|
*/
|
||||||
|
package pq
|
||||||
400
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
400
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/pqtime"
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func binaryEncode(x any) ([]byte, error) {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case []byte:
|
||||||
|
return v, nil
|
||||||
|
default:
|
||||||
|
return encode(x, oid.T_unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(x any, pgtypOid oid.Oid) ([]byte, error) {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(nil, v, 10), nil
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(nil, v, 'f', -1, 64), nil
|
||||||
|
case []byte:
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(v), nil
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
case string:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea([]byte(v)), nil
|
||||||
|
}
|
||||||
|
return []byte(v), nil
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(nil, v), nil
|
||||||
|
case time.Time:
|
||||||
|
return formatTS(v), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(ps *parameterStatus, s []byte, typ oid.Oid, f format) (any, error) {
|
||||||
|
switch f {
|
||||||
|
case formatBinary:
|
||||||
|
return binaryDecode(s, typ)
|
||||||
|
case formatText:
|
||||||
|
return textDecode(ps, s, typ)
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func binaryDecode(s []byte, typ oid.Oid) (any, error) {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_bytea:
|
||||||
|
return s, nil
|
||||||
|
case oid.T_int8:
|
||||||
|
return int64(binary.BigEndian.Uint64(s)), nil
|
||||||
|
case oid.T_int4:
|
||||||
|
return int64(int32(binary.BigEndian.Uint32(s))), nil
|
||||||
|
case oid.T_int2:
|
||||||
|
return int64(int16(binary.BigEndian.Uint16(s))), nil
|
||||||
|
case oid.T_uuid:
|
||||||
|
return decodeUUIDBinary(s)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: don't know how to decode binary parameter of type %d", uint32(typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format.
|
||||||
|
func decodeUUIDBinary(src []byte) ([]byte, error) {
|
||||||
|
if len(src) != 16 {
|
||||||
|
return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, 36)
|
||||||
|
dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-'
|
||||||
|
hex.Encode(dst[0:], src[0:4])
|
||||||
|
hex.Encode(dst[9:], src[4:6])
|
||||||
|
hex.Encode(dst[14:], src[6:8])
|
||||||
|
hex.Encode(dst[19:], src[8:10])
|
||||||
|
hex.Encode(dst[24:], src[10:16])
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func textDecode(ps *parameterStatus, s []byte, typ oid.Oid) (any, error) {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_char, oid.T_bpchar, oid.T_varchar, oid.T_text:
|
||||||
|
return string(s), nil
|
||||||
|
case oid.T_bytea:
|
||||||
|
b, err := parseBytea(s)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("pq: " + err.Error())
|
||||||
|
}
|
||||||
|
return b, err
|
||||||
|
case oid.T_timestamptz:
|
||||||
|
return parseTS(ps.currentLocation, string(s))
|
||||||
|
case oid.T_timestamp, oid.T_date:
|
||||||
|
return parseTS(nil, string(s))
|
||||||
|
case oid.T_time:
|
||||||
|
return parseTime(typ, s)
|
||||||
|
case oid.T_timetz:
|
||||||
|
return parseTime(typ, s)
|
||||||
|
case oid.T_bool:
|
||||||
|
return s[0] == 't', nil
|
||||||
|
case oid.T_int8, oid.T_int4, oid.T_int2:
|
||||||
|
i, err := strconv.ParseInt(string(s), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("pq: " + err.Error())
|
||||||
|
}
|
||||||
|
return i, err
|
||||||
|
case oid.T_float4, oid.T_float8:
|
||||||
|
// We always use 64 bit parsing, regardless of whether the input text is for
|
||||||
|
// a float4 or float8, because clients expect float64s for all float datatypes
|
||||||
|
// and returning a 32-bit parsed float64 produces lossy results.
|
||||||
|
f, err := strconv.ParseFloat(string(s), 64)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("pq: " + err.Error())
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEncodedText encodes item in text format as required by COPY
|
||||||
|
// and appends to buf
|
||||||
|
func appendEncodedText(buf []byte, x any) ([]byte, error) {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(buf, v, 10), nil
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(buf, v, 'f', -1, 64), nil
|
||||||
|
case []byte:
|
||||||
|
encodedBytea := encodeBytea(v)
|
||||||
|
return appendEscapedText(buf, string(encodedBytea)), nil
|
||||||
|
case string:
|
||||||
|
return appendEscapedText(buf, v), nil
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(buf, v), nil
|
||||||
|
case time.Time:
|
||||||
|
return append(buf, formatTS(v)...), nil
|
||||||
|
case nil:
|
||||||
|
return append(buf, `\N`...), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendEscapedText(buf []byte, text string) []byte {
|
||||||
|
escapeNeeded := false
|
||||||
|
startPos := 0
|
||||||
|
|
||||||
|
// check if we need to escape
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
c := text[i]
|
||||||
|
if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
|
||||||
|
escapeNeeded = true
|
||||||
|
startPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !escapeNeeded {
|
||||||
|
return append(buf, text...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy till first char to escape, iterate the rest
|
||||||
|
result := append(buf, text[:startPos]...)
|
||||||
|
for i := startPos; i < len(text); i++ {
|
||||||
|
switch c := text[i]; c {
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
case '\n':
|
||||||
|
result = append(result, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
result = append(result, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
result = append(result, '\\', 't')
|
||||||
|
default:
|
||||||
|
result = append(result, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTime(typ oid.Oid, s []byte) (time.Time, error) {
|
||||||
|
str := string(s)
|
||||||
|
|
||||||
|
f := "15:04:05"
|
||||||
|
if typ == oid.T_timetz {
|
||||||
|
f = "15:04:05-07"
|
||||||
|
// PostgreSQL just sends the hour if the minute and second is 0:
|
||||||
|
// 22:04:59+00
|
||||||
|
// 22:04:59+08
|
||||||
|
// 22:04:59+08:30
|
||||||
|
// 22:04:59+08:30:40
|
||||||
|
// 23:00:00.112321+02:12:13
|
||||||
|
// So add those to the format string.
|
||||||
|
c := strings.Count(str, ":")
|
||||||
|
if c > 3 {
|
||||||
|
f = "15:04:05-07:00:00"
|
||||||
|
} else if c > 2 {
|
||||||
|
f = "15:04:05-07:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go doesn't parse 24:00, so manually set that to midnight on Jan 2. 24:00
|
||||||
|
// is never with subseconds but may have a timezone:
|
||||||
|
// 24:00:00
|
||||||
|
// 24:00:00+08
|
||||||
|
// 24:00:00-08:01:01
|
||||||
|
var is2400Time bool
|
||||||
|
if strings.HasPrefix(str, "24:00:00") {
|
||||||
|
is2400Time = true
|
||||||
|
if len(str) > 8 {
|
||||||
|
str = "00:00:00" + str[8:]
|
||||||
|
} else {
|
||||||
|
str = "00:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(f, str)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, errors.New("pq: " + err.Error())
|
||||||
|
}
|
||||||
|
if is2400Time {
|
||||||
|
t = t.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
// TODO(v2): it uses UTC, which it shouldn't. But I'm afraid changing it now
|
||||||
|
// will break people's code.
|
||||||
|
//if typ == oid.T_time {
|
||||||
|
// // Don't use UTC but time.FixedZone("", 0)
|
||||||
|
// t = t.In(globalLocationCache.getLocation(0))
|
||||||
|
//}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
infinityTSEnabled = false
|
||||||
|
infinityTSNegative time.Time
|
||||||
|
infinityTSPositive time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableInfinityTs controls the handling of Postgres' "-infinity" and
|
||||||
|
// "infinity" "timestamp"s.
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is not called, "-infinity" and "infinity" will return
|
||||||
|
// []byte("-infinity") and []byte("infinity") respectively, and potentially
|
||||||
|
// cause error "sql: Scan error on column index 0: unsupported driver -> Scan
|
||||||
|
// pair: []uint8 -> *time.Time", when scanning into a time.Time value.
|
||||||
|
//
|
||||||
|
// Once EnableInfinityTs has been called, all connections created using this
|
||||||
|
// driver will decode Postgres' "-infinity" and "infinity" for "timestamp",
|
||||||
|
// "timestamp with time zone" and "date" types to the predefined minimum and
|
||||||
|
// maximum times, respectively. When encoding time.Time values, any time which
|
||||||
|
// equals or precedes the predefined minimum time will be encoded to
|
||||||
|
// "-infinity". Any values at or past the maximum time will similarly be
|
||||||
|
// encoded to "infinity".
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is called with negative >= positive, it will panic.
|
||||||
|
// Calling EnableInfinityTs after a connection has been established results in
|
||||||
|
// undefined behavior. If EnableInfinityTs is called more than once, it will
|
||||||
|
// panic.
|
||||||
|
func EnableInfinityTs(negative time.Time, positive time.Time) {
|
||||||
|
if infinityTSEnabled {
|
||||||
|
panic("pq: infinity timestamp already enabled")
|
||||||
|
}
|
||||||
|
if !negative.Before(positive) {
|
||||||
|
panic("pq: infinity timestamp: negative value must be smaller (before) than positive")
|
||||||
|
}
|
||||||
|
infinityTSEnabled = true
|
||||||
|
infinityTSNegative = negative
|
||||||
|
infinityTSPositive = positive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing might want to toggle infinityTSEnabled
|
||||||
|
func disableInfinityTS() {
|
||||||
|
infinityTSEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a time function specific to the Postgres default DateStyle setting
|
||||||
|
// ("ISO, MDY"), the only one we currently support. This accounts for the
|
||||||
|
// discrepancies between the parsing available with time.Parse and the Postgres
|
||||||
|
// date formatting quirks.
|
||||||
|
func parseTS(currentLocation *time.Location, str string) (any, error) {
|
||||||
|
switch str {
|
||||||
|
case "-infinity":
|
||||||
|
if infinityTSEnabled {
|
||||||
|
return infinityTSNegative, nil
|
||||||
|
}
|
||||||
|
return []byte(str), nil
|
||||||
|
case "infinity":
|
||||||
|
if infinityTSEnabled {
|
||||||
|
return infinityTSPositive, nil
|
||||||
|
}
|
||||||
|
return []byte(str), nil
|
||||||
|
}
|
||||||
|
t, err := ParseTimestamp(currentLocation, str)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("pq: " + err.Error())
|
||||||
|
}
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimestamp parses Postgres' text format. It returns a time.Time in
|
||||||
|
// currentLocation iff that time's offset agrees with the offset sent from the
|
||||||
|
// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the fixed
|
||||||
|
// offset offset provided by the Postgres server.
|
||||||
|
func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) {
|
||||||
|
return pqtime.Parse(currentLocation, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTS formats t into a format postgres understands.
|
||||||
|
func formatTS(t time.Time) []byte {
|
||||||
|
if infinityTSEnabled {
|
||||||
|
// t <= -infinity : ! (t > -infinity)
|
||||||
|
if !t.After(infinityTSNegative) {
|
||||||
|
return []byte("-infinity")
|
||||||
|
}
|
||||||
|
// t >= infinity : ! (!t < infinity)
|
||||||
|
if !t.Before(infinityTSPositive) {
|
||||||
|
return []byte("infinity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FormatTimestamp(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatTimestamp formats t into Postgres' text format for timestamps.
|
||||||
|
func FormatTimestamp(t time.Time) []byte {
|
||||||
|
return pqtime.Format(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||||
|
// "escape" format are supported.
|
||||||
|
func parseBytea(s []byte) (result []byte, err error) {
|
||||||
|
// Hex format.
|
||||||
|
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||||
|
s = s[2:] // trim off leading "\\x"
|
||||||
|
result = make([]byte, hex.DecodedLen(len(s)))
|
||||||
|
_, err := hex.Decode(result, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape format.
|
||||||
|
for len(s) > 0 {
|
||||||
|
if s[0] == '\\' {
|
||||||
|
// escaped '\\'
|
||||||
|
if len(s) >= 2 && s[1] == '\\' {
|
||||||
|
result = append(result, '\\')
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// '\\' followed by an octal number
|
||||||
|
if len(s) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(string(s[1:4]), 8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse bytea value: %w", err)
|
||||||
|
}
|
||||||
|
result = append(result, byte(r))
|
||||||
|
s = s[4:]
|
||||||
|
} else {
|
||||||
|
// We hit an unescaped, raw byte. Try to read in as many as
|
||||||
|
// possible in one go.
|
||||||
|
i := bytes.IndexByte(s, '\\')
|
||||||
|
if i == -1 {
|
||||||
|
result = append(result, s...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result = append(result, s[:i]...)
|
||||||
|
s = s[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBytea(v []byte) (result []byte) {
|
||||||
|
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||||
|
result[0] = '\\'
|
||||||
|
result[1] = 'x'
|
||||||
|
hex.Encode(result[2:], v)
|
||||||
|
return result
|
||||||
|
}
|
||||||
324
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
324
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/lib/pq/pqerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error returned by the PostgreSQL server.
|
||||||
|
//
|
||||||
|
// The [Error] method returns the error message and error code:
|
||||||
|
//
|
||||||
|
// pq: invalid input syntax for type json (22P02)
|
||||||
|
//
|
||||||
|
// The [ErrorWithDetail] method also includes the error Detail, Hint, and
|
||||||
|
// location context (if any):
|
||||||
|
//
|
||||||
|
// ERROR: invalid input syntax for type json (22P02)
|
||||||
|
// DETAIL: Token "asd" is invalid.
|
||||||
|
// CONTEXT: line 5, column 8:
|
||||||
|
//
|
||||||
|
// 3 | 'def',
|
||||||
|
// 4 | 123,
|
||||||
|
// 5 | 'foo', 'asd'::jsonb
|
||||||
|
// ^
|
||||||
|
type Error struct {
|
||||||
|
// [Efatal], [Epanic], [Ewarning], [Enotice], [Edebug], [Einfo], or [Elog].
|
||||||
|
// Always present.
|
||||||
|
Severity string
|
||||||
|
|
||||||
|
// SQLSTATE code. Always present.
|
||||||
|
Code pqerror.Code
|
||||||
|
|
||||||
|
// Primary human-readable error message. This should be accurate but terse
|
||||||
|
// (typically one line). Always present.
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// Optional secondary error message carrying more detail about the problem.
|
||||||
|
// Might run to multiple lines.
|
||||||
|
Detail string
|
||||||
|
|
||||||
|
// Optional suggestion what to do about the problem. This is intended to
|
||||||
|
// differ from Detail in that it offers advice (potentially inappropriate)
|
||||||
|
// rather than hard facts. Might run to multiple lines.
|
||||||
|
Hint string
|
||||||
|
|
||||||
|
// error position as an index into the original query string, as decimal
|
||||||
|
// ASCII integer. The first character has index 1, and positions are
|
||||||
|
// measured in characters not bytes.
|
||||||
|
Position string
|
||||||
|
|
||||||
|
// This is defined the same as the Position field, but it is used when the
|
||||||
|
// cursor position refers to an internally generated command rather than the
|
||||||
|
// one submitted by the client. The InternalQuery field will always appear
|
||||||
|
// when this field appears.
|
||||||
|
InternalPosition string
|
||||||
|
|
||||||
|
// Text of a failed internally-generated command. This could be, for
|
||||||
|
// example, an SQL query issued by a PL/pgSQL function.
|
||||||
|
InternalQuery string
|
||||||
|
|
||||||
|
// An indication of the context in which the error occurred. Presently this
|
||||||
|
// includes a call stack traceback of active procedural language functions
|
||||||
|
// and internally-generated queries. The trace is one entry per line, most
|
||||||
|
// recent first.
|
||||||
|
Where string
|
||||||
|
|
||||||
|
// If the error was associated with a specific database object, the name of
|
||||||
|
// the schema containing that object, if any.
|
||||||
|
Schema string
|
||||||
|
|
||||||
|
// If the error was associated with a specific table, the name of the table.
|
||||||
|
// (Refer to the schema name field for the name of the table's schema.)
|
||||||
|
Table string
|
||||||
|
|
||||||
|
// If the error was associated with a specific table column, the name of the
|
||||||
|
// column. (Refer to the schema and table name fields to identify the
|
||||||
|
// table.)
|
||||||
|
Column string
|
||||||
|
|
||||||
|
// If the error was associated with a specific data type, the name of the
|
||||||
|
// data type. (Refer to the schema name field for the name of the data
|
||||||
|
// type's schema.)
|
||||||
|
DataTypeName string
|
||||||
|
|
||||||
|
// If the error was associated with a specific constraint, the name of the
|
||||||
|
// constraint. Refer to fields listed above for the associated table or
|
||||||
|
// domain. (For this purpose, indexes are treated as constraints, even if
|
||||||
|
// they weren't created with constraint syntax.)
|
||||||
|
Constraint string
|
||||||
|
|
||||||
|
// File name of the source-code location where the error was reported.
|
||||||
|
File string
|
||||||
|
|
||||||
|
// Line number of the source-code location where the error was reported.
|
||||||
|
Line string
|
||||||
|
|
||||||
|
// Name of the source-code routine reporting the error.
|
||||||
|
Routine string
|
||||||
|
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ErrorCode is a five-character error code.
|
||||||
|
//
|
||||||
|
// Deprecated: use pqerror.Code
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
|
ErrorCode = pqerror.Code
|
||||||
|
|
||||||
|
// ErrorClass is only the class part of an error code.
|
||||||
|
//
|
||||||
|
// Deprecated: use pqerror.Class
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
|
ErrorClass = pqerror.Class
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseError(r *readBuf, q string) *Error {
|
||||||
|
err := &Error{query: q}
|
||||||
|
for t := r.byte(); t != 0; t = r.byte() {
|
||||||
|
msg := r.string()
|
||||||
|
switch t {
|
||||||
|
case 'S':
|
||||||
|
err.Severity = msg
|
||||||
|
case 'C':
|
||||||
|
err.Code = pqerror.Code(msg)
|
||||||
|
case 'M':
|
||||||
|
err.Message = msg
|
||||||
|
case 'D':
|
||||||
|
err.Detail = msg
|
||||||
|
case 'H':
|
||||||
|
err.Hint = msg
|
||||||
|
case 'P':
|
||||||
|
err.Position = msg
|
||||||
|
case 'p':
|
||||||
|
err.InternalPosition = msg
|
||||||
|
case 'q':
|
||||||
|
err.InternalQuery = msg
|
||||||
|
case 'W':
|
||||||
|
err.Where = msg
|
||||||
|
case 's':
|
||||||
|
err.Schema = msg
|
||||||
|
case 't':
|
||||||
|
err.Table = msg
|
||||||
|
case 'c':
|
||||||
|
err.Column = msg
|
||||||
|
case 'd':
|
||||||
|
err.DataTypeName = msg
|
||||||
|
case 'n':
|
||||||
|
err.Constraint = msg
|
||||||
|
case 'F':
|
||||||
|
err.File = msg
|
||||||
|
case 'L':
|
||||||
|
err.Line = msg
|
||||||
|
case 'R':
|
||||||
|
err.Routine = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal returns true if the Error Severity is fatal.
|
||||||
|
func (e *Error) Fatal() bool { return e.Severity == pqerror.SeverityFatal }
|
||||||
|
|
||||||
|
// SQLState returns the SQLState of the error.
|
||||||
|
func (e *Error) SQLState() string { return string(e.Code) }
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
msg := e.Message
|
||||||
|
if e.query != "" && e.Position != "" {
|
||||||
|
pos, err := strconv.Atoi(e.Position)
|
||||||
|
if err == nil {
|
||||||
|
lines := strings.Split(e.query, "\n")
|
||||||
|
line, col := posToLine(pos, lines)
|
||||||
|
if len(lines) == 1 {
|
||||||
|
msg += " at column " + strconv.Itoa(col)
|
||||||
|
} else {
|
||||||
|
msg += " at position " + strconv.Itoa(line) + ":" + strconv.Itoa(col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Code != "" {
|
||||||
|
return "pq: " + msg + " (" + string(e.Code) + ")"
|
||||||
|
}
|
||||||
|
return "pq: " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorWithDetail returns the error message with detailed information and
|
||||||
|
// location context (if any).
|
||||||
|
//
|
||||||
|
// See the documentation on [Error].
|
||||||
|
func (e *Error) ErrorWithDetail() string {
|
||||||
|
b := new(strings.Builder)
|
||||||
|
b.Grow(len(e.Message) + len(e.Detail) + len(e.Hint) + 30)
|
||||||
|
b.WriteString("ERROR: ")
|
||||||
|
b.WriteString(e.Message)
|
||||||
|
if e.Code != "" {
|
||||||
|
b.WriteString(" (")
|
||||||
|
b.WriteString(string(e.Code))
|
||||||
|
b.WriteByte(')')
|
||||||
|
}
|
||||||
|
if e.Detail != "" {
|
||||||
|
b.WriteString("\nDETAIL: ")
|
||||||
|
b.WriteString(e.Detail)
|
||||||
|
}
|
||||||
|
if e.Hint != "" {
|
||||||
|
b.WriteString("\nHINT: ")
|
||||||
|
b.WriteString(e.Hint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.query != "" && e.Position != "" {
|
||||||
|
b.Grow(512)
|
||||||
|
pos, err := strconv.Atoi(e.Position)
|
||||||
|
if err != nil {
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
lines := strings.Split(e.query, "\n")
|
||||||
|
line, col := posToLine(pos, lines)
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "\nCONTEXT: line %d, column %d:\n\n", line, col)
|
||||||
|
if line > 2 {
|
||||||
|
fmt.Fprintf(b, "% 7d | %s\n", line-2, expandTab(lines[line-3]))
|
||||||
|
}
|
||||||
|
if line > 1 {
|
||||||
|
fmt.Fprintf(b, "% 7d | %s\n", line-1, expandTab(lines[line-2]))
|
||||||
|
}
|
||||||
|
/// Expand tabs, so that the ^ is at at the correct position, but leave
|
||||||
|
/// "column 10-13" intact. Adjusting this to the visual column would be
|
||||||
|
/// better, but we don't know the tabsize of the user in their editor,
|
||||||
|
/// which can be 8, 4, 2, or something else. We can't know. So leaving
|
||||||
|
/// it as the character index is probably the "most correct".
|
||||||
|
expanded := expandTab(lines[line-1])
|
||||||
|
diff := len(expanded) - len(lines[line-1])
|
||||||
|
fmt.Fprintf(b, "% 7d | %s\n", line, expanded)
|
||||||
|
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col-1+diff), "^")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func posToLine(pos int, lines []string) (line, col int) {
|
||||||
|
read := 0
|
||||||
|
for i := range lines {
|
||||||
|
line++
|
||||||
|
ll := utf8.RuneCountInString(lines[i]) + 1 // +1 for the removed newline
|
||||||
|
if read+ll >= pos {
|
||||||
|
col = max(pos-read, 1) // Should be lower than 1, but just in case.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
read += ll
|
||||||
|
}
|
||||||
|
return line, col
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandTab(s string) string {
|
||||||
|
var (
|
||||||
|
b strings.Builder
|
||||||
|
l int
|
||||||
|
fill = func(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = ' '
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
b.Grow(len(s))
|
||||||
|
for _, r := range s {
|
||||||
|
switch r {
|
||||||
|
case '\t':
|
||||||
|
tw := 8 - l%8
|
||||||
|
b.WriteString(fill(tw))
|
||||||
|
l += tw
|
||||||
|
default:
|
||||||
|
b.WriteRune(r)
|
||||||
|
l += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) handleError(reported error, query ...string) error {
|
||||||
|
switch err := reported.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
case runtime.Error, *net.OpError:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
case *safeRetryError:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
reported = driver.ErrBadConn
|
||||||
|
case *Error:
|
||||||
|
if len(query) > 0 && query[0] != "" {
|
||||||
|
err.query = query[0]
|
||||||
|
reported = err
|
||||||
|
}
|
||||||
|
if err.Fatal() {
|
||||||
|
reported = driver.ErrBadConn
|
||||||
|
}
|
||||||
|
case error:
|
||||||
|
if err == io.EOF || err.Error() == "remote error: handshake failure" {
|
||||||
|
reported = driver.ErrBadConn
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
reported = fmt.Errorf("pq: unknown error %T: %[1]s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any time we return ErrBadConn, we need to remember it since *Tx doesn't
|
||||||
|
// mark the connection bad in database/sql.
|
||||||
|
if reported == driver.ErrBadConn {
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
}
|
||||||
|
return reported
|
||||||
|
}
|
||||||
71
vendor/github.com/lib/pq/internal/pgpass/pgpass.go
generated
vendored
Normal file
71
vendor/github.com/lib/pq/internal/pgpass/pgpass.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package pgpass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/pqutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PasswordFromPgpass(passfile, user, password, host, port, dbname string, passwordSet bool) string {
|
||||||
|
// Do not process .pgpass if a password was supplied.
|
||||||
|
if passwordSet {
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := pqutil.Pgpass(passfile)
|
||||||
|
if filename == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
scan := bufio.NewScanner(fp)
|
||||||
|
for scan.Scan() {
|
||||||
|
line := scan.Text()
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
split := splitFields(line)
|
||||||
|
if len(split) != 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
socket := host == "" || filepath.IsAbs(host) || strings.HasPrefix(host, "@")
|
||||||
|
if (split[0] == "*" || split[0] == host || (split[0] == "localhost" && socket)) &&
|
||||||
|
(split[1] == "*" || split[1] == port) &&
|
||||||
|
(split[2] == "*" || split[2] == dbname) &&
|
||||||
|
(split[3] == "*" || split[3] == user) {
|
||||||
|
return split[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitFields(s string) []string {
|
||||||
|
var (
|
||||||
|
fs = make([]string, 0, 5)
|
||||||
|
f = make([]rune, 0, len(s))
|
||||||
|
esc bool
|
||||||
|
)
|
||||||
|
for _, c := range s {
|
||||||
|
switch {
|
||||||
|
case esc:
|
||||||
|
f, esc = append(f, c), false
|
||||||
|
case c == '\\':
|
||||||
|
esc = true
|
||||||
|
case c == ':':
|
||||||
|
fs, f = append(fs, string(f)), f[:0]
|
||||||
|
default:
|
||||||
|
f = append(f, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(fs, string(f))
|
||||||
|
}
|
||||||
70
vendor/github.com/lib/pq/internal/pgservice/pgservice.go
generated
vendored
Normal file
70
vendor/github.com/lib/pq/internal/pgservice/pgservice.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package pgservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/pqutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FindService(path string, service string) (map[string]string, error) {
|
||||||
|
fp, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
if pqutil.ErrNotExists(err) {
|
||||||
|
// libpq just returns "definition of service not found" if the
|
||||||
|
// default file doesn't exist, but IMO that's confusing.
|
||||||
|
return nil, fmt.Errorf("service file %q not found", path)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
scan = bufio.NewScanner(fp)
|
||||||
|
i int
|
||||||
|
)
|
||||||
|
for scan.Scan() {
|
||||||
|
i++
|
||||||
|
line := strings.TrimSpace(scan.Text())
|
||||||
|
if line == "" || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// [service] header that we want.
|
||||||
|
if line[0] == '[' && line[len(line)-1] == ']' && strings.TrimSpace(line[1:len(line)-1]) == service {
|
||||||
|
opts := make(map[string]string)
|
||||||
|
for scan.Scan() {
|
||||||
|
i++
|
||||||
|
line := strings.TrimSpace(scan.Text())
|
||||||
|
if line == "" || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Next header: our work here is done.
|
||||||
|
if line[0] == '[' && line[len(line)-1] == ']' {
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k, v, ok := strings.Cut(line, "=")
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("line %d: missing '=' in %q", i, line)
|
||||||
|
}
|
||||||
|
k, v = strings.TrimSpace(k), strings.TrimSpace(v)
|
||||||
|
if k == "" {
|
||||||
|
return nil, fmt.Errorf("line %d: no value before '=' in %q", i, line)
|
||||||
|
}
|
||||||
|
opts[k] = v
|
||||||
|
}
|
||||||
|
if scan.Err() != nil {
|
||||||
|
return nil, scan.Err()
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.Err() != nil {
|
||||||
|
return nil, scan.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("definition of service %q not found", service)
|
||||||
|
}
|
||||||
37
vendor/github.com/lib/pq/internal/pqsql/copy.go
generated
vendored
Normal file
37
vendor/github.com/lib/pq/internal/pqsql/copy.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package pqsql
|
||||||
|
|
||||||
|
// StartsWithCopy reports if the SQL strings start with "copy", ignoring
|
||||||
|
// whitespace, comments, and casing.
|
||||||
|
func StartsWithCopy(query string) bool {
|
||||||
|
if len(query) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var linecmt, blockcmt bool
|
||||||
|
for i := 0; i < len(query); i++ {
|
||||||
|
c := query[i]
|
||||||
|
if linecmt {
|
||||||
|
linecmt = c != '\n'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if blockcmt {
|
||||||
|
blockcmt = !(c == '/' && query[i-1] == '*')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '-' && len(query) > i+1 && query[i+1] == '-' {
|
||||||
|
linecmt = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '/' && len(query) > i+1 && query[i+1] == '*' {
|
||||||
|
blockcmt = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// First non-comment and non-whitespace.
|
||||||
|
return len(query) > i+3 && c|0x20 == 'c' && query[i+1]|0x20 == 'o' &&
|
||||||
|
query[i+2]|0x20 == 'p' && query[i+3]|0x20 == 'y'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
37
vendor/github.com/lib/pq/internal/pqtime/loc.go
generated
vendored
Normal file
37
vendor/github.com/lib/pq/internal/pqtime/loc.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package pqtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The location cache caches the time zones typically used by the client.
|
||||||
|
type locationCache struct {
|
||||||
|
cache map[int]*time.Location
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// All connections share the same list of timezones. Benchmarking shows that
|
||||||
|
// about 5% speed could be gained by putting the cache in the connection and
|
||||||
|
// losing the mutex, at the cost of a small amount of memory and a somewhat
|
||||||
|
// significant increase in code complexity.
|
||||||
|
var globalLocationCache = &locationCache{cache: make(map[int]*time.Location)}
|
||||||
|
|
||||||
|
func Reset() {
|
||||||
|
globalLocationCache = &locationCache{cache: make(map[int]*time.Location)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cached timezone for the specified offset, creating and caching
|
||||||
|
// it if necessary.
|
||||||
|
func (c *locationCache) getLocation(offset int) *time.Location {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
l, ok := c.cache[offset]
|
||||||
|
if !ok {
|
||||||
|
// TODO(v2): for offset=0 it should use some descriptive text like
|
||||||
|
// "without time zone".
|
||||||
|
l = time.FixedZone("", offset)
|
||||||
|
c.cache[offset] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
190
vendor/github.com/lib/pq/internal/pqtime/pqtime.go
generated
vendored
Normal file
190
vendor/github.com/lib/pq/internal/pqtime/pqtime.go
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package pqtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
|
|
||||||
|
type timestampParser struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) expect(str string, char byte, pos int) {
|
||||||
|
if p.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pos+1 > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := str[pos]; c != char && p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) mustAtoi(str string, begin int, end int) int {
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if begin < 0 || end < 0 || begin > end || end > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
result, err := strconv.Atoi(str[begin:end])
|
||||||
|
if err != nil {
|
||||||
|
if p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected number; got '%v'", str)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(currentLocation *time.Location, str string) (time.Time, error) {
|
||||||
|
p := timestampParser{}
|
||||||
|
|
||||||
|
monSep := strings.IndexRune(str, '-')
|
||||||
|
// this is Gregorian year, not ISO Year
|
||||||
|
// In Gregorian system, the year 1 BC is followed by AD 1
|
||||||
|
year := p.mustAtoi(str, 0, monSep)
|
||||||
|
daySep := monSep + 3
|
||||||
|
month := p.mustAtoi(str, monSep+1, daySep)
|
||||||
|
p.expect(str, '-', daySep)
|
||||||
|
timeSep := daySep + 3
|
||||||
|
day := p.mustAtoi(str, daySep+1, timeSep)
|
||||||
|
|
||||||
|
minLen := monSep + len("01-01") + 1
|
||||||
|
|
||||||
|
isBC := strings.HasSuffix(str, " BC")
|
||||||
|
if isBC {
|
||||||
|
minLen += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
var hour, minute, second int
|
||||||
|
if len(str) > minLen {
|
||||||
|
p.expect(str, ' ', timeSep)
|
||||||
|
minSep := timeSep + 3
|
||||||
|
p.expect(str, ':', minSep)
|
||||||
|
hour = p.mustAtoi(str, timeSep+1, minSep)
|
||||||
|
secSep := minSep + 3
|
||||||
|
p.expect(str, ':', secSep)
|
||||||
|
minute = p.mustAtoi(str, minSep+1, secSep)
|
||||||
|
secEnd := secSep + 3
|
||||||
|
second = p.mustAtoi(str, secSep+1, secEnd)
|
||||||
|
}
|
||||||
|
remainderIdx := monSep + len("01-01 00:00:00") + 1
|
||||||
|
// Three optional (but ordered) sections follow: the
|
||||||
|
// fractional seconds, the time zone offset, and the BC
|
||||||
|
// designation. We set them up here and adjust the other
|
||||||
|
// offsets if the preceding sections exist.
|
||||||
|
|
||||||
|
nanoSec := 0
|
||||||
|
tzOff := 0
|
||||||
|
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == '.' {
|
||||||
|
fracStart := remainderIdx + 1
|
||||||
|
fracOff := strings.IndexAny(str[fracStart:], "-+Z ")
|
||||||
|
if fracOff < 0 {
|
||||||
|
fracOff = len(str) - fracStart
|
||||||
|
}
|
||||||
|
fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff)
|
||||||
|
nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
|
||||||
|
|
||||||
|
remainderIdx += fracOff + 1
|
||||||
|
}
|
||||||
|
if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') {
|
||||||
|
// time zone separator is always '-' or '+' or 'Z' (UTC is +00)
|
||||||
|
var tzSign int
|
||||||
|
switch c := str[tzStart]; c {
|
||||||
|
case '-':
|
||||||
|
tzSign = -1
|
||||||
|
case '+':
|
||||||
|
tzSign = +1
|
||||||
|
default:
|
||||||
|
return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
|
||||||
|
}
|
||||||
|
tzHours := p.mustAtoi(str, tzStart+1, tzStart+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
var tzMin, tzSec int
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||||
|
} else if tzStart < len(str) && str[tzStart] == 'Z' {
|
||||||
|
// time zone Z separator indicates UTC is +00
|
||||||
|
remainderIdx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var isoYear int
|
||||||
|
|
||||||
|
if isBC {
|
||||||
|
isoYear = 1 - year
|
||||||
|
remainderIdx += 3
|
||||||
|
} else {
|
||||||
|
isoYear = year
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) {
|
||||||
|
return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:])
|
||||||
|
}
|
||||||
|
t := time.Date(isoYear, time.Month(month), day,
|
||||||
|
hour, minute, second, nanoSec,
|
||||||
|
globalLocationCache.getLocation(tzOff))
|
||||||
|
|
||||||
|
if currentLocation != nil {
|
||||||
|
// Set the location of the returned Time based on the session's
|
||||||
|
// TimeZone value, but only if the local time zone database agrees with
|
||||||
|
// the remote database on the offset.
|
||||||
|
lt := t.In(currentLocation)
|
||||||
|
_, newOff := lt.Zone()
|
||||||
|
if newOff == tzOff {
|
||||||
|
t = lt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format into Postgres' text format for timestamps.
|
||||||
|
func Format(t time.Time) []byte {
|
||||||
|
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||||
|
// minus sign preferred by Go.
|
||||||
|
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||||
|
bc := false
|
||||||
|
if t.Year() <= 0 {
|
||||||
|
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||||
|
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||||
|
bc = true
|
||||||
|
}
|
||||||
|
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
|
||||||
|
|
||||||
|
_, offset := t.Zone()
|
||||||
|
offset %= 60
|
||||||
|
if offset != 0 {
|
||||||
|
// RFC3339Nano already printed the minus sign
|
||||||
|
if offset < 0 {
|
||||||
|
offset = -offset
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, ':')
|
||||||
|
if offset < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
b = strconv.AppendInt(b, int64(offset), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
b = append(b, " BC"...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
86
vendor/github.com/lib/pq/internal/pqutil/path.go
generated
vendored
Normal file
86
vendor/github.com/lib/pq/internal/pqutil/path.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package pqutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Home gets the PostgreSQL configuration dir in the user's home directory:
|
||||||
|
// %APPDATA%/postgresql on Windows, and $HOME/.postgresql/postgresql.crt
|
||||||
|
// everywhere else.
|
||||||
|
//
|
||||||
|
// Returns an empy string if no home directory was found.
|
||||||
|
//
|
||||||
|
// Matches pqGetHomeDirectory() from PostgreSQL.
|
||||||
|
// https://github.com/postgres/postgres/blob/2b117bb/src/interfaces/libpq/fe-connect.c#L8214
|
||||||
|
func Home() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// pq uses SHGetFolderPath(), which is deprecated but x/sys/windows has
|
||||||
|
// KnownFolderPath(). We don't really want to pull that in though, so
|
||||||
|
// use APPDATA env. This is also what PostgreSQL uses in some other
|
||||||
|
// codepaths (get_home_path() for example).
|
||||||
|
ad := os.Getenv("APPDATA")
|
||||||
|
if ad == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(ad, "postgresql")
|
||||||
|
}
|
||||||
|
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
if home == "" {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
home = u.HomeDir
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".postgresql")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNotExists reports if err is a "path doesn't exist" type error.
|
||||||
|
//
|
||||||
|
// fs.ErrNotExist is not enough, as "/dev/null/somefile" will return ENOTDIR
|
||||||
|
// instead of ENOENT.
|
||||||
|
func ErrNotExists(err error) bool {
|
||||||
|
perr := new(os.PathError)
|
||||||
|
if errors.As(err, &perr) && (perr.Err == syscall.ENOENT || perr.Err == syscall.ENOTDIR) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var WarnFD io.Writer = os.Stderr
|
||||||
|
|
||||||
|
// Pgpass gets the filepath to the pgpass file to use, returning "" if a pgpass
|
||||||
|
// file shouldn't be used.
|
||||||
|
func Pgpass(passfile string) string {
|
||||||
|
// Get passfile from the options.
|
||||||
|
if passfile == "" {
|
||||||
|
home := Home()
|
||||||
|
if home == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
passfile = filepath.Join(home, ".pgpass")
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Win32, the directory is protected, so we don't have to check the file.
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
fi, err := os.Stat(passfile)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if fi.Mode().Perm()&(0x77) != 0 {
|
||||||
|
fmt.Fprintf(WarnFD,
|
||||||
|
"WARNING: password file %q has group or world access; permissions should be u=rw (0600) or less\n",
|
||||||
|
passfile)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return passfile
|
||||||
|
}
|
||||||
64
vendor/github.com/lib/pq/internal/pqutil/perm.go
generated
vendored
Normal file
64
vendor/github.com/lib/pq/internal/pqutil/perm.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//go:build !windows && !plan9
|
||||||
|
|
||||||
|
package pqutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSSLKeyUnknownOwnership = errors.New("pq: could not get owner information for private key, may not be properly protected")
|
||||||
|
ErrSSLKeyHasWorldPermissions = errors.New("pq: private key has world access; permissions should be u=rw,g=r (0640) if owned by root, or u=rw (0600), or less")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSLKeyPermissions checks the permissions on user-supplied SSL key files,
|
||||||
|
// which should have very little access. libpq does not check key file
|
||||||
|
// permissions on Windows.
|
||||||
|
//
|
||||||
|
// If the file is owned by the same user the process is running as, the file
|
||||||
|
// should only have 0600. If the file is owned by root, and the group matches
|
||||||
|
// the group that the process is running in, the permissions cannot be more than
|
||||||
|
// 0640. The file should never have world permissions.
|
||||||
|
//
|
||||||
|
// Returns an error when the permission check fails.
|
||||||
|
func SSLKeyPermissions(sslkey string) error {
|
||||||
|
fi, err := os.Stat(sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckPermissions(fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPermissions(fi os.FileInfo) error {
|
||||||
|
// The maximum permissions that a private key file owned by a regular user
|
||||||
|
// is allowed to have. This translates to u=rw. Regardless of if we're
|
||||||
|
// running as root or not, 0600 is acceptable, so we return if no bits
|
||||||
|
// beyond the regular user permission mask are set.
|
||||||
|
if fi.Mode().Perm()&^os.FileMode(0o600) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to pull the Unix file information to get the file's owner.
|
||||||
|
// If we can't access it, there's some sort of operating system level error
|
||||||
|
// and we should fail rather than attempting to use faulty information.
|
||||||
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return ErrSSLKeyUnknownOwnership
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the file is owned by root, we allow 0640 (u=rw,g=r) to match what
|
||||||
|
// Postgres does.
|
||||||
|
if sys.Uid == 0 {
|
||||||
|
// The maximum permissions that a private key file owned by root is
|
||||||
|
// allowed to have. This translates to u=rw,g=r.
|
||||||
|
if fi.Mode().Perm()&^os.FileMode(0o640) != 0 {
|
||||||
|
return ErrSSLKeyHasWorldPermissions
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrSSLKeyHasWorldPermissions
|
||||||
|
}
|
||||||
12
vendor/github.com/lib/pq/internal/pqutil/perm_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/lib/pq/internal/pqutil/perm_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
//go:build windows || plan9
|
||||||
|
|
||||||
|
package pqutil
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSSLKeyUnknownOwnership = errors.New("unused")
|
||||||
|
ErrSSLKeyHasWorldPermissions = errors.New("unused")
|
||||||
|
)
|
||||||
|
|
||||||
|
func SSLKeyPermissions(sslkey string) error { return nil }
|
||||||
32
vendor/github.com/lib/pq/internal/pqutil/pqutil.go
generated
vendored
Normal file
32
vendor/github.com/lib/pq/internal/pqutil/pqutil.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package pqutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBool is like strconv.ParseBool, but also accepts "yes"/"no" and
|
||||||
|
// "on"/"off".
|
||||||
|
func ParseBool(str string) (bool, error) {
|
||||||
|
switch str {
|
||||||
|
case "1", "t", "T", "true", "TRUE", "True", "yes", "on":
|
||||||
|
return true, nil
|
||||||
|
case "0", "f", "F", "false", "FALSE", "False", "no", "off":
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, &strconv.NumError{Func: "ParseBool", Num: str, Err: strconv.ErrSyntax}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Join[S ~[]E, E ~string](s S) string {
|
||||||
|
var b strings.Builder
|
||||||
|
for i := range s {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
if i == len(s)-1 {
|
||||||
|
b.WriteString("or ")
|
||||||
|
}
|
||||||
|
b.WriteString(string(s[i]))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
9
vendor/github.com/lib/pq/internal/pqutil/user_other.go
generated
vendored
Normal file
9
vendor/github.com/lib/pq/internal/pqutil/user_other.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build js || android || hurd || zos || wasip1 || appengine
|
||||||
|
|
||||||
|
package pqutil
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func User() (string, error) {
|
||||||
|
return "", errors.New("pqutil.User: not supported on current platform")
|
||||||
|
}
|
||||||
25
vendor/github.com/lib/pq/internal/pqutil/user_posix.go
generated
vendored
Normal file
25
vendor/github.com/lib/pq/internal/pqutil/user_posix.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//go:build !windows && !js && !android && !hurd && !zos && !wasip1 && !appengine
|
||||||
|
|
||||||
|
package pqutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func User() (string, error) {
|
||||||
|
env := "USER"
|
||||||
|
if runtime.GOOS == "plan9" {
|
||||||
|
env = "user"
|
||||||
|
}
|
||||||
|
if n := os.Getenv(env); n != "" {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return u.Username, nil
|
||||||
|
}
|
||||||
28
vendor/github.com/lib/pq/internal/pqutil/user_windows.go
generated
vendored
Normal file
28
vendor/github.com/lib/pq/internal/pqutil/user_windows.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//go:build windows && !appengine
|
||||||
|
|
||||||
|
package pqutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func User() (string, error) {
|
||||||
|
// Perform Windows user name lookup identically to libpq.
|
||||||
|
//
|
||||||
|
// The PostgreSQL code makes use of the legacy Win32 function GetUserName,
|
||||||
|
// and that function has not been imported into stock Go. GetUserNameEx is
|
||||||
|
// available though, the difference being that a wider range of names are
|
||||||
|
// available. To get the output to be the same as GetUserName, only the
|
||||||
|
// base (or last) component of the result is returned.
|
||||||
|
var (
|
||||||
|
name = make([]uint16, 128)
|
||||||
|
pwnameSz = uint32(len(name)) - 1
|
||||||
|
)
|
||||||
|
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &name[0], &pwnameSz)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s := syscall.UTF16ToString(name)
|
||||||
|
return filepath.Base(s), nil
|
||||||
|
}
|
||||||
186
vendor/github.com/lib/pq/internal/proto/proto.go
generated
vendored
Normal file
186
vendor/github.com/lib/pq/internal/proto/proto.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// From src/include/libpq/protocol.h and src/include/libpq/pqcomm.h – PostgreSQL 18.1
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants from pqcomm.h
|
||||||
|
const (
|
||||||
|
ProtocolVersion30 = (3 << 16) | 0 //lint:ignore SA4016 x
|
||||||
|
ProtocolVersion32 = (3 << 16) | 2 // PostgreSQL ≥18.
|
||||||
|
CancelRequestCode = (1234 << 16) | 5678
|
||||||
|
NegotiateSSLCode = (1234 << 16) | 5679
|
||||||
|
NegotiateGSSCode = (1234 << 16) | 5680
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants from fe-connect.c
|
||||||
|
const (
|
||||||
|
MaxErrlen = 30_000 // https://github.com/postgres/postgres/blob/c6a10a89f/src/interfaces/libpq/fe-connect.c#L4067
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestCode is a request codes sent by the frontend.
|
||||||
|
type RequestCode byte
|
||||||
|
|
||||||
|
// These are the request codes sent by the frontend.
|
||||||
|
const (
|
||||||
|
Bind = RequestCode('B')
|
||||||
|
Close = RequestCode('C')
|
||||||
|
Describe = RequestCode('D')
|
||||||
|
Execute = RequestCode('E')
|
||||||
|
FunctionCall = RequestCode('F')
|
||||||
|
Flush = RequestCode('H')
|
||||||
|
Parse = RequestCode('P')
|
||||||
|
Query = RequestCode('Q')
|
||||||
|
Sync = RequestCode('S')
|
||||||
|
Terminate = RequestCode('X')
|
||||||
|
CopyFail = RequestCode('f')
|
||||||
|
GSSResponse = RequestCode('p')
|
||||||
|
PasswordMessage = RequestCode('p')
|
||||||
|
SASLInitialResponse = RequestCode('p')
|
||||||
|
SASLResponse = RequestCode('p')
|
||||||
|
CopyDoneRequest = RequestCode('c')
|
||||||
|
CopyDataRequest = RequestCode('d')
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r RequestCode) String() string {
|
||||||
|
s, ok := map[RequestCode]string{
|
||||||
|
Bind: "Bind",
|
||||||
|
Close: "Close",
|
||||||
|
Describe: "Describe",
|
||||||
|
Execute: "Execute",
|
||||||
|
FunctionCall: "FunctionCall",
|
||||||
|
Flush: "Flush",
|
||||||
|
Parse: "Parse",
|
||||||
|
Query: "Query",
|
||||||
|
Sync: "Sync",
|
||||||
|
Terminate: "Terminate",
|
||||||
|
CopyFail: "CopyFail",
|
||||||
|
// These are all the same :-/
|
||||||
|
//GSSResponse: "GSSResponse",
|
||||||
|
PasswordMessage: "PasswordMessage",
|
||||||
|
//SASLInitialResponse: "SASLInitialResponse",
|
||||||
|
//SASLResponse: "SASLResponse",
|
||||||
|
CopyDoneRequest: "CopyDone",
|
||||||
|
CopyDataRequest: "CopyData",
|
||||||
|
}[r]
|
||||||
|
if !ok {
|
||||||
|
s = "<unknown>"
|
||||||
|
}
|
||||||
|
c := string(r)
|
||||||
|
if r <= 0x1f || r == 0x7f {
|
||||||
|
c = fmt.Sprintf("0x%x", string(r))
|
||||||
|
}
|
||||||
|
return "(" + c + ") " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseCode is a response codes sent by the backend.
|
||||||
|
type ResponseCode byte
|
||||||
|
|
||||||
|
// These are the response codes sent by the backend.
|
||||||
|
const (
|
||||||
|
ParseComplete = ResponseCode('1')
|
||||||
|
BindComplete = ResponseCode('2')
|
||||||
|
CloseComplete = ResponseCode('3')
|
||||||
|
NotificationResponse = ResponseCode('A')
|
||||||
|
CommandComplete = ResponseCode('C')
|
||||||
|
DataRow = ResponseCode('D')
|
||||||
|
ErrorResponse = ResponseCode('E')
|
||||||
|
CopyInResponse = ResponseCode('G')
|
||||||
|
CopyOutResponse = ResponseCode('H')
|
||||||
|
EmptyQueryResponse = ResponseCode('I')
|
||||||
|
BackendKeyData = ResponseCode('K')
|
||||||
|
NoticeResponse = ResponseCode('N')
|
||||||
|
AuthenticationRequest = ResponseCode('R')
|
||||||
|
ParameterStatus = ResponseCode('S')
|
||||||
|
RowDescription = ResponseCode('T')
|
||||||
|
FunctionCallResponse = ResponseCode('V')
|
||||||
|
CopyBothResponse = ResponseCode('W')
|
||||||
|
ReadyForQuery = ResponseCode('Z')
|
||||||
|
NoData = ResponseCode('n')
|
||||||
|
PortalSuspended = ResponseCode('s')
|
||||||
|
ParameterDescription = ResponseCode('t')
|
||||||
|
NegotiateProtocolVersion = ResponseCode('v')
|
||||||
|
CopyDoneResponse = ResponseCode('c')
|
||||||
|
CopyDataResponse = ResponseCode('d')
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ResponseCode) String() string {
|
||||||
|
s, ok := map[ResponseCode]string{
|
||||||
|
ParseComplete: "ParseComplete",
|
||||||
|
BindComplete: "BindComplete",
|
||||||
|
CloseComplete: "CloseComplete",
|
||||||
|
NotificationResponse: "NotificationResponse",
|
||||||
|
CommandComplete: "CommandComplete",
|
||||||
|
DataRow: "DataRow",
|
||||||
|
ErrorResponse: "ErrorResponse",
|
||||||
|
CopyInResponse: "CopyInResponse",
|
||||||
|
CopyOutResponse: "CopyOutResponse",
|
||||||
|
EmptyQueryResponse: "EmptyQueryResponse",
|
||||||
|
BackendKeyData: "BackendKeyData",
|
||||||
|
NoticeResponse: "NoticeResponse",
|
||||||
|
AuthenticationRequest: "AuthRequest",
|
||||||
|
ParameterStatus: "ParamStatus",
|
||||||
|
RowDescription: "RowDescription",
|
||||||
|
FunctionCallResponse: "FunctionCallResponse",
|
||||||
|
CopyBothResponse: "CopyBothResponse",
|
||||||
|
ReadyForQuery: "ReadyForQuery",
|
||||||
|
NoData: "NoData",
|
||||||
|
PortalSuspended: "PortalSuspended",
|
||||||
|
ParameterDescription: "ParamDescription",
|
||||||
|
NegotiateProtocolVersion: "NegotiateProtocolVersion",
|
||||||
|
CopyDoneResponse: "CopyDone",
|
||||||
|
CopyDataResponse: "CopyData",
|
||||||
|
}[r]
|
||||||
|
if !ok {
|
||||||
|
s = "<unknown>"
|
||||||
|
}
|
||||||
|
c := string(r)
|
||||||
|
if r <= 0x1f || r == 0x7f {
|
||||||
|
c = fmt.Sprintf("0x%x", string(r))
|
||||||
|
}
|
||||||
|
return "(" + c + ") " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCode are authentication request codes sent by the backend.
|
||||||
|
type AuthCode int32
|
||||||
|
|
||||||
|
// These are the authentication request codes sent by the backend.
|
||||||
|
const (
|
||||||
|
AuthReqOk = AuthCode(0) // User is authenticated
|
||||||
|
AuthReqKrb4 = AuthCode(1) // Kerberos V4. Not supported any more.
|
||||||
|
AuthReqKrb5 = AuthCode(2) // Kerberos V5. Not supported any more.
|
||||||
|
AuthReqPassword = AuthCode(3) // Password
|
||||||
|
AuthReqCrypt = AuthCode(4) // crypt password. Not supported any more.
|
||||||
|
AuthReqMD5 = AuthCode(5) // md5 password
|
||||||
|
_ = AuthCode(6) // 6 is available. It was used for SCM creds, not supported any more.
|
||||||
|
AuthReqGSS = AuthCode(7) // GSSAPI without wrap()
|
||||||
|
AuthReqGSSCont = AuthCode(8) // Continue GSS exchanges
|
||||||
|
AuthReqSSPI = AuthCode(9) // SSPI negotiate without wrap()
|
||||||
|
AuthReqSASL = AuthCode(10) // Begin SASL authentication
|
||||||
|
AuthReqSASLCont = AuthCode(11) // Continue SASL authentication
|
||||||
|
AuthReqSASLFin = AuthCode(12) // Final SASL message
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a AuthCode) String() string {
|
||||||
|
s, ok := map[AuthCode]string{
|
||||||
|
AuthReqOk: "ok",
|
||||||
|
AuthReqKrb4: "krb4",
|
||||||
|
AuthReqKrb5: "krb5",
|
||||||
|
AuthReqPassword: "password",
|
||||||
|
AuthReqCrypt: "crypt",
|
||||||
|
AuthReqMD5: "md5",
|
||||||
|
AuthReqGSS: "GDD",
|
||||||
|
AuthReqGSSCont: "GSSCont",
|
||||||
|
AuthReqSSPI: "SSPI",
|
||||||
|
AuthReqSASL: "SASL",
|
||||||
|
AuthReqSASLCont: "SASLCont",
|
||||||
|
AuthReqSASLFin: "SASLFin",
|
||||||
|
}[a]
|
||||||
|
if !ok {
|
||||||
|
s = "<unknown>"
|
||||||
|
}
|
||||||
|
return s + " (" + strconv.Itoa(int(a)) + ")"
|
||||||
|
}
|
||||||
7
vendor/github.com/lib/pq/internal/proto/sz_32.go
generated
vendored
Normal file
7
vendor/github.com/lib/pq/internal/proto/sz_32.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build 386 || arm || mips || mipsle
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const MaxUint32 = math.MaxInt
|
||||||
7
vendor/github.com/lib/pq/internal/proto/sz_64.go
generated
vendored
Normal file
7
vendor/github.com/lib/pq/internal/proto/sz_64.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build !386 && !arm && !mips && !mipsle
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const MaxUint32 = math.MaxUint32
|
||||||
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
// NewGSSFunc creates a GSS authentication provider, for use with
|
||||||
|
// RegisterGSSProvider.
|
||||||
|
type NewGSSFunc func() (GSS, error)
|
||||||
|
|
||||||
|
var newGss NewGSSFunc
|
||||||
|
|
||||||
|
// RegisterGSSProvider registers a GSS authentication provider. For example, if
|
||||||
|
// you need to use Kerberos to authenticate with your server, add this to your
|
||||||
|
// main package:
|
||||||
|
//
|
||||||
|
// import "github.com/lib/pq/auth/kerberos"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// pq.RegisterGSSProvider(func() (pq.GSS, error) { return kerberos.NewGSS() })
|
||||||
|
// }
|
||||||
|
func RegisterGSSProvider(newGssArg NewGSSFunc) {
|
||||||
|
newGss = newGssArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// GSS provides GSSAPI authentication (e.g., Kerberos).
|
||||||
|
type GSS interface {
|
||||||
|
GetInitToken(host string, service string) ([]byte, error)
|
||||||
|
GetInitTokenFromSpn(spn string) ([]byte, error)
|
||||||
|
Continue(inToken []byte) (done bool, outToken []byte, err error)
|
||||||
|
}
|
||||||
69
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
69
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoticeHandler returns the notice handler on the given connection, if any. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. This is rarely used
|
||||||
|
// directly, use [ConnectorNoticeHandler] and [ConnectorWithNoticeHandler] instead.
|
||||||
|
func NoticeHandler(c driver.Conn) func(*Error) {
|
||||||
|
return c.(*conn).noticeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoticeHandler sets the given notice handler on the given connection. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. A nil handler may be used
|
||||||
|
// to unset it. This is rarely used directly, use ConnectorNoticeHandler and
|
||||||
|
// [ConnectorWithNoticeHandler] instead.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNoticeHandler(c driver.Conn, handler func(*Error)) {
|
||||||
|
c.(*conn).noticeHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoticeHandlerConnector wraps a regular connector and sets a notice handler
|
||||||
|
// on it.
|
||||||
|
type NoticeHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
noticeHandler func(*Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notice handler.
|
||||||
|
func (n *NoticeHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNoticeHandler(c, n.noticeHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNoticeHandler returns the currently set notice handler, if any. If
|
||||||
|
// the given connector is not a result of [ConnectorWithNoticeHandler], nil is
|
||||||
|
// returned.
|
||||||
|
func ConnectorNoticeHandler(c driver.Connector) func(*Error) {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
return c.noticeHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNoticeHandler creates or sets the given handler for the given
|
||||||
|
// connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the notice
|
||||||
|
// handler. A nil notice handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNoticeHandler(c driver.Connector, handler func(*Error)) *NoticeHandlerConnector {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
c.noticeHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NoticeHandlerConnector{Connector: c, noticeHandler: handler}
|
||||||
|
}
|
||||||
834
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
834
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
@ -0,0 +1,834 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notification represents a single notification from the database.
|
||||||
|
type Notification struct {
|
||||||
|
BePid int // Process ID (PID) of the notifying postgres backend.
|
||||||
|
Channel string // Name of the channel the notification was sent on.
|
||||||
|
Extra string // Payload, or the empty string if unspecified.
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvNotification(r *readBuf) *Notification {
|
||||||
|
bePid := r.int32()
|
||||||
|
channel := r.string()
|
||||||
|
extra := r.string()
|
||||||
|
return &Notification{bePid, channel, extra}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNotificationHandler sets the given notification handler on the given
|
||||||
|
// connection. A runtime panic occurs if c is not a pq connection. A nil handler
|
||||||
|
// may be used to unset it.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNotificationHandler(c driver.Conn, handler func(*Notification)) {
|
||||||
|
c.(*conn).notificationHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationHandlerConnector wraps a regular connector and sets a
|
||||||
|
// notification handler on it.
|
||||||
|
type NotificationHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
notificationHandler func(*Notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notification handler.
|
||||||
|
func (n *NotificationHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNotificationHandler(c, n.notificationHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNotificationHandler returns the currently set notification handler,
|
||||||
|
// if any. If the given connector is not a result of
|
||||||
|
// [ConnectorWithNotificationHandler], nil is returned.
|
||||||
|
func ConnectorNotificationHandler(c driver.Connector) func(*Notification) {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
return c.notificationHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNotificationHandler creates or sets the given handler for the
|
||||||
|
// given connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the
|
||||||
|
// notification handler. A nil notification handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNotificationHandler(c driver.Connector, handler func(*Notification)) *NotificationHandlerConnector {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
c.notificationHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NotificationHandlerConnector{Connector: c, notificationHandler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
connStateIdle int32 = iota
|
||||||
|
connStateExpectResponse
|
||||||
|
connStateExpectReadyForQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
typ proto.ResponseCode
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
|
||||||
|
|
||||||
|
// ListenerConn is a low-level interface for waiting for notifications. You
|
||||||
|
// should use [Listener] instead.
|
||||||
|
type ListenerConn struct {
|
||||||
|
connectionLock sync.Mutex // guards cn and err
|
||||||
|
senderLock sync.Mutex // the sending goroutine will be holding this lock
|
||||||
|
cn *conn
|
||||||
|
err error
|
||||||
|
connState int32
|
||||||
|
notificationChan chan<- *Notification
|
||||||
|
replyChan chan message
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListenerConn creates a new ListenerConn. Use NewListener instead.
|
||||||
|
func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
return newDialListenerConn(defaultDialer{}, name, notificationChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
cn, err := DialOpen(d, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &ListenerConn{
|
||||||
|
cn: cn.(*conn),
|
||||||
|
notificationChan: c,
|
||||||
|
connState: connStateIdle,
|
||||||
|
replyChan: make(chan message, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.listenerConnMain()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only allow one goroutine at a time to be running a query on the
|
||||||
|
// connection for various reasons, so the goroutine sending on the connection
|
||||||
|
// must be holding senderLock.
|
||||||
|
//
|
||||||
|
// Returns an error if an unrecoverable error has occurred and the ListenerConn
|
||||||
|
// should be abandoned.
|
||||||
|
func (l *ListenerConn) acquireSenderLock() error {
|
||||||
|
// we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery
|
||||||
|
l.senderLock.Lock()
|
||||||
|
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerConn) releaseSenderLock() {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setState advances the protocol state to newState. Returns false if moving
|
||||||
|
// to that state from the current state is not allowed.
|
||||||
|
func (l *ListenerConn) setState(newState int32) bool {
|
||||||
|
var expectedState int32
|
||||||
|
|
||||||
|
switch newState {
|
||||||
|
case connStateIdle:
|
||||||
|
expectedState = connStateExpectReadyForQuery
|
||||||
|
case connStateExpectResponse:
|
||||||
|
expectedState = connStateIdle
|
||||||
|
case connStateExpectReadyForQuery:
|
||||||
|
expectedState = connStateExpectResponse
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
|
||||||
|
}
|
||||||
|
|
||||||
|
return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic is here: receive messages from the postgres backend, forward
|
||||||
|
// notifications and query replies and keep the internal state in sync with the
|
||||||
|
// protocol state. Returns when the connection has been lost, is about to go
|
||||||
|
// away or should be discarded because we couldn't agree on the state with the
|
||||||
|
// server backend.
|
||||||
|
func (l *ListenerConn) listenerConnLoop() (err error) {
|
||||||
|
r := &readBuf{}
|
||||||
|
for {
|
||||||
|
t, err := l.cn.recvMessage(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case proto.NotificationResponse:
|
||||||
|
// recvNotification copies all the data so we don't need to worry
|
||||||
|
// about the scratch buffer being overwritten.
|
||||||
|
l.notificationChan <- recvNotification(r)
|
||||||
|
|
||||||
|
case proto.RowDescription, proto.DataRow:
|
||||||
|
// only used by tests; ignore
|
||||||
|
|
||||||
|
case proto.ErrorResponse:
|
||||||
|
// We might receive an ErrorResponse even when not in a query; it
|
||||||
|
// is expected that the server will close the connection after
|
||||||
|
// that, but we should make sure that the error we display is the
|
||||||
|
// one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
return parseError(r, "")
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, parseError(r, "")}
|
||||||
|
|
||||||
|
case proto.CommandComplete, proto.EmptyQueryResponse:
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected CommandComplete")
|
||||||
|
}
|
||||||
|
// ExecSimpleQuery doesn't need to know about this message
|
||||||
|
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
if !l.setState(connStateIdle) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected ReadyForQuery")
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, nil}
|
||||||
|
|
||||||
|
case proto.ParameterStatus:
|
||||||
|
// ignore
|
||||||
|
case proto.NoticeResponse:
|
||||||
|
if n := l.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(r, ""))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the main routine for the goroutine receiving on the database
|
||||||
|
// connection. Most of the main logic is in listenerConnLoop.
|
||||||
|
func (l *ListenerConn) listenerConnMain() {
|
||||||
|
err := l.listenerConnLoop()
|
||||||
|
|
||||||
|
// listenerConnLoop terminated; we're done, but we still have to clean up.
|
||||||
|
// Make sure nobody tries to start any new queries by making sure the err
|
||||||
|
// pointer is set. It is important that we do not overwrite its value; a
|
||||||
|
// connection could be closed by either this goroutine or one sending on the
|
||||||
|
// connection – whoever closes the connection is assumed to have the more
|
||||||
|
// meaningful error message (as the other one will probably get
|
||||||
|
// net.errClosed), so that goroutine sets the error we expose while the
|
||||||
|
// other error is discarded. If the connection is lost while two goroutines
|
||||||
|
// are operating on the socket, it probably doesn't matter which error we
|
||||||
|
// expose so we don't try to do anything more complex.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
_ = l.cn.Close()
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
|
||||||
|
// There might be a query in-flight; make sure nobody's waiting for a
|
||||||
|
// response to it, since there's not going to be one.
|
||||||
|
close(l.replyChan)
|
||||||
|
|
||||||
|
// let the listener know we're done
|
||||||
|
close(l.notificationChan)
|
||||||
|
|
||||||
|
// this ListenerConn is done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen sends a LISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten sends an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll sends an `UNLISTEN *` query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) UnlistenAll() (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN *")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil error means the
|
||||||
|
// connection has failed and should be abandoned.
|
||||||
|
func (l *ListenerConn) Ping() error {
|
||||||
|
sent, err := l.ExecSimpleQuery("")
|
||||||
|
if !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil { // shouldn't happen
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send a query on the connection. Returns an error if sending the
|
||||||
|
// query failed, and the caller should initiate closure of this connection. The
|
||||||
|
// caller must be holding senderLock (see acquireSenderLock and
|
||||||
|
// releaseSenderLock).
|
||||||
|
func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
|
||||||
|
// Must set connection state before sending the query
|
||||||
|
if !l.setState(connStateExpectResponse) {
|
||||||
|
return errors.New("pq: two queries running at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use l.cn.writeBuf here because it uses the scratch buffer which
|
||||||
|
// might get overwritten by listenerConnLoop.
|
||||||
|
b := &writeBuf{
|
||||||
|
buf: []byte("Q\x00\x00\x00\x00"),
|
||||||
|
pos: 1,
|
||||||
|
}
|
||||||
|
b.string(q)
|
||||||
|
return l.cn.send(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecSimpleQuery executes a "simple query" (i.e. one with no bindable
|
||||||
|
// parameters) on the connection. The possible return values are:
|
||||||
|
// 1. "executed" is true; the query was executed to completion on the database
|
||||||
|
// server. If the query failed, err will be set to the error returned by the
|
||||||
|
// database, otherwise err will be nil.
|
||||||
|
// 2. If "executed" is false, the query could not be executed on the remote
|
||||||
|
// server. err will be non-nil.
|
||||||
|
//
|
||||||
|
// After a call to ExecSimpleQuery has returned an executed=false value, the
|
||||||
|
// connection has either been closed or will be closed shortly thereafter, and
|
||||||
|
// all subsequently executed queries will return an error.
|
||||||
|
func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
|
||||||
|
if err = l.acquireSenderLock(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer l.releaseSenderLock()
|
||||||
|
|
||||||
|
err = l.sendSimpleQuery(q)
|
||||||
|
if err != nil {
|
||||||
|
// We can't know what state the protocol is in, so we need to abandon
|
||||||
|
// this connection.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
// Set the error pointer if it hasn't been set already; see
|
||||||
|
// listenerConnMain.
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
_ = l.cn.c.Close()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we just wait for a reply..
|
||||||
|
for {
|
||||||
|
m, ok := <-l.replyChan
|
||||||
|
if !ok {
|
||||||
|
// We lost the connection to server, don't bother waiting for a
|
||||||
|
// a response. err should have been set already.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch m.typ {
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
// sanity check
|
||||||
|
if m.err != nil {
|
||||||
|
panic("m.err != nil")
|
||||||
|
}
|
||||||
|
// done; err might or might not be set
|
||||||
|
return true, err
|
||||||
|
|
||||||
|
case proto.ErrorResponse:
|
||||||
|
// sanity check
|
||||||
|
if m.err == nil {
|
||||||
|
panic("m.err == nil")
|
||||||
|
}
|
||||||
|
// server responded with an error; ReadyForQuery to follow
|
||||||
|
err = m.err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (l *ListenerConn) Close() error {
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err != nil {
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return errListenerConnClosed
|
||||||
|
}
|
||||||
|
l.err = errListenerConnClosed
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
// We can't send anything on the connection without holding senderLock.
|
||||||
|
// Simply close the net.Conn to wake up everyone operating on it.
|
||||||
|
return l.cn.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the reason the connection was closed. It is not safe to call
|
||||||
|
// this function until l.Notify has been closed.
|
||||||
|
func (l *ListenerConn) Err() error {
|
||||||
|
return l.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrChannelAlreadyOpen is returned from Listen when a channel is already
|
||||||
|
// open.
|
||||||
|
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||||
|
|
||||||
|
// ErrChannelNotOpen is returned from Unlisten when a channel is not open.
|
||||||
|
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||||
|
|
||||||
|
// ListenerEventType is an enumeration of listener event types.
|
||||||
|
type ListenerEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ListenerEventConnected is emitted only when the database connection has
|
||||||
|
// been initially initialized. The err argument of the callback will always
|
||||||
|
// be nil.
|
||||||
|
ListenerEventConnected ListenerEventType = iota
|
||||||
|
|
||||||
|
// ListenerEventDisconnected is emitted after a database connection has been
|
||||||
|
// lost, either because of an error or because Close has been called. The
|
||||||
|
// err argument will be set to the reason the database connection was lost.
|
||||||
|
ListenerEventDisconnected
|
||||||
|
|
||||||
|
// ListenerEventReconnected is emitted after a database connection has been
|
||||||
|
// re-established after connection loss. The err argument of the callback
|
||||||
|
// will always be nil. After this event has been emitted, a nil
|
||||||
|
// pq.Notification is sent on the Listener.Notify channel.
|
||||||
|
ListenerEventReconnected
|
||||||
|
|
||||||
|
// ListenerEventConnectionAttemptFailed is emitted after a connection to the
|
||||||
|
// database was attempted, but failed. The err argument will be set to an
|
||||||
|
// error describing why the connection attempt did not succeed.
|
||||||
|
ListenerEventConnectionAttemptFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventCallbackType is the event callback type. See also ListenerEventType
|
||||||
|
// constants' documentation.
|
||||||
|
type EventCallbackType func(event ListenerEventType, err error)
|
||||||
|
|
||||||
|
func (l ListenerEventType) String() string {
|
||||||
|
return map[ListenerEventType]string{
|
||||||
|
ListenerEventConnected: "connected",
|
||||||
|
ListenerEventDisconnected: "disconnected",
|
||||||
|
ListenerEventReconnected: "reconnected",
|
||||||
|
ListenerEventConnectionAttemptFailed: "connectionAttemptFailed",
|
||||||
|
}[l]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener provides an interface for listening to notifications from a
|
||||||
|
// PostgreSQL database. For general usage information, see section
|
||||||
|
// "Notifications".
|
||||||
|
//
|
||||||
|
// Listener can safely be used from concurrently running goroutines.
|
||||||
|
type Listener struct {
|
||||||
|
// Channel for receiving notifications from the database. In some cases a
|
||||||
|
// nil value will be sent. See section "Notifications" above.
|
||||||
|
Notify chan *Notification
|
||||||
|
|
||||||
|
dsn string
|
||||||
|
minReconnectInterval time.Duration
|
||||||
|
maxReconnectInterval time.Duration
|
||||||
|
dialer Dialer
|
||||||
|
eventCallback EventCallbackType
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
isClosed bool
|
||||||
|
reconnectCond *sync.Cond
|
||||||
|
cn *ListenerConn
|
||||||
|
connNotificationChan <-chan *Notification
|
||||||
|
channels map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
|
||||||
|
//
|
||||||
|
// name should be set to a connection string to be used to establish the
|
||||||
|
// database connection (see section "Connection String Parameters" above).
|
||||||
|
//
|
||||||
|
// minReconnect controls the duration to wait before trying to re-establish the
|
||||||
|
// database connection after connection loss. After each consecutive failure
|
||||||
|
// this interval is doubled, until maxReconnect is reached. Successfully
|
||||||
|
// completing the connection establishment procedure resets the interval back to
|
||||||
|
// minReconnect.
|
||||||
|
//
|
||||||
|
// The last parameter cb can be set to a function which will be called by the
|
||||||
|
// Listener when the state of the underlying database connection changes. This
|
||||||
|
// callback will be called by the goroutine which dispatches the notifications
|
||||||
|
// over the Notify channel, so you should try to avoid doing potentially
|
||||||
|
// time-consuming operations from the callback.
|
||||||
|
func NewListener(dsn string, minReconnect, maxReconnect time.Duration, cb EventCallbackType) *Listener {
|
||||||
|
return NewDialListener(defaultDialer{}, dsn, minReconnect, maxReconnect, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialListener is like NewListener but it takes a Dialer.
|
||||||
|
func NewDialListener(d Dialer, dsn string, minReconnect, maxReconnect time.Duration, cb EventCallbackType) *Listener {
|
||||||
|
l := &Listener{
|
||||||
|
dsn: dsn,
|
||||||
|
minReconnectInterval: minReconnect,
|
||||||
|
maxReconnectInterval: maxReconnect,
|
||||||
|
dialer: d,
|
||||||
|
eventCallback: cb,
|
||||||
|
channels: make(map[string]struct{}),
|
||||||
|
Notify: make(chan *Notification, 32),
|
||||||
|
}
|
||||||
|
l.reconnectCond = sync.NewCond(&l.lock)
|
||||||
|
go l.listenerMain()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationChannel returns the notification channel for this listener. This
|
||||||
|
// is the same channel as Notify, and will not be recreated during the life time
|
||||||
|
// of the Listener.
|
||||||
|
func (l *Listener) NotificationChannel() <-chan *Notification {
|
||||||
|
return l.Notify
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen starts listening for notifications on a channel. Calls to this
|
||||||
|
// function will block until an acknowledgement has been received from the
|
||||||
|
// server. Note that Listener automatically re-establishes the connection after
|
||||||
|
// connection loss, so this function may block indefinitely if the connection
|
||||||
|
// can not be re-established.
|
||||||
|
//
|
||||||
|
// Listen will only fail in three conditions:
|
||||||
|
// 1. The channel is already open. The returned error will be
|
||||||
|
// [ErrChannelAlreadyOpen].
|
||||||
|
// 2. The query was executed on the remote server, but PostgreSQL returned an
|
||||||
|
// error message in response to the query. The returned error will be a
|
||||||
|
// [pq.Error] containing the information the server supplied.
|
||||||
|
// 3. Close is called on the Listener before the request could be completed.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Listen(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server allows you to issue a LISTEN on a channel which is already
|
||||||
|
// open, but it seems useful to be able to detect this case to spot for
|
||||||
|
// mistakes in application logic. If the application genuinely does't care,
|
||||||
|
// it can check the exported error and ignore it.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if exists {
|
||||||
|
return ErrChannelAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// If resp is true but error is set then the query was executed on the
|
||||||
|
// remote server but resulted in an error. This should be relatively
|
||||||
|
// rare, so it's fine if we just pass the error to our caller.
|
||||||
|
// If resp is false then we could not complete the query on the remote
|
||||||
|
// server and our underlying connection is about to go away, so we only
|
||||||
|
// add relname to l.channels, and wait for resync() to take care of the
|
||||||
|
// rest.
|
||||||
|
resp, err := l.cn.Listen(channel)
|
||||||
|
if resp && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.channels[channel] = struct{}{}
|
||||||
|
for l.cn == nil {
|
||||||
|
l.reconnectCond.Wait()
|
||||||
|
// we let go of the mutex for a while
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten removes a channel from the Listener's channel list. Returns
|
||||||
|
// ErrChannelNotOpen if the Listener is not listening on the specified channel.
|
||||||
|
// Returns immediately with no error if there is no connection. Note that you
|
||||||
|
// might still get notifications for this channel even after Unlisten has
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Unlisten(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly to LISTEN, this is not an error in Postgres, but it seems
|
||||||
|
// useful to distinguish from the normal conditions.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if !exists {
|
||||||
|
return ErrChannelNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment there), the caller should only be
|
||||||
|
// bothered with an error if it came from the backend as a response to
|
||||||
|
// our query.
|
||||||
|
resp, err := l.cn.Unlisten(channel)
|
||||||
|
if resp && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
delete(l.channels, channel)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll removes all channels from the Listener's channel list. Returns
|
||||||
|
// immediately with no error if there is no connection. Note that you might
|
||||||
|
// still get notifications for any of the deleted channels even after
|
||||||
|
// UnlistenAll has returned.
|
||||||
|
func (l *Listener) UnlistenAll() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.UnlistenAll()
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
l.channels = make(map[string]struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil return value means
|
||||||
|
// that there is no active connection.
|
||||||
|
func (l *Listener) Ping() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
if l.cn == nil {
|
||||||
|
return errors.New("no connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.cn.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after losing the server connection. Returns l.cn.Err(), which should
|
||||||
|
// have the reason the connection was lost.
|
||||||
|
func (l *Listener) disconnectCleanup() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
// sanity check; can't look at Err() until the channel has been closed
|
||||||
|
select {
|
||||||
|
case _, ok := <-l.connNotificationChan:
|
||||||
|
if ok {
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.cn.Err()
|
||||||
|
_ = l.cn.Close()
|
||||||
|
l.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the list of channels we want to be listening on with the server
|
||||||
|
// after the connection has been established.
|
||||||
|
func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
|
||||||
|
doneChan := make(chan error)
|
||||||
|
go func(notificationChan <-chan *Notification) {
|
||||||
|
for channel := range l.channels {
|
||||||
|
// If we got a response, return that error to our caller as it's
|
||||||
|
// going to be more descriptive than cn.Err().
|
||||||
|
gotResponse, err := cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
doneChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't reach the server, wait for notificationChan to
|
||||||
|
// close and then return the error message from the connection, as
|
||||||
|
// per ListenerConn's interface.
|
||||||
|
if err != nil {
|
||||||
|
for range notificationChan {
|
||||||
|
}
|
||||||
|
doneChan <- cn.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneChan <- nil
|
||||||
|
}(notificationChan)
|
||||||
|
|
||||||
|
// Ignore notifications while synchronization is going on to avoid
|
||||||
|
// deadlocks. We have to send a nil notification over Notify anyway as we
|
||||||
|
// can't possibly know which notifications (if any) were lost while the
|
||||||
|
// connection was down, so there's no reason to try and process these
|
||||||
|
// messages at all.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-notificationChan:
|
||||||
|
if !ok {
|
||||||
|
notificationChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case err := <-doneChan:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should NOT be holding l.lock
|
||||||
|
func (l *Listener) closed() bool {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
return l.isClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) connect() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationChan := make(chan *Notification, 32)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
l.cn, err = newDialListenerConn(l.dialer, l.dsn, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.resync(l.cn, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
_ = l.cn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.connNotificationChan = notificationChan
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close disconnects the Listener from the database and shuts it down.
|
||||||
|
// Subsequent calls to its methods will return an error. Close returns an error
|
||||||
|
// if the connection has already been closed.
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
_ = l.cn.Close()
|
||||||
|
}
|
||||||
|
l.isClosed = true
|
||||||
|
|
||||||
|
// Unblock calls to Listen()
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) emitEvent(event ListenerEventType, err error) {
|
||||||
|
if l.eventCallback != nil {
|
||||||
|
l.eventCallback(event, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic here: maintain a connection to the server when possible, wait
|
||||||
|
// for notifications and emit events.
|
||||||
|
func (l *Listener) listenerConnLoop() {
|
||||||
|
var (
|
||||||
|
nextReconnect time.Time
|
||||||
|
reconnectInterval = l.minReconnectInterval
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
err := l.connect()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.emitEvent(ListenerEventConnectionAttemptFailed, err)
|
||||||
|
time.Sleep(reconnectInterval)
|
||||||
|
reconnectInterval *= 2
|
||||||
|
if reconnectInterval > l.maxReconnectInterval {
|
||||||
|
reconnectInterval = l.maxReconnectInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextReconnect.IsZero() {
|
||||||
|
l.emitEvent(ListenerEventConnected, nil)
|
||||||
|
} else {
|
||||||
|
l.emitEvent(ListenerEventReconnected, nil)
|
||||||
|
l.Notify <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectInterval = l.minReconnectInterval
|
||||||
|
nextReconnect = time.Now().Add(reconnectInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
notification, ok := <-l.connNotificationChan
|
||||||
|
if !ok { // lost connection, loop again
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.Notify <- notification
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.disconnectCleanup()
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventDisconnected, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Until(nextReconnect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) listenerMain() {
|
||||||
|
l.listenerConnLoop()
|
||||||
|
close(l.Notify)
|
||||||
|
}
|
||||||
7
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
7
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:generate go run ./gen.go
|
||||||
|
|
||||||
|
// Package oid contains OID constants as defined by the Postgres server.
|
||||||
|
package oid
|
||||||
|
|
||||||
|
// Oid is a Postgres Object ID.
|
||||||
|
type Oid uint32
|
||||||
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
// Code generated by gen.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
package oid
|
||||||
|
|
||||||
|
const (
|
||||||
|
T_bool Oid = 16
|
||||||
|
T_bytea Oid = 17
|
||||||
|
T_char Oid = 18
|
||||||
|
T_name Oid = 19
|
||||||
|
T_int8 Oid = 20
|
||||||
|
T_int2 Oid = 21
|
||||||
|
T_int2vector Oid = 22
|
||||||
|
T_int4 Oid = 23
|
||||||
|
T_regproc Oid = 24
|
||||||
|
T_text Oid = 25
|
||||||
|
T_oid Oid = 26
|
||||||
|
T_tid Oid = 27
|
||||||
|
T_xid Oid = 28
|
||||||
|
T_cid Oid = 29
|
||||||
|
T_oidvector Oid = 30
|
||||||
|
T_pg_ddl_command Oid = 32
|
||||||
|
T_pg_type Oid = 71
|
||||||
|
T_pg_attribute Oid = 75
|
||||||
|
T_pg_proc Oid = 81
|
||||||
|
T_pg_class Oid = 83
|
||||||
|
T_json Oid = 114
|
||||||
|
T_xml Oid = 142
|
||||||
|
T__xml Oid = 143
|
||||||
|
T_pg_node_tree Oid = 194
|
||||||
|
T__json Oid = 199
|
||||||
|
T_smgr Oid = 210
|
||||||
|
T_index_am_handler Oid = 325
|
||||||
|
T_point Oid = 600
|
||||||
|
T_lseg Oid = 601
|
||||||
|
T_path Oid = 602
|
||||||
|
T_box Oid = 603
|
||||||
|
T_polygon Oid = 604
|
||||||
|
T_line Oid = 628
|
||||||
|
T__line Oid = 629
|
||||||
|
T_cidr Oid = 650
|
||||||
|
T__cidr Oid = 651
|
||||||
|
T_float4 Oid = 700
|
||||||
|
T_float8 Oid = 701
|
||||||
|
T_abstime Oid = 702
|
||||||
|
T_reltime Oid = 703
|
||||||
|
T_tinterval Oid = 704
|
||||||
|
T_unknown Oid = 705
|
||||||
|
T_circle Oid = 718
|
||||||
|
T__circle Oid = 719
|
||||||
|
T_money Oid = 790
|
||||||
|
T__money Oid = 791
|
||||||
|
T_macaddr Oid = 829
|
||||||
|
T_inet Oid = 869
|
||||||
|
T__bool Oid = 1000
|
||||||
|
T__bytea Oid = 1001
|
||||||
|
T__char Oid = 1002
|
||||||
|
T__name Oid = 1003
|
||||||
|
T__int2 Oid = 1005
|
||||||
|
T__int2vector Oid = 1006
|
||||||
|
T__int4 Oid = 1007
|
||||||
|
T__regproc Oid = 1008
|
||||||
|
T__text Oid = 1009
|
||||||
|
T__tid Oid = 1010
|
||||||
|
T__xid Oid = 1011
|
||||||
|
T__cid Oid = 1012
|
||||||
|
T__oidvector Oid = 1013
|
||||||
|
T__bpchar Oid = 1014
|
||||||
|
T__varchar Oid = 1015
|
||||||
|
T__int8 Oid = 1016
|
||||||
|
T__point Oid = 1017
|
||||||
|
T__lseg Oid = 1018
|
||||||
|
T__path Oid = 1019
|
||||||
|
T__box Oid = 1020
|
||||||
|
T__float4 Oid = 1021
|
||||||
|
T__float8 Oid = 1022
|
||||||
|
T__abstime Oid = 1023
|
||||||
|
T__reltime Oid = 1024
|
||||||
|
T__tinterval Oid = 1025
|
||||||
|
T__polygon Oid = 1027
|
||||||
|
T__oid Oid = 1028
|
||||||
|
T_aclitem Oid = 1033
|
||||||
|
T__aclitem Oid = 1034
|
||||||
|
T__macaddr Oid = 1040
|
||||||
|
T__inet Oid = 1041
|
||||||
|
T_bpchar Oid = 1042
|
||||||
|
T_varchar Oid = 1043
|
||||||
|
T_date Oid = 1082
|
||||||
|
T_time Oid = 1083
|
||||||
|
T_timestamp Oid = 1114
|
||||||
|
T__timestamp Oid = 1115
|
||||||
|
T__date Oid = 1182
|
||||||
|
T__time Oid = 1183
|
||||||
|
T_timestamptz Oid = 1184
|
||||||
|
T__timestamptz Oid = 1185
|
||||||
|
T_interval Oid = 1186
|
||||||
|
T__interval Oid = 1187
|
||||||
|
T__numeric Oid = 1231
|
||||||
|
T_pg_database Oid = 1248
|
||||||
|
T__cstring Oid = 1263
|
||||||
|
T_timetz Oid = 1266
|
||||||
|
T__timetz Oid = 1270
|
||||||
|
T_bit Oid = 1560
|
||||||
|
T__bit Oid = 1561
|
||||||
|
T_varbit Oid = 1562
|
||||||
|
T__varbit Oid = 1563
|
||||||
|
T_numeric Oid = 1700
|
||||||
|
T_refcursor Oid = 1790
|
||||||
|
T__refcursor Oid = 2201
|
||||||
|
T_regprocedure Oid = 2202
|
||||||
|
T_regoper Oid = 2203
|
||||||
|
T_regoperator Oid = 2204
|
||||||
|
T_regclass Oid = 2205
|
||||||
|
T_regtype Oid = 2206
|
||||||
|
T__regprocedure Oid = 2207
|
||||||
|
T__regoper Oid = 2208
|
||||||
|
T__regoperator Oid = 2209
|
||||||
|
T__regclass Oid = 2210
|
||||||
|
T__regtype Oid = 2211
|
||||||
|
T_record Oid = 2249
|
||||||
|
T_cstring Oid = 2275
|
||||||
|
T_any Oid = 2276
|
||||||
|
T_anyarray Oid = 2277
|
||||||
|
T_void Oid = 2278
|
||||||
|
T_trigger Oid = 2279
|
||||||
|
T_language_handler Oid = 2280
|
||||||
|
T_internal Oid = 2281
|
||||||
|
T_opaque Oid = 2282
|
||||||
|
T_anyelement Oid = 2283
|
||||||
|
T__record Oid = 2287
|
||||||
|
T_anynonarray Oid = 2776
|
||||||
|
T_pg_authid Oid = 2842
|
||||||
|
T_pg_auth_members Oid = 2843
|
||||||
|
T__txid_snapshot Oid = 2949
|
||||||
|
T_uuid Oid = 2950
|
||||||
|
T__uuid Oid = 2951
|
||||||
|
T_txid_snapshot Oid = 2970
|
||||||
|
T_fdw_handler Oid = 3115
|
||||||
|
T_pg_lsn Oid = 3220
|
||||||
|
T__pg_lsn Oid = 3221
|
||||||
|
T_tsm_handler Oid = 3310
|
||||||
|
T_anyenum Oid = 3500
|
||||||
|
T_tsvector Oid = 3614
|
||||||
|
T_tsquery Oid = 3615
|
||||||
|
T_gtsvector Oid = 3642
|
||||||
|
T__tsvector Oid = 3643
|
||||||
|
T__gtsvector Oid = 3644
|
||||||
|
T__tsquery Oid = 3645
|
||||||
|
T_regconfig Oid = 3734
|
||||||
|
T__regconfig Oid = 3735
|
||||||
|
T_regdictionary Oid = 3769
|
||||||
|
T__regdictionary Oid = 3770
|
||||||
|
T_jsonb Oid = 3802
|
||||||
|
T__jsonb Oid = 3807
|
||||||
|
T_anyrange Oid = 3831
|
||||||
|
T_event_trigger Oid = 3838
|
||||||
|
T_int4range Oid = 3904
|
||||||
|
T__int4range Oid = 3905
|
||||||
|
T_numrange Oid = 3906
|
||||||
|
T__numrange Oid = 3907
|
||||||
|
T_tsrange Oid = 3908
|
||||||
|
T__tsrange Oid = 3909
|
||||||
|
T_tstzrange Oid = 3910
|
||||||
|
T__tstzrange Oid = 3911
|
||||||
|
T_daterange Oid = 3912
|
||||||
|
T__daterange Oid = 3913
|
||||||
|
T_int8range Oid = 3926
|
||||||
|
T__int8range Oid = 3927
|
||||||
|
T_pg_shseclabel Oid = 4066
|
||||||
|
T_regnamespace Oid = 4089
|
||||||
|
T__regnamespace Oid = 4090
|
||||||
|
T_regrole Oid = 4096
|
||||||
|
T__regrole Oid = 4097
|
||||||
|
)
|
||||||
|
|
||||||
|
var TypeName = map[Oid]string{
|
||||||
|
T_bool: "BOOL",
|
||||||
|
T_bytea: "BYTEA",
|
||||||
|
T_char: "CHAR",
|
||||||
|
T_name: "NAME",
|
||||||
|
T_int8: "INT8",
|
||||||
|
T_int2: "INT2",
|
||||||
|
T_int2vector: "INT2VECTOR",
|
||||||
|
T_int4: "INT4",
|
||||||
|
T_regproc: "REGPROC",
|
||||||
|
T_text: "TEXT",
|
||||||
|
T_oid: "OID",
|
||||||
|
T_tid: "TID",
|
||||||
|
T_xid: "XID",
|
||||||
|
T_cid: "CID",
|
||||||
|
T_oidvector: "OIDVECTOR",
|
||||||
|
T_pg_ddl_command: "PG_DDL_COMMAND",
|
||||||
|
T_pg_type: "PG_TYPE",
|
||||||
|
T_pg_attribute: "PG_ATTRIBUTE",
|
||||||
|
T_pg_proc: "PG_PROC",
|
||||||
|
T_pg_class: "PG_CLASS",
|
||||||
|
T_json: "JSON",
|
||||||
|
T_xml: "XML",
|
||||||
|
T__xml: "_XML",
|
||||||
|
T_pg_node_tree: "PG_NODE_TREE",
|
||||||
|
T__json: "_JSON",
|
||||||
|
T_smgr: "SMGR",
|
||||||
|
T_index_am_handler: "INDEX_AM_HANDLER",
|
||||||
|
T_point: "POINT",
|
||||||
|
T_lseg: "LSEG",
|
||||||
|
T_path: "PATH",
|
||||||
|
T_box: "BOX",
|
||||||
|
T_polygon: "POLYGON",
|
||||||
|
T_line: "LINE",
|
||||||
|
T__line: "_LINE",
|
||||||
|
T_cidr: "CIDR",
|
||||||
|
T__cidr: "_CIDR",
|
||||||
|
T_float4: "FLOAT4",
|
||||||
|
T_float8: "FLOAT8",
|
||||||
|
T_abstime: "ABSTIME",
|
||||||
|
T_reltime: "RELTIME",
|
||||||
|
T_tinterval: "TINTERVAL",
|
||||||
|
T_unknown: "UNKNOWN",
|
||||||
|
T_circle: "CIRCLE",
|
||||||
|
T__circle: "_CIRCLE",
|
||||||
|
T_money: "MONEY",
|
||||||
|
T__money: "_MONEY",
|
||||||
|
T_macaddr: "MACADDR",
|
||||||
|
T_inet: "INET",
|
||||||
|
T__bool: "_BOOL",
|
||||||
|
T__bytea: "_BYTEA",
|
||||||
|
T__char: "_CHAR",
|
||||||
|
T__name: "_NAME",
|
||||||
|
T__int2: "_INT2",
|
||||||
|
T__int2vector: "_INT2VECTOR",
|
||||||
|
T__int4: "_INT4",
|
||||||
|
T__regproc: "_REGPROC",
|
||||||
|
T__text: "_TEXT",
|
||||||
|
T__tid: "_TID",
|
||||||
|
T__xid: "_XID",
|
||||||
|
T__cid: "_CID",
|
||||||
|
T__oidvector: "_OIDVECTOR",
|
||||||
|
T__bpchar: "_BPCHAR",
|
||||||
|
T__varchar: "_VARCHAR",
|
||||||
|
T__int8: "_INT8",
|
||||||
|
T__point: "_POINT",
|
||||||
|
T__lseg: "_LSEG",
|
||||||
|
T__path: "_PATH",
|
||||||
|
T__box: "_BOX",
|
||||||
|
T__float4: "_FLOAT4",
|
||||||
|
T__float8: "_FLOAT8",
|
||||||
|
T__abstime: "_ABSTIME",
|
||||||
|
T__reltime: "_RELTIME",
|
||||||
|
T__tinterval: "_TINTERVAL",
|
||||||
|
T__polygon: "_POLYGON",
|
||||||
|
T__oid: "_OID",
|
||||||
|
T_aclitem: "ACLITEM",
|
||||||
|
T__aclitem: "_ACLITEM",
|
||||||
|
T__macaddr: "_MACADDR",
|
||||||
|
T__inet: "_INET",
|
||||||
|
T_bpchar: "BPCHAR",
|
||||||
|
T_varchar: "VARCHAR",
|
||||||
|
T_date: "DATE",
|
||||||
|
T_time: "TIME",
|
||||||
|
T_timestamp: "TIMESTAMP",
|
||||||
|
T__timestamp: "_TIMESTAMP",
|
||||||
|
T__date: "_DATE",
|
||||||
|
T__time: "_TIME",
|
||||||
|
T_timestamptz: "TIMESTAMPTZ",
|
||||||
|
T__timestamptz: "_TIMESTAMPTZ",
|
||||||
|
T_interval: "INTERVAL",
|
||||||
|
T__interval: "_INTERVAL",
|
||||||
|
T__numeric: "_NUMERIC",
|
||||||
|
T_pg_database: "PG_DATABASE",
|
||||||
|
T__cstring: "_CSTRING",
|
||||||
|
T_timetz: "TIMETZ",
|
||||||
|
T__timetz: "_TIMETZ",
|
||||||
|
T_bit: "BIT",
|
||||||
|
T__bit: "_BIT",
|
||||||
|
T_varbit: "VARBIT",
|
||||||
|
T__varbit: "_VARBIT",
|
||||||
|
T_numeric: "NUMERIC",
|
||||||
|
T_refcursor: "REFCURSOR",
|
||||||
|
T__refcursor: "_REFCURSOR",
|
||||||
|
T_regprocedure: "REGPROCEDURE",
|
||||||
|
T_regoper: "REGOPER",
|
||||||
|
T_regoperator: "REGOPERATOR",
|
||||||
|
T_regclass: "REGCLASS",
|
||||||
|
T_regtype: "REGTYPE",
|
||||||
|
T__regprocedure: "_REGPROCEDURE",
|
||||||
|
T__regoper: "_REGOPER",
|
||||||
|
T__regoperator: "_REGOPERATOR",
|
||||||
|
T__regclass: "_REGCLASS",
|
||||||
|
T__regtype: "_REGTYPE",
|
||||||
|
T_record: "RECORD",
|
||||||
|
T_cstring: "CSTRING",
|
||||||
|
T_any: "ANY",
|
||||||
|
T_anyarray: "ANYARRAY",
|
||||||
|
T_void: "VOID",
|
||||||
|
T_trigger: "TRIGGER",
|
||||||
|
T_language_handler: "LANGUAGE_HANDLER",
|
||||||
|
T_internal: "INTERNAL",
|
||||||
|
T_opaque: "OPAQUE",
|
||||||
|
T_anyelement: "ANYELEMENT",
|
||||||
|
T__record: "_RECORD",
|
||||||
|
T_anynonarray: "ANYNONARRAY",
|
||||||
|
T_pg_authid: "PG_AUTHID",
|
||||||
|
T_pg_auth_members: "PG_AUTH_MEMBERS",
|
||||||
|
T__txid_snapshot: "_TXID_SNAPSHOT",
|
||||||
|
T_uuid: "UUID",
|
||||||
|
T__uuid: "_UUID",
|
||||||
|
T_txid_snapshot: "TXID_SNAPSHOT",
|
||||||
|
T_fdw_handler: "FDW_HANDLER",
|
||||||
|
T_pg_lsn: "PG_LSN",
|
||||||
|
T__pg_lsn: "_PG_LSN",
|
||||||
|
T_tsm_handler: "TSM_HANDLER",
|
||||||
|
T_anyenum: "ANYENUM",
|
||||||
|
T_tsvector: "TSVECTOR",
|
||||||
|
T_tsquery: "TSQUERY",
|
||||||
|
T_gtsvector: "GTSVECTOR",
|
||||||
|
T__tsvector: "_TSVECTOR",
|
||||||
|
T__gtsvector: "_GTSVECTOR",
|
||||||
|
T__tsquery: "_TSQUERY",
|
||||||
|
T_regconfig: "REGCONFIG",
|
||||||
|
T__regconfig: "_REGCONFIG",
|
||||||
|
T_regdictionary: "REGDICTIONARY",
|
||||||
|
T__regdictionary: "_REGDICTIONARY",
|
||||||
|
T_jsonb: "JSONB",
|
||||||
|
T__jsonb: "_JSONB",
|
||||||
|
T_anyrange: "ANYRANGE",
|
||||||
|
T_event_trigger: "EVENT_TRIGGER",
|
||||||
|
T_int4range: "INT4RANGE",
|
||||||
|
T__int4range: "_INT4RANGE",
|
||||||
|
T_numrange: "NUMRANGE",
|
||||||
|
T__numrange: "_NUMRANGE",
|
||||||
|
T_tsrange: "TSRANGE",
|
||||||
|
T__tsrange: "_TSRANGE",
|
||||||
|
T_tstzrange: "TSTZRANGE",
|
||||||
|
T__tstzrange: "_TSTZRANGE",
|
||||||
|
T_daterange: "DATERANGE",
|
||||||
|
T__daterange: "_DATERANGE",
|
||||||
|
T_int8range: "INT8RANGE",
|
||||||
|
T__int8range: "_INT8RANGE",
|
||||||
|
T_pg_shseclabel: "PG_SHSECLABEL",
|
||||||
|
T_regnamespace: "REGNAMESPACE",
|
||||||
|
T__regnamespace: "_REGNAMESPACE",
|
||||||
|
T_regrole: "REGROLE",
|
||||||
|
T__regrole: "_REGROLE",
|
||||||
|
}
|
||||||
581
vendor/github.com/lib/pq/pqerror/codes.go
generated
vendored
Normal file
581
vendor/github.com/lib/pq/pqerror/codes.go
generated
vendored
Normal file
@ -0,0 +1,581 @@
|
|||||||
|
// Code generated by gen.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
// Last updated for PostgreSQL 18.3
|
||||||
|
|
||||||
|
package pqerror
|
||||||
|
|
||||||
|
var (
|
||||||
|
ClassSuccessfulCompletion = Class("00") // Successful Completion
|
||||||
|
ClassWarning = Class("01") // Warning
|
||||||
|
ClassNoData = Class("02") // No Data (this is also a warning class per the SQL standard)
|
||||||
|
ClassSQLStatementNotYetComplete = Class("03") // SQL Statement Not Yet Complete
|
||||||
|
ClassConnectionException = Class("08") // Connection Exception
|
||||||
|
ClassTriggeredActionException = Class("09") // Triggered Action Exception
|
||||||
|
ClassFeatureNotSupported = Class("0A") // Feature Not Supported
|
||||||
|
ClassInvalidTransactionInitiation = Class("0B") // Invalid Transaction Initiation
|
||||||
|
ClassLocatorException = Class("0F") // Locator Exception
|
||||||
|
ClassInvalidGrantor = Class("0L") // Invalid Grantor
|
||||||
|
ClassInvalidRoleSpecification = Class("0P") // Invalid Role Specification
|
||||||
|
ClassDiagnosticsException = Class("0Z") // Diagnostics Exception
|
||||||
|
ClassCaseNotFound = Class("20") // Case Not Found
|
||||||
|
ClassCardinalityViolation = Class("21") // Cardinality Violation
|
||||||
|
ClassDataException = Class("22") // Data Exception
|
||||||
|
ClassIntegrityConstraintViolation = Class("23") // Integrity Constraint Violation
|
||||||
|
ClassInvalidCursorState = Class("24") // Invalid Cursor State
|
||||||
|
ClassInvalidTransactionState = Class("25") // Invalid Transaction State
|
||||||
|
ClassInvalidSQLStatementName = Class("26") // Invalid SQL Statement Name
|
||||||
|
ClassTriggeredDataChangeViolation = Class("27") // Triggered Data Change Violation
|
||||||
|
ClassInvalidAuthorizationSpecification = Class("28") // Invalid Authorization Specification
|
||||||
|
ClassDependentPrivilegeDescriptorsStillExist = Class("2B") // Dependent Privilege Descriptors Still Exist
|
||||||
|
ClassInvalidTransactionTermination = Class("2D") // Invalid Transaction Termination
|
||||||
|
ClassSQLRoutineException = Class("2F") // SQL Routine Exception
|
||||||
|
ClassInvalidCursorName = Class("34") // Invalid Cursor Name
|
||||||
|
ClassExternalRoutineException = Class("38") // External Routine Exception
|
||||||
|
ClassExternalRoutineInvocationException = Class("39") // External Routine Invocation Exception
|
||||||
|
ClassSavepointException = Class("3B") // Savepoint Exception
|
||||||
|
ClassInvalidCatalogName = Class("3D") // Invalid Catalog Name
|
||||||
|
ClassInvalidSchemaName = Class("3F") // Invalid Schema Name
|
||||||
|
ClassTransactionRollback = Class("40") // Transaction Rollback
|
||||||
|
ClassSyntaxErrorOrAccessRuleViolation = Class("42") // Syntax Error or Access Rule Violation
|
||||||
|
ClassWithCheckOptionViolation = Class("44") // WITH CHECK OPTION Violation
|
||||||
|
ClassInsufficientResources = Class("53") // Insufficient Resources
|
||||||
|
ClassProgramLimitExceeded = Class("54") // Program Limit Exceeded
|
||||||
|
ClassObjectNotInPrerequisiteState = Class("55") // Object Not In Prerequisite State
|
||||||
|
ClassOperatorIntervention = Class("57") // Operator Intervention
|
||||||
|
ClassSystemError = Class("58") // System Error (errors external to PostgreSQL itself)
|
||||||
|
ClassConfigFileError = Class("F0") // Configuration File Error
|
||||||
|
ClassFDWError = Class("HV") // Foreign Data Wrapper Error (SQL/MED)
|
||||||
|
ClassPLpgSQLError = Class("P0") // PL/pgSQL Error
|
||||||
|
ClassInternalError = Class("XX") // Internal Error
|
||||||
|
)
|
||||||
|
|
||||||
|
// A list of all error codes used in PostgreSQL.
|
||||||
|
var (
|
||||||
|
SuccessfulCompletion = Code("00000") // Class 00 - Successful Completion
|
||||||
|
Warning = Code("01000") // Class 01 - Warning
|
||||||
|
WarningDynamicResultSetsReturned = Code("0100C")
|
||||||
|
WarningImplicitZeroBitPadding = Code("01008")
|
||||||
|
WarningNullValueEliminatedInSetFunction = Code("01003")
|
||||||
|
WarningPrivilegeNotGranted = Code("01007")
|
||||||
|
WarningPrivilegeNotRevoked = Code("01006")
|
||||||
|
WarningStringDataRightTruncation = Code("01004")
|
||||||
|
WarningDeprecatedFeature = Code("01P01")
|
||||||
|
NoData = Code("02000") // Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||||
|
NoAdditionalDynamicResultSetsReturned = Code("02001")
|
||||||
|
SQLStatementNotYetComplete = Code("03000") // Class 03 - SQL Statement Not Yet Complete
|
||||||
|
ConnectionException = Code("08000") // Class 08 - Connection Exception
|
||||||
|
ConnectionDoesNotExist = Code("08003")
|
||||||
|
ConnectionFailure = Code("08006")
|
||||||
|
SQLClientUnableToEstablishSQLConnection = Code("08001")
|
||||||
|
SQLServerRejectedEstablishmentOfSQLConnection = Code("08004")
|
||||||
|
TransactionResolutionUnknown = Code("08007")
|
||||||
|
ProtocolViolation = Code("08P01")
|
||||||
|
TriggeredActionException = Code("09000") // Class 09 - Triggered Action Exception
|
||||||
|
FeatureNotSupported = Code("0A000") // Class 0A - Feature Not Supported
|
||||||
|
InvalidTransactionInitiation = Code("0B000") // Class 0B - Invalid Transaction Initiation
|
||||||
|
LocatorException = Code("0F000") // Class 0F - Locator Exception
|
||||||
|
LEInvalidSpecification = Code("0F001")
|
||||||
|
InvalidGrantor = Code("0L000") // Class 0L - Invalid Grantor
|
||||||
|
InvalidGrantOperation = Code("0LP01")
|
||||||
|
InvalidRoleSpecification = Code("0P000") // Class 0P - Invalid Role Specification
|
||||||
|
DiagnosticsException = Code("0Z000") // Class 0Z - Diagnostics Exception
|
||||||
|
StackedDiagnosticsAccessedWithoutActiveHandler = Code("0Z002")
|
||||||
|
InvalidArgumentForXquery = Code("10608")
|
||||||
|
CaseNotFound = Code("20000") // Class 20 - Case Not Found
|
||||||
|
CardinalityViolation = Code("21000") // Class 21 - Cardinality Violation
|
||||||
|
DataException = Code("22000") // Class 22 - Data Exception
|
||||||
|
ArraySubscriptError = Code("2202E")
|
||||||
|
CharacterNotInRepertoire = Code("22021")
|
||||||
|
DatetimeFieldOverflow = Code("22008")
|
||||||
|
DivisionByZero = Code("22012")
|
||||||
|
ErrorInAssignment = Code("22005")
|
||||||
|
EscapeCharacterConflict = Code("2200B")
|
||||||
|
IndicatorOverflow = Code("22022")
|
||||||
|
IntervalFieldOverflow = Code("22015")
|
||||||
|
InvalidArgumentForLog = Code("2201E")
|
||||||
|
InvalidArgumentForNtile = Code("22014")
|
||||||
|
InvalidArgumentForNthValue = Code("22016")
|
||||||
|
InvalidArgumentForPowerFunction = Code("2201F")
|
||||||
|
InvalidArgumentForWidthBucketFunction = Code("2201G")
|
||||||
|
InvalidCharacterValueForCast = Code("22018")
|
||||||
|
InvalidDatetimeFormat = Code("22007")
|
||||||
|
InvalidEscapeCharacter = Code("22019")
|
||||||
|
InvalidEscapeOctet = Code("2200D")
|
||||||
|
InvalidEscapeSequence = Code("22025")
|
||||||
|
NonstandardUseOfEscapeCharacter = Code("22P06")
|
||||||
|
InvalidIndicatorParameterValue = Code("22010")
|
||||||
|
InvalidParameterValue = Code("22023")
|
||||||
|
InvalidPrecedingOrFollowingSize = Code("22013")
|
||||||
|
InvalidRegularExpression = Code("2201B")
|
||||||
|
InvalidRowCountInLimitClause = Code("2201W")
|
||||||
|
InvalidRowCountInResultOffsetClause = Code("2201X")
|
||||||
|
InvalidTablesampleArgument = Code("2202H")
|
||||||
|
InvalidTablesampleRepeat = Code("2202G")
|
||||||
|
InvalidTimeZoneDisplacementValue = Code("22009")
|
||||||
|
InvalidUseOfEscapeCharacter = Code("2200C")
|
||||||
|
MostSpecificTypeMismatch = Code("2200G")
|
||||||
|
NullValueNotAllowed = Code("22004")
|
||||||
|
NullValueNoIndicatorParameter = Code("22002")
|
||||||
|
NumericValueOutOfRange = Code("22003")
|
||||||
|
SequenceGeneratorLimitExceeded = Code("2200H")
|
||||||
|
StringDataLengthMismatch = Code("22026")
|
||||||
|
StringDataRightTruncation = Code("22001")
|
||||||
|
SubstringError = Code("22011")
|
||||||
|
TrimError = Code("22027")
|
||||||
|
UnterminatedCString = Code("22024")
|
||||||
|
ZeroLengthCharacterString = Code("2200F")
|
||||||
|
FloatingPointException = Code("22P01")
|
||||||
|
InvalidTextRepresentation = Code("22P02")
|
||||||
|
InvalidBinaryRepresentation = Code("22P03")
|
||||||
|
BadCopyFileFormat = Code("22P04")
|
||||||
|
UntranslatableCharacter = Code("22P05")
|
||||||
|
NotAnXMLDocument = Code("2200L")
|
||||||
|
InvalidXMLDocument = Code("2200M")
|
||||||
|
InvalidXMLContent = Code("2200N")
|
||||||
|
InvalidXMLComment = Code("2200S")
|
||||||
|
InvalidXMLProcessingInstruction = Code("2200T")
|
||||||
|
DuplicateJSONObjectKeyValue = Code("22030")
|
||||||
|
InvalidArgumentForSQLJSONDatetimeFunction = Code("22031")
|
||||||
|
InvalidJSONText = Code("22032")
|
||||||
|
InvalidSQLJSONSubscript = Code("22033")
|
||||||
|
MoreThanOneSQLJSONItem = Code("22034")
|
||||||
|
NoSQLJSONItem = Code("22035")
|
||||||
|
NonNumericSQLJSONItem = Code("22036")
|
||||||
|
NonUniqueKeysInAJSONObject = Code("22037")
|
||||||
|
SingletonSQLJSONItemRequired = Code("22038")
|
||||||
|
SQLJSONArrayNotFound = Code("22039")
|
||||||
|
SQLJSONMemberNotFound = Code("2203A")
|
||||||
|
SQLJSONNumberNotFound = Code("2203B")
|
||||||
|
SQLJSONObjectNotFound = Code("2203C")
|
||||||
|
TooManyJSONArrayElements = Code("2203D")
|
||||||
|
TooManyJSONObjectMembers = Code("2203E")
|
||||||
|
SQLJSONScalarRequired = Code("2203F")
|
||||||
|
SQLJSONItemCannotBeCastToTargetType = Code("2203G")
|
||||||
|
IntegrityConstraintViolation = Code("23000") // Class 23 - Integrity Constraint Violation
|
||||||
|
RestrictViolation = Code("23001")
|
||||||
|
NotNullViolation = Code("23502")
|
||||||
|
ForeignKeyViolation = Code("23503")
|
||||||
|
UniqueViolation = Code("23505")
|
||||||
|
CheckViolation = Code("23514")
|
||||||
|
ExclusionViolation = Code("23P01")
|
||||||
|
InvalidCursorState = Code("24000") // Class 24 - Invalid Cursor State
|
||||||
|
InvalidTransactionState = Code("25000") // Class 25 - Invalid Transaction State
|
||||||
|
ActiveSQLTransaction = Code("25001")
|
||||||
|
BranchTransactionAlreadyActive = Code("25002")
|
||||||
|
HeldCursorRequiresSameIsolationLevel = Code("25008")
|
||||||
|
InappropriateAccessModeForBranchTransaction = Code("25003")
|
||||||
|
InappropriateIsolationLevelForBranchTransaction = Code("25004")
|
||||||
|
NoActiveSQLTransactionForBranchTransaction = Code("25005")
|
||||||
|
ReadOnlySQLTransaction = Code("25006")
|
||||||
|
SchemaAndDataStatementMixingNotSupported = Code("25007")
|
||||||
|
NoActiveSQLTransaction = Code("25P01")
|
||||||
|
InFailedSQLTransaction = Code("25P02")
|
||||||
|
IdleInTransactionSessionTimeout = Code("25P03")
|
||||||
|
TransactionTimeout = Code("25P04")
|
||||||
|
InvalidSQLStatementName = Code("26000") // Class 26 - Invalid SQL Statement Name
|
||||||
|
TriggeredDataChangeViolation = Code("27000") // Class 27 - Triggered Data Change Violation
|
||||||
|
InvalidAuthorizationSpecification = Code("28000") // Class 28 - Invalid Authorization Specification
|
||||||
|
InvalidPassword = Code("28P01")
|
||||||
|
DependentPrivilegeDescriptorsStillExist = Code("2B000") // Class 2B - Dependent Privilege Descriptors Still Exist
|
||||||
|
DependentObjectsStillExist = Code("2BP01")
|
||||||
|
InvalidTransactionTermination = Code("2D000") // Class 2D - Invalid Transaction Termination
|
||||||
|
SQLRoutineException = Code("2F000") // Class 2F - SQL Routine Exception
|
||||||
|
SREFunctionExecutedNoReturnStatement = Code("2F005")
|
||||||
|
SREModifyingSQLDataNotPermitted = Code("2F002")
|
||||||
|
SREProhibitedSQLStatementAttempted = Code("2F003")
|
||||||
|
SREReadingSQLDataNotPermitted = Code("2F004")
|
||||||
|
InvalidCursorName = Code("34000") // Class 34 - Invalid Cursor Name
|
||||||
|
ExternalRoutineException = Code("38000") // Class 38 - External Routine Exception
|
||||||
|
EREContainingSQLNotPermitted = Code("38001")
|
||||||
|
EREModifyingSQLDataNotPermitted = Code("38002")
|
||||||
|
EREProhibitedSQLStatementAttempted = Code("38003")
|
||||||
|
EREReadingSQLDataNotPermitted = Code("38004")
|
||||||
|
ExternalRoutineInvocationException = Code("39000") // Class 39 - External Routine Invocation Exception
|
||||||
|
ERIEInvalidSQLSTATEReturned = Code("39001")
|
||||||
|
ERIENullValueNotAllowed = Code("39004")
|
||||||
|
ERIETriggerProtocolViolated = Code("39P01")
|
||||||
|
ERIESrfProtocolViolated = Code("39P02")
|
||||||
|
ERIEEventTriggerProtocolViolated = Code("39P03")
|
||||||
|
SavepointException = Code("3B000") // Class 3B - Savepoint Exception
|
||||||
|
SEInvalidSpecification = Code("3B001")
|
||||||
|
InvalidCatalogName = Code("3D000") // Class 3D - Invalid Catalog Name
|
||||||
|
InvalidSchemaName = Code("3F000") // Class 3F - Invalid Schema Name
|
||||||
|
TransactionRollback = Code("40000") // Class 40 - Transaction Rollback
|
||||||
|
TRIntegrityConstraintViolation = Code("40002")
|
||||||
|
TRSerializationFailure = Code("40001")
|
||||||
|
TRStatementCompletionUnknown = Code("40003")
|
||||||
|
TRDeadlockDetected = Code("40P01")
|
||||||
|
SyntaxErrorOrAccessRuleViolation = Code("42000") // Class 42 - Syntax Error or Access Rule Violation
|
||||||
|
SyntaxError = Code("42601")
|
||||||
|
InsufficientPrivilege = Code("42501")
|
||||||
|
CannotCoerce = Code("42846")
|
||||||
|
GroupingError = Code("42803")
|
||||||
|
WindowingError = Code("42P20")
|
||||||
|
InvalidRecursion = Code("42P19")
|
||||||
|
InvalidForeignKey = Code("42830")
|
||||||
|
InvalidName = Code("42602")
|
||||||
|
NameTooLong = Code("42622")
|
||||||
|
ReservedName = Code("42939")
|
||||||
|
DatatypeMismatch = Code("42804")
|
||||||
|
IndeterminateDatatype = Code("42P18")
|
||||||
|
CollationMismatch = Code("42P21")
|
||||||
|
IndeterminateCollation = Code("42P22")
|
||||||
|
WrongObjectType = Code("42809")
|
||||||
|
GeneratedAlways = Code("428C9")
|
||||||
|
UndefinedColumn = Code("42703")
|
||||||
|
UndefinedFunction = Code("42883")
|
||||||
|
UndefinedTable = Code("42P01")
|
||||||
|
UndefinedParameter = Code("42P02")
|
||||||
|
UndefinedObject = Code("42704")
|
||||||
|
DuplicateColumn = Code("42701")
|
||||||
|
DuplicateCursor = Code("42P03")
|
||||||
|
DuplicateDatabase = Code("42P04")
|
||||||
|
DuplicateFunction = Code("42723")
|
||||||
|
DuplicatePstatement = Code("42P05")
|
||||||
|
DuplicateSchema = Code("42P06")
|
||||||
|
DuplicateTable = Code("42P07")
|
||||||
|
DuplicateAlias = Code("42712")
|
||||||
|
DuplicateObject = Code("42710")
|
||||||
|
AmbiguousColumn = Code("42702")
|
||||||
|
AmbiguousFunction = Code("42725")
|
||||||
|
AmbiguousParameter = Code("42P08")
|
||||||
|
AmbiguousAlias = Code("42P09")
|
||||||
|
InvalidColumnReference = Code("42P10")
|
||||||
|
InvalidColumnDefinition = Code("42611")
|
||||||
|
InvalidCursorDefinition = Code("42P11")
|
||||||
|
InvalidDatabaseDefinition = Code("42P12")
|
||||||
|
InvalidFunctionDefinition = Code("42P13")
|
||||||
|
InvalidPstatementDefinition = Code("42P14")
|
||||||
|
InvalidSchemaDefinition = Code("42P15")
|
||||||
|
InvalidTableDefinition = Code("42P16")
|
||||||
|
InvalidObjectDefinition = Code("42P17")
|
||||||
|
WithCheckOptionViolation = Code("44000") // Class 44 - WITH CHECK OPTION Violation
|
||||||
|
InsufficientResources = Code("53000") // Class 53 - Insufficient Resources
|
||||||
|
DiskFull = Code("53100")
|
||||||
|
OutOfMemory = Code("53200")
|
||||||
|
TooManyConnections = Code("53300")
|
||||||
|
ConfigurationLimitExceeded = Code("53400")
|
||||||
|
ProgramLimitExceeded = Code("54000") // Class 54 - Program Limit Exceeded
|
||||||
|
StatementTooComplex = Code("54001")
|
||||||
|
TooManyColumns = Code("54011")
|
||||||
|
TooManyArguments = Code("54023")
|
||||||
|
ObjectNotInPrerequisiteState = Code("55000") // Class 55 - Object Not In Prerequisite State
|
||||||
|
ObjectInUse = Code("55006")
|
||||||
|
CantChangeRuntimeParam = Code("55P02")
|
||||||
|
LockNotAvailable = Code("55P03")
|
||||||
|
UnsafeNewEnumValueUsage = Code("55P04")
|
||||||
|
OperatorIntervention = Code("57000") // Class 57 - Operator Intervention
|
||||||
|
QueryCanceled = Code("57014")
|
||||||
|
AdminShutdown = Code("57P01")
|
||||||
|
CrashShutdown = Code("57P02")
|
||||||
|
CannotConnectNow = Code("57P03")
|
||||||
|
DatabaseDropped = Code("57P04")
|
||||||
|
IdleSessionTimeout = Code("57P05")
|
||||||
|
SystemError = Code("58000") // Class 58 - System Error (errors external to PostgreSQL itself)
|
||||||
|
IOError = Code("58030")
|
||||||
|
UndefinedFile = Code("58P01")
|
||||||
|
DuplicateFile = Code("58P02")
|
||||||
|
FileNameTooLong = Code("58P03")
|
||||||
|
ConfigFileError = Code("F0000") // Class F0 - Configuration File Error
|
||||||
|
LockFileExists = Code("F0001")
|
||||||
|
FDWError = Code("HV000") // Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||||
|
FDWColumnNameNotFound = Code("HV005")
|
||||||
|
FDWDynamicParameterValueNeeded = Code("HV002")
|
||||||
|
FDWFunctionSequenceError = Code("HV010")
|
||||||
|
FDWInconsistentDescriptorInformation = Code("HV021")
|
||||||
|
FDWInvalidAttributeValue = Code("HV024")
|
||||||
|
FDWInvalidColumnName = Code("HV007")
|
||||||
|
FDWInvalidColumnNumber = Code("HV008")
|
||||||
|
FDWInvalidDataType = Code("HV004")
|
||||||
|
FDWInvalidDataTypeDescriptors = Code("HV006")
|
||||||
|
FDWInvalidDescriptorFieldIdentifier = Code("HV091")
|
||||||
|
FDWInvalidHandle = Code("HV00B")
|
||||||
|
FDWInvalidOptionIndex = Code("HV00C")
|
||||||
|
FDWInvalidOptionName = Code("HV00D")
|
||||||
|
FDWInvalidStringLengthOrBufferLength = Code("HV090")
|
||||||
|
FDWInvalidStringFormat = Code("HV00A")
|
||||||
|
FDWInvalidUseOfNullPointer = Code("HV009")
|
||||||
|
FDWTooManyHandles = Code("HV014")
|
||||||
|
FDWOutOfMemory = Code("HV001")
|
||||||
|
FDWNoSchemas = Code("HV00P")
|
||||||
|
FDWOptionNameNotFound = Code("HV00J")
|
||||||
|
FDWReplyHandle = Code("HV00K")
|
||||||
|
FDWSchemaNotFound = Code("HV00Q")
|
||||||
|
FDWTableNotFound = Code("HV00R")
|
||||||
|
FDWUnableToCreateExecution = Code("HV00L")
|
||||||
|
FDWUnableToCreateReply = Code("HV00M")
|
||||||
|
FDWUnableToEstablishConnection = Code("HV00N")
|
||||||
|
PLpgSQLError = Code("P0000") // Class P0 - PL/pgSQL Error
|
||||||
|
RaiseException = Code("P0001")
|
||||||
|
NoDataFound = Code("P0002")
|
||||||
|
TooManyRows = Code("P0003")
|
||||||
|
AssertFailure = Code("P0004")
|
||||||
|
InternalError = Code("XX000") // Class XX - Internal Error
|
||||||
|
DataCorrupted = Code("XX001")
|
||||||
|
IndexCorrupted = Code("XX002")
|
||||||
|
)
|
||||||
|
|
||||||
|
var errorCodeNames = map[Code]string{
|
||||||
|
"00000": "successful_completion",
|
||||||
|
"01000": "warning",
|
||||||
|
"0100C": "dynamic_result_sets_returned",
|
||||||
|
"01008": "implicit_zero_bit_padding",
|
||||||
|
"01003": "null_value_eliminated_in_set_function",
|
||||||
|
"01007": "privilege_not_granted",
|
||||||
|
"01006": "privilege_not_revoked",
|
||||||
|
"01004": "string_data_right_truncation",
|
||||||
|
"01P01": "deprecated_feature",
|
||||||
|
"02000": "no_data",
|
||||||
|
"02001": "no_additional_dynamic_result_sets_returned",
|
||||||
|
"03000": "sql_statement_not_yet_complete",
|
||||||
|
"08000": "connection_exception",
|
||||||
|
"08003": "connection_does_not_exist",
|
||||||
|
"08006": "connection_failure",
|
||||||
|
"08001": "sqlclient_unable_to_establish_sqlconnection",
|
||||||
|
"08004": "sqlserver_rejected_establishment_of_sqlconnection",
|
||||||
|
"08007": "transaction_resolution_unknown",
|
||||||
|
"08P01": "protocol_violation",
|
||||||
|
"09000": "triggered_action_exception",
|
||||||
|
"0A000": "feature_not_supported",
|
||||||
|
"0B000": "invalid_transaction_initiation",
|
||||||
|
"0F000": "locator_exception",
|
||||||
|
"0F001": "invalid_locator_specification",
|
||||||
|
"0L000": "invalid_grantor",
|
||||||
|
"0LP01": "invalid_grant_operation",
|
||||||
|
"0P000": "invalid_role_specification",
|
||||||
|
"0Z000": "diagnostics_exception",
|
||||||
|
"0Z002": "stacked_diagnostics_accessed_without_active_handler",
|
||||||
|
"10608": "invalid_argument_for_xquery",
|
||||||
|
"20000": "case_not_found",
|
||||||
|
"21000": "cardinality_violation",
|
||||||
|
"22000": "data_exception",
|
||||||
|
"2202E": "array_subscript_error",
|
||||||
|
"22021": "character_not_in_repertoire",
|
||||||
|
"22008": "datetime_field_overflow",
|
||||||
|
"22012": "division_by_zero",
|
||||||
|
"22005": "error_in_assignment",
|
||||||
|
"2200B": "escape_character_conflict",
|
||||||
|
"22022": "indicator_overflow",
|
||||||
|
"22015": "interval_field_overflow",
|
||||||
|
"2201E": "invalid_argument_for_logarithm",
|
||||||
|
"22014": "invalid_argument_for_ntile_function",
|
||||||
|
"22016": "invalid_argument_for_nth_value_function",
|
||||||
|
"2201F": "invalid_argument_for_power_function",
|
||||||
|
"2201G": "invalid_argument_for_width_bucket_function",
|
||||||
|
"22018": "invalid_character_value_for_cast",
|
||||||
|
"22007": "invalid_datetime_format",
|
||||||
|
"22019": "invalid_escape_character",
|
||||||
|
"2200D": "invalid_escape_octet",
|
||||||
|
"22025": "invalid_escape_sequence",
|
||||||
|
"22P06": "nonstandard_use_of_escape_character",
|
||||||
|
"22010": "invalid_indicator_parameter_value",
|
||||||
|
"22023": "invalid_parameter_value",
|
||||||
|
"22013": "invalid_preceding_or_following_size",
|
||||||
|
"2201B": "invalid_regular_expression",
|
||||||
|
"2201W": "invalid_row_count_in_limit_clause",
|
||||||
|
"2201X": "invalid_row_count_in_result_offset_clause",
|
||||||
|
"2202H": "invalid_tablesample_argument",
|
||||||
|
"2202G": "invalid_tablesample_repeat",
|
||||||
|
"22009": "invalid_time_zone_displacement_value",
|
||||||
|
"2200C": "invalid_use_of_escape_character",
|
||||||
|
"2200G": "most_specific_type_mismatch",
|
||||||
|
"22004": "null_value_not_allowed",
|
||||||
|
"22002": "null_value_no_indicator_parameter",
|
||||||
|
"22003": "numeric_value_out_of_range",
|
||||||
|
"2200H": "sequence_generator_limit_exceeded",
|
||||||
|
"22026": "string_data_length_mismatch",
|
||||||
|
"22001": "string_data_right_truncation",
|
||||||
|
"22011": "substring_error",
|
||||||
|
"22027": "trim_error",
|
||||||
|
"22024": "unterminated_c_string",
|
||||||
|
"2200F": "zero_length_character_string",
|
||||||
|
"22P01": "floating_point_exception",
|
||||||
|
"22P02": "invalid_text_representation",
|
||||||
|
"22P03": "invalid_binary_representation",
|
||||||
|
"22P04": "bad_copy_file_format",
|
||||||
|
"22P05": "untranslatable_character",
|
||||||
|
"2200L": "not_an_xml_document",
|
||||||
|
"2200M": "invalid_xml_document",
|
||||||
|
"2200N": "invalid_xml_content",
|
||||||
|
"2200S": "invalid_xml_comment",
|
||||||
|
"2200T": "invalid_xml_processing_instruction",
|
||||||
|
"22030": "duplicate_json_object_key_value",
|
||||||
|
"22031": "invalid_argument_for_sql_json_datetime_function",
|
||||||
|
"22032": "invalid_json_text",
|
||||||
|
"22033": "invalid_sql_json_subscript",
|
||||||
|
"22034": "more_than_one_sql_json_item",
|
||||||
|
"22035": "no_sql_json_item",
|
||||||
|
"22036": "non_numeric_sql_json_item",
|
||||||
|
"22037": "non_unique_keys_in_a_json_object",
|
||||||
|
"22038": "singleton_sql_json_item_required",
|
||||||
|
"22039": "sql_json_array_not_found",
|
||||||
|
"2203A": "sql_json_member_not_found",
|
||||||
|
"2203B": "sql_json_number_not_found",
|
||||||
|
"2203C": "sql_json_object_not_found",
|
||||||
|
"2203D": "too_many_json_array_elements",
|
||||||
|
"2203E": "too_many_json_object_members",
|
||||||
|
"2203F": "sql_json_scalar_required",
|
||||||
|
"2203G": "sql_json_item_cannot_be_cast_to_target_type",
|
||||||
|
"23000": "integrity_constraint_violation",
|
||||||
|
"23001": "restrict_violation",
|
||||||
|
"23502": "not_null_violation",
|
||||||
|
"23503": "foreign_key_violation",
|
||||||
|
"23505": "unique_violation",
|
||||||
|
"23514": "check_violation",
|
||||||
|
"23P01": "exclusion_violation",
|
||||||
|
"24000": "invalid_cursor_state",
|
||||||
|
"25000": "invalid_transaction_state",
|
||||||
|
"25001": "active_sql_transaction",
|
||||||
|
"25002": "branch_transaction_already_active",
|
||||||
|
"25008": "held_cursor_requires_same_isolation_level",
|
||||||
|
"25003": "inappropriate_access_mode_for_branch_transaction",
|
||||||
|
"25004": "inappropriate_isolation_level_for_branch_transaction",
|
||||||
|
"25005": "no_active_sql_transaction_for_branch_transaction",
|
||||||
|
"25006": "read_only_sql_transaction",
|
||||||
|
"25007": "schema_and_data_statement_mixing_not_supported",
|
||||||
|
"25P01": "no_active_sql_transaction",
|
||||||
|
"25P02": "in_failed_sql_transaction",
|
||||||
|
"25P03": "idle_in_transaction_session_timeout",
|
||||||
|
"25P04": "transaction_timeout",
|
||||||
|
"26000": "invalid_sql_statement_name",
|
||||||
|
"27000": "triggered_data_change_violation",
|
||||||
|
"28000": "invalid_authorization_specification",
|
||||||
|
"28P01": "invalid_password",
|
||||||
|
"2B000": "dependent_privilege_descriptors_still_exist",
|
||||||
|
"2BP01": "dependent_objects_still_exist",
|
||||||
|
"2D000": "invalid_transaction_termination",
|
||||||
|
"2F000": "sql_routine_exception",
|
||||||
|
"2F005": "function_executed_no_return_statement",
|
||||||
|
"2F002": "modifying_sql_data_not_permitted",
|
||||||
|
"2F003": "prohibited_sql_statement_attempted",
|
||||||
|
"2F004": "reading_sql_data_not_permitted",
|
||||||
|
"34000": "invalid_cursor_name",
|
||||||
|
"38000": "external_routine_exception",
|
||||||
|
"38001": "containing_sql_not_permitted",
|
||||||
|
"38002": "modifying_sql_data_not_permitted",
|
||||||
|
"38003": "prohibited_sql_statement_attempted",
|
||||||
|
"38004": "reading_sql_data_not_permitted",
|
||||||
|
"39000": "external_routine_invocation_exception",
|
||||||
|
"39001": "invalid_sqlstate_returned",
|
||||||
|
"39004": "null_value_not_allowed",
|
||||||
|
"39P01": "trigger_protocol_violated",
|
||||||
|
"39P02": "srf_protocol_violated",
|
||||||
|
"39P03": "event_trigger_protocol_violated",
|
||||||
|
"3B000": "savepoint_exception",
|
||||||
|
"3B001": "invalid_savepoint_specification",
|
||||||
|
"3D000": "invalid_catalog_name",
|
||||||
|
"3F000": "invalid_schema_name",
|
||||||
|
"40000": "transaction_rollback",
|
||||||
|
"40002": "transaction_integrity_constraint_violation",
|
||||||
|
"40001": "serialization_failure",
|
||||||
|
"40003": "statement_completion_unknown",
|
||||||
|
"40P01": "deadlock_detected",
|
||||||
|
"42000": "syntax_error_or_access_rule_violation",
|
||||||
|
"42601": "syntax_error",
|
||||||
|
"42501": "insufficient_privilege",
|
||||||
|
"42846": "cannot_coerce",
|
||||||
|
"42803": "grouping_error",
|
||||||
|
"42P20": "windowing_error",
|
||||||
|
"42P19": "invalid_recursion",
|
||||||
|
"42830": "invalid_foreign_key",
|
||||||
|
"42602": "invalid_name",
|
||||||
|
"42622": "name_too_long",
|
||||||
|
"42939": "reserved_name",
|
||||||
|
"42804": "datatype_mismatch",
|
||||||
|
"42P18": "indeterminate_datatype",
|
||||||
|
"42P21": "collation_mismatch",
|
||||||
|
"42P22": "indeterminate_collation",
|
||||||
|
"42809": "wrong_object_type",
|
||||||
|
"428C9": "generated_always",
|
||||||
|
"42703": "undefined_column",
|
||||||
|
"42883": "undefined_function",
|
||||||
|
"42P01": "undefined_table",
|
||||||
|
"42P02": "undefined_parameter",
|
||||||
|
"42704": "undefined_object",
|
||||||
|
"42701": "duplicate_column",
|
||||||
|
"42P03": "duplicate_cursor",
|
||||||
|
"42P04": "duplicate_database",
|
||||||
|
"42723": "duplicate_function",
|
||||||
|
"42P05": "duplicate_prepared_statement",
|
||||||
|
"42P06": "duplicate_schema",
|
||||||
|
"42P07": "duplicate_table",
|
||||||
|
"42712": "duplicate_alias",
|
||||||
|
"42710": "duplicate_object",
|
||||||
|
"42702": "ambiguous_column",
|
||||||
|
"42725": "ambiguous_function",
|
||||||
|
"42P08": "ambiguous_parameter",
|
||||||
|
"42P09": "ambiguous_alias",
|
||||||
|
"42P10": "invalid_column_reference",
|
||||||
|
"42611": "invalid_column_definition",
|
||||||
|
"42P11": "invalid_cursor_definition",
|
||||||
|
"42P12": "invalid_database_definition",
|
||||||
|
"42P13": "invalid_function_definition",
|
||||||
|
"42P14": "invalid_prepared_statement_definition",
|
||||||
|
"42P15": "invalid_schema_definition",
|
||||||
|
"42P16": "invalid_table_definition",
|
||||||
|
"42P17": "invalid_object_definition",
|
||||||
|
"44000": "with_check_option_violation",
|
||||||
|
"53000": "insufficient_resources",
|
||||||
|
"53100": "disk_full",
|
||||||
|
"53200": "out_of_memory",
|
||||||
|
"53300": "too_many_connections",
|
||||||
|
"53400": "configuration_limit_exceeded",
|
||||||
|
"54000": "program_limit_exceeded",
|
||||||
|
"54001": "statement_too_complex",
|
||||||
|
"54011": "too_many_columns",
|
||||||
|
"54023": "too_many_arguments",
|
||||||
|
"55000": "object_not_in_prerequisite_state",
|
||||||
|
"55006": "object_in_use",
|
||||||
|
"55P02": "cant_change_runtime_param",
|
||||||
|
"55P03": "lock_not_available",
|
||||||
|
"55P04": "unsafe_new_enum_value_usage",
|
||||||
|
"57000": "operator_intervention",
|
||||||
|
"57014": "query_canceled",
|
||||||
|
"57P01": "admin_shutdown",
|
||||||
|
"57P02": "crash_shutdown",
|
||||||
|
"57P03": "cannot_connect_now",
|
||||||
|
"57P04": "database_dropped",
|
||||||
|
"57P05": "idle_session_timeout",
|
||||||
|
"58000": "system_error",
|
||||||
|
"58030": "io_error",
|
||||||
|
"58P01": "undefined_file",
|
||||||
|
"58P02": "duplicate_file",
|
||||||
|
"58P03": "file_name_too_long",
|
||||||
|
"F0000": "config_file_error",
|
||||||
|
"F0001": "lock_file_exists",
|
||||||
|
"HV000": "fdw_error",
|
||||||
|
"HV005": "fdw_column_name_not_found",
|
||||||
|
"HV002": "fdw_dynamic_parameter_value_needed",
|
||||||
|
"HV010": "fdw_function_sequence_error",
|
||||||
|
"HV021": "fdw_inconsistent_descriptor_information",
|
||||||
|
"HV024": "fdw_invalid_attribute_value",
|
||||||
|
"HV007": "fdw_invalid_column_name",
|
||||||
|
"HV008": "fdw_invalid_column_number",
|
||||||
|
"HV004": "fdw_invalid_data_type",
|
||||||
|
"HV006": "fdw_invalid_data_type_descriptors",
|
||||||
|
"HV091": "fdw_invalid_descriptor_field_identifier",
|
||||||
|
"HV00B": "fdw_invalid_handle",
|
||||||
|
"HV00C": "fdw_invalid_option_index",
|
||||||
|
"HV00D": "fdw_invalid_option_name",
|
||||||
|
"HV090": "fdw_invalid_string_length_or_buffer_length",
|
||||||
|
"HV00A": "fdw_invalid_string_format",
|
||||||
|
"HV009": "fdw_invalid_use_of_null_pointer",
|
||||||
|
"HV014": "fdw_too_many_handles",
|
||||||
|
"HV001": "fdw_out_of_memory",
|
||||||
|
"HV00P": "fdw_no_schemas",
|
||||||
|
"HV00J": "fdw_option_name_not_found",
|
||||||
|
"HV00K": "fdw_reply_handle",
|
||||||
|
"HV00Q": "fdw_schema_not_found",
|
||||||
|
"HV00R": "fdw_table_not_found",
|
||||||
|
"HV00L": "fdw_unable_to_create_execution",
|
||||||
|
"HV00M": "fdw_unable_to_create_reply",
|
||||||
|
"HV00N": "fdw_unable_to_establish_connection",
|
||||||
|
"P0000": "plpgsql_error",
|
||||||
|
"P0001": "raise_exception",
|
||||||
|
"P0002": "no_data_found",
|
||||||
|
"P0003": "too_many_rows",
|
||||||
|
"P0004": "assert_failure",
|
||||||
|
"XX000": "internal_error",
|
||||||
|
"XX001": "data_corrupted",
|
||||||
|
"XX002": "index_corrupted",
|
||||||
|
}
|
||||||
35
vendor/github.com/lib/pq/pqerror/pqerror.go
generated
vendored
Normal file
35
vendor/github.com/lib/pq/pqerror/pqerror.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//go:generate go run gen.go
|
||||||
|
|
||||||
|
// Package pqerror contains PostgreSQL error codes for use with pq.Error.
|
||||||
|
package pqerror
|
||||||
|
|
||||||
|
// Code is a five-character error code.
|
||||||
|
type Code string
|
||||||
|
|
||||||
|
// Name returns a more human friendly rendering of the error code, namely the
|
||||||
|
// "condition name".
|
||||||
|
func (ec Code) Name() string { return errorCodeNames[ec] }
|
||||||
|
|
||||||
|
// Class returns the error class, e.g. "28".
|
||||||
|
func (ec Code) Class() Class { return Class(ec[:2]) }
|
||||||
|
|
||||||
|
// Class is only the class part of an error code.
|
||||||
|
type Class string
|
||||||
|
|
||||||
|
// Name returns the condition name of an error class. It is equivalent to the
|
||||||
|
// condition name of the "standard" error code (i.e. the one having the last
|
||||||
|
// three characters "000").
|
||||||
|
func (ec Class) Name() string { return errorCodeNames[Code(ec+"000")] }
|
||||||
|
|
||||||
|
// TODO(v2): use "type Severity string" for the below.
|
||||||
|
|
||||||
|
// Error severity values.
|
||||||
|
const (
|
||||||
|
SeverityFatal = "FATAL"
|
||||||
|
SeverityPanic = "PANIC"
|
||||||
|
SeverityWarning = "WARNING"
|
||||||
|
SeverityNotice = "NOTICE"
|
||||||
|
SeverityDebug = "DEBUG"
|
||||||
|
SeverityInfo = "INFO"
|
||||||
|
SeverityLog = "LOG"
|
||||||
|
)
|
||||||
71
vendor/github.com/lib/pq/quote.go
generated
vendored
Normal file
71
vendor/github.com/lib/pq/quote.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be
|
||||||
|
// used as part of an SQL statement. For example:
|
||||||
|
//
|
||||||
|
// tblname := "my_table"
|
||||||
|
// data := "my_data"
|
||||||
|
// quoted := pq.QuoteIdentifier(tblname)
|
||||||
|
// err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data)
|
||||||
|
//
|
||||||
|
// Any double quotes in name will be escaped. The quoted identifier will be case
|
||||||
|
// sensitive when used in a query. If the input string contains a zero byte, the
|
||||||
|
// result will be truncated immediately before it.
|
||||||
|
func QuoteIdentifier(name string) string {
|
||||||
|
end := strings.IndexRune(name, 0)
|
||||||
|
if end > -1 {
|
||||||
|
name = name[:end]
|
||||||
|
}
|
||||||
|
return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferQuoteIdentifier satisfies the same purpose as QuoteIdentifier, but backed by a
|
||||||
|
// byte buffer.
|
||||||
|
func BufferQuoteIdentifier(name string, buffer *bytes.Buffer) {
|
||||||
|
// TODO(v2): this should have accepted an io.Writer, not *bytes.Buffer.
|
||||||
|
end := strings.IndexRune(name, 0)
|
||||||
|
if end > -1 {
|
||||||
|
name = name[:end]
|
||||||
|
}
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
buffer.WriteString(strings.Replace(name, `"`, `""`, -1))
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal
|
||||||
|
// to DDL and other statements that do not accept parameters) to be used as part
|
||||||
|
// of an SQL statement. For example:
|
||||||
|
//
|
||||||
|
// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z")
|
||||||
|
// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date))
|
||||||
|
//
|
||||||
|
// Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be
|
||||||
|
// replaced by two backslashes (i.e. "\\") and the C-style escape identifier
|
||||||
|
// that PostgreSQL provides ('E') will be prepended to the string.
|
||||||
|
func QuoteLiteral(literal string) string {
|
||||||
|
// This follows the PostgreSQL internal algorithm for handling quoted literals
|
||||||
|
// from libpq, which can be found in the "PQEscapeStringInternal" function,
|
||||||
|
// which is found in the libpq/fe-exec.c source file:
|
||||||
|
// https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/interfaces/libpq/fe-exec.c
|
||||||
|
//
|
||||||
|
// substitute any single-quotes (') with two single-quotes ('')
|
||||||
|
literal = strings.Replace(literal, `'`, `''`, -1)
|
||||||
|
// determine if the string has any backslashes (\) in it.
|
||||||
|
// if it does, replace any backslashes (\) with two backslashes (\\)
|
||||||
|
// then, we need to wrap the entire string with a PostgreSQL
|
||||||
|
// C-style escape. Per how "PQEscapeStringInternal" handles this case, we
|
||||||
|
// also add a space before the "E"
|
||||||
|
if strings.Contains(literal, `\`) {
|
||||||
|
literal = strings.Replace(literal, `\`, `\\`, -1)
|
||||||
|
literal = ` E'` + literal + `'`
|
||||||
|
} else {
|
||||||
|
// otherwise, we can just wrap the literal with a pair of single quotes
|
||||||
|
literal = `'` + literal + `'`
|
||||||
|
}
|
||||||
|
return literal
|
||||||
|
}
|
||||||
245
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
245
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noRows struct{}
|
||||||
|
|
||||||
|
var emptyRows noRows
|
||||||
|
|
||||||
|
var _ driver.Result = noRows{}
|
||||||
|
|
||||||
|
func (noRows) LastInsertId() (int64, error) { return 0, errNoLastInsertID }
|
||||||
|
func (noRows) RowsAffected() (int64, error) { return 0, errNoRowsAffected }
|
||||||
|
|
||||||
|
type (
|
||||||
|
rowsHeader struct {
|
||||||
|
colNames []string
|
||||||
|
colTyps []fieldDesc
|
||||||
|
colFmts []format
|
||||||
|
}
|
||||||
|
rows struct {
|
||||||
|
cn *conn
|
||||||
|
finish func()
|
||||||
|
rowsHeader
|
||||||
|
done bool
|
||||||
|
rb readBuf
|
||||||
|
result driver.Result
|
||||||
|
tag string
|
||||||
|
|
||||||
|
next *rowsHeader
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rs *rows) Close() error {
|
||||||
|
if finish := rs.finish; finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
// no need to look at cn.bad as Next() will
|
||||||
|
for {
|
||||||
|
err := rs.Next(nil)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
case io.EOF:
|
||||||
|
// rs.Next can return io.EOF on both ReadyForQuery and
|
||||||
|
// RowDescription (used with HasNextResultSet). We need to fetch
|
||||||
|
// messages until we hit a ReadyForQuery, which is done by waiting
|
||||||
|
// for done to be set.
|
||||||
|
if rs.done {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) Columns() []string {
|
||||||
|
return rs.colNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) Result() driver.Result {
|
||||||
|
if rs.result == nil {
|
||||||
|
return emptyRows
|
||||||
|
}
|
||||||
|
return rs.result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) Tag() string {
|
||||||
|
return rs.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) Next(dest []driver.Value) (resErr error) {
|
||||||
|
if rs.done {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if err := rs.cn.err.getForNext(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, err := rs.cn.recv1Buf(&rs.rb)
|
||||||
|
if err != nil {
|
||||||
|
return rs.cn.handleError(err)
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case proto.ErrorResponse:
|
||||||
|
resErr = parseError(&rs.rb, "")
|
||||||
|
case proto.CommandComplete, proto.EmptyQueryResponse:
|
||||||
|
if t == proto.CommandComplete {
|
||||||
|
rs.result, rs.tag, err = rs.cn.parseComplete(rs.rb.string())
|
||||||
|
if err != nil {
|
||||||
|
return rs.cn.handleError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case proto.ReadyForQuery:
|
||||||
|
rs.cn.processReadyForQuery(&rs.rb)
|
||||||
|
rs.done = true
|
||||||
|
if resErr != nil {
|
||||||
|
return rs.cn.handleError(resErr)
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
case proto.DataRow:
|
||||||
|
n := rs.rb.int16()
|
||||||
|
if resErr != nil {
|
||||||
|
rs.cn.err.set(driver.ErrBadConn)
|
||||||
|
return fmt.Errorf("pq: unexpected DataRow after error %s", resErr)
|
||||||
|
}
|
||||||
|
if n < len(dest) {
|
||||||
|
dest = dest[:n]
|
||||||
|
}
|
||||||
|
for i := range dest {
|
||||||
|
l := rs.rb.int32()
|
||||||
|
if l == -1 {
|
||||||
|
dest[i] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dest[i], err = decode(&rs.cn.parameterStatus, rs.rb.next(l), rs.colTyps[i].OID, rs.colFmts[i])
|
||||||
|
if err != nil {
|
||||||
|
return rs.cn.handleError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rs.cn.handleError(resErr)
|
||||||
|
case proto.RowDescription:
|
||||||
|
next := parsePortalRowDescribe(&rs.rb)
|
||||||
|
rs.next = &next
|
||||||
|
return io.EOF
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: unexpected message after execute: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) HasNextResultSet() bool {
|
||||||
|
hasNext := rs.next != nil && !rs.done
|
||||||
|
return hasNext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *rows) NextResultSet() error {
|
||||||
|
if rs.next == nil {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
rs.rowsHeader = *rs.next
|
||||||
|
rs.next = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeScanType returns the value type that can be used to scan types into.
|
||||||
|
func (rs *rows) ColumnTypeScanType(index int) reflect.Type {
|
||||||
|
return rs.colTyps[index].Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeDatabaseTypeName return the database system type name.
|
||||||
|
func (rs *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||||
|
return rs.colTyps[index].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeLength returns the length of the column type if the column is a
|
||||||
|
// variable length type. If the column is not a variable length type ok
|
||||||
|
// should return false.
|
||||||
|
func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) {
|
||||||
|
return rs.colTyps[index].Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypePrecisionScale should return the precision and scale for decimal
|
||||||
|
// types. If not applicable, ok should be false.
|
||||||
|
func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
|
||||||
|
return rs.colTyps[index].PrecisionScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerSize = 4
|
||||||
|
|
||||||
|
type fieldDesc struct {
|
||||||
|
// The object ID of the data type.
|
||||||
|
OID oid.Oid
|
||||||
|
// The data type size (see pg_type.typlen).
|
||||||
|
// Note that negative values denote variable-width types.
|
||||||
|
Len int
|
||||||
|
// The type modifier (see pg_attribute.atttypmod).
|
||||||
|
// The meaning of the modifier is type-specific.
|
||||||
|
Mod int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Type() reflect.Type {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_int8:
|
||||||
|
return reflect.TypeOf(int64(0))
|
||||||
|
case oid.T_int4:
|
||||||
|
return reflect.TypeOf(int32(0))
|
||||||
|
case oid.T_int2:
|
||||||
|
return reflect.TypeOf(int16(0))
|
||||||
|
case oid.T_float8:
|
||||||
|
return reflect.TypeOf(float64(0))
|
||||||
|
case oid.T_float4:
|
||||||
|
return reflect.TypeOf(float32(0))
|
||||||
|
case oid.T_varchar, oid.T_text, oid.T_varbit, oid.T_bit:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case oid.T_bool:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz:
|
||||||
|
return reflect.TypeOf(time.Time{})
|
||||||
|
case oid.T_bytea:
|
||||||
|
return reflect.TypeOf([]byte(nil))
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(new(any)).Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Name() string {
|
||||||
|
return oid.TypeName[fd.OID]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Length() (length int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_text, oid.T_bytea:
|
||||||
|
return math.MaxInt64, true
|
||||||
|
case oid.T_varchar, oid.T_bpchar:
|
||||||
|
return int64(fd.Mod - headerSize), true
|
||||||
|
case oid.T_varbit, oid.T_bit:
|
||||||
|
return int64(fd.Mod), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_numeric, oid.T__numeric:
|
||||||
|
mod := fd.Mod - headerSize
|
||||||
|
precision = int64((mod >> 16) & 0xffff)
|
||||||
|
scale = int64(mod & 0xffff)
|
||||||
|
return precision, scale, true
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
261
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
261
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Package scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc5802
|
||||||
|
package scram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||||
|
//
|
||||||
|
// A Client may be used within a SASL conversation with logic resembling:
|
||||||
|
//
|
||||||
|
// var in []byte
|
||||||
|
// var client = scram.NewClient(sha1.New, user, pass)
|
||||||
|
// for client.Step(in) {
|
||||||
|
// out := client.Out()
|
||||||
|
// // send out to server
|
||||||
|
// in := serverOut
|
||||||
|
// }
|
||||||
|
// if client.Err() != nil {
|
||||||
|
// // auth failed
|
||||||
|
// }
|
||||||
|
type Client struct {
|
||||||
|
newHash func() hash.Hash
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
step int
|
||||||
|
out bytes.Buffer
|
||||||
|
err error
|
||||||
|
|
||||||
|
clientNonce []byte
|
||||||
|
serverNonce []byte
|
||||||
|
saltedPass []byte
|
||||||
|
authMsg bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||||
|
//
|
||||||
|
// For SCRAM-SHA-256, for example, use:
|
||||||
|
//
|
||||||
|
// client := scram.NewClient(sha256.New, user, pass)
|
||||||
|
func NewClient(newHash func() hash.Hash, user, pass string) *Client {
|
||||||
|
c := &Client{
|
||||||
|
newHash: newHash,
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
}
|
||||||
|
c.out.Grow(256)
|
||||||
|
c.authMsg.Grow(256)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns the data to be sent to the server in the current step.
|
||||||
|
func (c *Client) Out() []byte {
|
||||||
|
if c.out.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error that occurred, or nil if there were no errors.
|
||||||
|
func (c *Client) Err() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the client nonce to the provided value.
|
||||||
|
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||||
|
func (c *Client) SetNonce(nonce []byte) {
|
||||||
|
c.clientNonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
|
||||||
|
|
||||||
|
// Step processes the incoming data from the server and makes the
|
||||||
|
// next round of data for the server available via Client.Out.
|
||||||
|
// Step returns false if there are no errors and more data is
|
||||||
|
// still expected.
|
||||||
|
func (c *Client) Step(in []byte) bool {
|
||||||
|
c.out.Reset()
|
||||||
|
if c.step > 2 || c.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.step++
|
||||||
|
switch c.step {
|
||||||
|
case 1:
|
||||||
|
c.err = c.step1(in)
|
||||||
|
case 2:
|
||||||
|
c.err = c.step2(in)
|
||||||
|
case 3:
|
||||||
|
c.err = c.step3(in)
|
||||||
|
}
|
||||||
|
return c.step > 2 || c.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step1(in []byte) error {
|
||||||
|
if len(c.clientNonce) == 0 {
|
||||||
|
const nonceLen = 16
|
||||||
|
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
|
||||||
|
if _, err := rand.Read(buf[:nonceLen]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %w", err)
|
||||||
|
}
|
||||||
|
c.clientNonce = buf[nonceLen:]
|
||||||
|
b64.Encode(c.clientNonce, buf[:nonceLen])
|
||||||
|
}
|
||||||
|
c.authMsg.WriteString("n=")
|
||||||
|
escaper.WriteString(&c.authMsg, c.user)
|
||||||
|
c.authMsg.WriteString(",r=")
|
||||||
|
c.authMsg.Write(c.clientNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("n,,")
|
||||||
|
c.out.Write(c.authMsg.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func (c *Client) step2(in []byte) error {
|
||||||
|
c.authMsg.WriteByte(',')
|
||||||
|
c.authMsg.Write(in)
|
||||||
|
|
||||||
|
fields := bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) != 3 {
|
||||||
|
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
c.serverNonce = fields[0][2:]
|
||||||
|
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
|
||||||
|
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
|
||||||
|
n, err := b64.Decode(salt, fields[1][2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
|
||||||
|
}
|
||||||
|
salt = salt[:n]
|
||||||
|
iterCount, err := strconv.Atoi(string(fields[2][2:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
c.saltPassword(salt, iterCount)
|
||||||
|
|
||||||
|
c.authMsg.WriteString(",c=biws,r=")
|
||||||
|
c.authMsg.Write(c.serverNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("c=biws,r=")
|
||||||
|
c.out.Write(c.serverNonce)
|
||||||
|
c.out.WriteString(",p=")
|
||||||
|
c.out.Write(c.clientProof())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step3(in []byte) error {
|
||||||
|
var isv, ise bool
|
||||||
|
var fields = bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) == 1 {
|
||||||
|
isv = bytes.HasPrefix(fields[0], []byte("v="))
|
||||||
|
ise = bytes.HasPrefix(fields[0], []byte("e="))
|
||||||
|
}
|
||||||
|
if ise {
|
||||||
|
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
|
||||||
|
} else if !isv {
|
||||||
|
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
|
||||||
|
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) saltPassword(salt []byte, iterCount int) {
|
||||||
|
mac := hmac.New(c.newHash, []byte(c.pass))
|
||||||
|
mac.Write(salt)
|
||||||
|
mac.Write([]byte{0, 0, 0, 1})
|
||||||
|
ui := mac.Sum(nil)
|
||||||
|
hi := make([]byte, len(ui))
|
||||||
|
copy(hi, ui)
|
||||||
|
for i := 1; i < iterCount; i++ {
|
||||||
|
mac.Reset()
|
||||||
|
mac.Write(ui)
|
||||||
|
mac.Sum(ui[:0])
|
||||||
|
for j, b := range ui {
|
||||||
|
hi[j] ^= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.saltedPass = hi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientProof() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Client Key"))
|
||||||
|
clientKey := mac.Sum(nil)
|
||||||
|
hash := c.newHash()
|
||||||
|
hash.Write(clientKey)
|
||||||
|
storedKey := hash.Sum(nil)
|
||||||
|
mac = hmac.New(c.newHash, storedKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
clientProof := mac.Sum(nil)
|
||||||
|
for i, b := range clientKey {
|
||||||
|
clientProof[i] ^= b
|
||||||
|
}
|
||||||
|
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
|
||||||
|
b64.Encode(clientProof64, clientProof)
|
||||||
|
return clientProof64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) serverSignature() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Server Key"))
|
||||||
|
serverKey := mac.Sum(nil)
|
||||||
|
|
||||||
|
mac = hmac.New(c.newHash, serverKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
serverSignature := mac.Sum(nil)
|
||||||
|
|
||||||
|
encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
|
||||||
|
b64.Encode(encoded, serverSignature)
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
312
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
312
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/pqutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Registry for custom tls.Configs
|
||||||
|
var (
|
||||||
|
tlsConfs = make(map[string]*tls.Config)
|
||||||
|
tlsConfsMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterTLSConfig registers a custom [tls.Config]. They are used by using
|
||||||
|
// sslmode=pqgo-«key» in the connection string.
|
||||||
|
//
|
||||||
|
// Set the config to nil to remove a configuration.
|
||||||
|
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
|
key = strings.TrimPrefix(key, "pqgo-")
|
||||||
|
if config == nil {
|
||||||
|
tlsConfsMu.Lock()
|
||||||
|
delete(tlsConfs, key)
|
||||||
|
tlsConfsMu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfsMu.Lock()
|
||||||
|
tlsConfs[key] = config
|
||||||
|
tlsConfsMu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasTLSConfig(key string) bool {
|
||||||
|
tlsConfsMu.RLock()
|
||||||
|
defer tlsConfsMu.RUnlock()
|
||||||
|
_, ok := tlsConfs[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTLSConfigClone(key string) *tls.Config {
|
||||||
|
tlsConfsMu.RLock()
|
||||||
|
defer tlsConfsMu.RUnlock()
|
||||||
|
if v, ok := tlsConfs[key]; ok {
|
||||||
|
return v.Clone()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
|
||||||
|
// related settings. The function is nil when no upgrade should take place.
|
||||||
|
//
|
||||||
|
// Don't refer to Config.SSLMode here, as the mode in arguments may be different
|
||||||
|
// in case of sslmode=allow or prefer.
|
||||||
|
func ssl(cfg Config, mode SSLMode) (func(net.Conn) (net.Conn, error), error) {
|
||||||
|
var (
|
||||||
|
home = pqutil.Home()
|
||||||
|
// Don't set defaults here, because tlsConf may be overwritten if a
|
||||||
|
// custom one was registered. Set it after the sslmode switch.
|
||||||
|
tlsConf = &tls.Config{}
|
||||||
|
// Only verify the CA signing but not the hostname.
|
||||||
|
verifyCaOnly = false
|
||||||
|
)
|
||||||
|
if mode.useSSL() && !cfg.SSLInline && cfg.SSLRootCert == "" && home != "" {
|
||||||
|
f := filepath.Join(home, "root.crt")
|
||||||
|
if _, err := os.Stat(f); err == nil {
|
||||||
|
cfg.SSLRootCert = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case mode == SSLModeDisable || mode == SSLModeAllow:
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
case mode == SSLModeRequire || mode == SSLModePrefer:
|
||||||
|
// Skip TLS's own verification since it requires full verification.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
|
||||||
|
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
|
||||||
|
//
|
||||||
|
// For backwards compatibility with earlier versions of PostgreSQL, if a
|
||||||
|
// root CA file exists, the behavior of sslmode=require will be the same
|
||||||
|
// as that of verify-ca, meaning the server certificate is validated
|
||||||
|
// against the CA. Relying on this behavior is discouraged, and
|
||||||
|
// applications that need certificate validation should always use
|
||||||
|
// verify-ca or verify-full.
|
||||||
|
if cfg.SSLRootCert != "" {
|
||||||
|
if cfg.SSLInline {
|
||||||
|
verifyCaOnly = true
|
||||||
|
} else if _, err := os.Stat(cfg.SSLRootCert); err == nil {
|
||||||
|
verifyCaOnly = true
|
||||||
|
} else if cfg.SSLRootCert != "system" {
|
||||||
|
cfg.SSLRootCert = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case mode == SSLModeVerifyCA:
|
||||||
|
// Skip TLS's own verification since it requires full verification.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
verifyCaOnly = true
|
||||||
|
case mode == SSLModeVerifyFull:
|
||||||
|
tlsConf.ServerName = cfg.Host
|
||||||
|
case strings.HasPrefix(string(mode), "pqgo-"):
|
||||||
|
tlsConf = getTLSConfigClone(string(mode[5:]))
|
||||||
|
if tlsConf == nil {
|
||||||
|
return nil, fmt.Errorf(`pq: unknown custom sslmode %q`, mode)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.MinVersion = cfg.SSLMinProtocolVersion.tlsconf()
|
||||||
|
tlsConf.MaxVersion = cfg.SSLMaxProtocolVersion.tlsconf()
|
||||||
|
|
||||||
|
// RFC 6066 asks to not set SNI if the host is a literal IP address (IPv4 or
|
||||||
|
// IPv6). This check is coded already crypto.tls.hostnameInSNI, so just
|
||||||
|
// always set ServerName here and let crypto/tls do the filtering.
|
||||||
|
if cfg.SSLSNI {
|
||||||
|
tlsConf.ServerName = cfg.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sslClientCertificates(tlsConf, cfg, home)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootPem, err := sslCertificateAuthority(tlsConf, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sslAppendIntermediates(tlsConf, cfg, rootPem)
|
||||||
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but the
|
||||||
|
// default configuration of older versions has it enabled. Redshift also
|
||||||
|
// initiates renegotiations and cannot be reconfigured.
|
||||||
|
//
|
||||||
|
// TODO: I think this can be removed?
|
||||||
|
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
|
||||||
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
|
client := tls.Client(conn, tlsConf)
|
||||||
|
if verifyCaOnly {
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
certs = client.ConnectionState().PeerCertificates
|
||||||
|
opts = x509.VerifyOptions{Intermediates: x509.NewCertPool(), Roots: tlsConf.RootCAs}
|
||||||
|
)
|
||||||
|
for _, cert := range certs[1:] {
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
_, err = certs[0].Verify(opts)
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslClientCertificates adds the certificate specified in the "sslcert" and
|
||||||
|
//
|
||||||
|
// "sslkey" settings, or if they aren't set, from the .postgresql directory
|
||||||
|
// in the user's home directory. The configured files must exist and have
|
||||||
|
// the correct permissions.
|
||||||
|
func sslClientCertificates(tlsConf *tls.Config, cfg Config, home string) error {
|
||||||
|
if cfg.SSLInline {
|
||||||
|
cert, err := tls.X509KeyPair([]byte(cfg.SSLCert), []byte(cfg.SSLKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Use GetClientCertificate instead of the Certificates field. When
|
||||||
|
// Certificates is set, Go's TLS client only sends the cert if the
|
||||||
|
// server's CertificateRequest includes a CA that issued it. When the
|
||||||
|
// client cert was signed by an intermediate CA but the server only
|
||||||
|
// advertises the root CA, Go skips sending the cert entirely.
|
||||||
|
// GetClientCertificate bypasses this filtering.
|
||||||
|
tlsConf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only load client certificate and key if the setting is not blank, like libpq.
|
||||||
|
if cfg.SSLCert == "" && home != "" {
|
||||||
|
cfg.SSLCert = filepath.Join(home, "postgresql.crt")
|
||||||
|
}
|
||||||
|
if cfg.SSLCert == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := os.Stat(cfg.SSLCert)
|
||||||
|
if err != nil {
|
||||||
|
if pqutil.ErrNotExists(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In libpq, the ssl key is only loaded if the setting is not blank.
|
||||||
|
if cfg.SSLKey == "" && home != "" {
|
||||||
|
cfg.SSLKey = filepath.Join(home, "postgresql.key")
|
||||||
|
}
|
||||||
|
if cfg.SSLKey != "" {
|
||||||
|
err := pqutil.SSLKeyPermissions(cfg.SSLKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(cfg.SSLCert, cfg.SSLKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using GetClientCertificate instead of Certificates per comment above.
|
||||||
|
tlsConf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var testSystemRoots *x509.CertPool
|
||||||
|
|
||||||
|
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
|
||||||
|
func sslCertificateAuthority(tlsConf *tls.Config, cfg Config) ([]byte, error) {
|
||||||
|
// Only load root certificate if not blank, like libpq.
|
||||||
|
if cfg.SSLRootCert == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.SSLRootCert == "system" {
|
||||||
|
// No work to do as system CAs are used by default if RootCAs is nil.
|
||||||
|
tlsConf.RootCAs = testSystemRoots
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.RootCAs = x509.NewCertPool()
|
||||||
|
|
||||||
|
var cert []byte
|
||||||
|
if cfg.SSLInline {
|
||||||
|
cert = []byte(cfg.SSLRootCert)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
cert, err = os.ReadFile(cfg.SSLRootCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
|
||||||
|
return nil, errors.New("pq: couldn't parse pem from sslrootcert")
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslAppendIntermediates appends intermediate CA certificates from sslrootcert
|
||||||
|
// to the client certificate chain. This is needed so the server can verify the
|
||||||
|
// client cert when it was signed by an intermediate CA — without this, the TLS
|
||||||
|
// handshake only sends the leaf client cert.
|
||||||
|
func sslAppendIntermediates(tlsConf *tls.Config, cfg Config, rootPem []byte) {
|
||||||
|
if cfg.SSLRootCert == "" || tlsConf.GetClientCertificate == nil || len(rootPem) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pemData = slices.Clone(rootPem)
|
||||||
|
intermediates [][]byte
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
var block *pem.Block
|
||||||
|
block, pemData = pem.Decode(pemData)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Skip self-signed root CAs; only append intermediates.
|
||||||
|
if cert.IsCA && !bytes.Equal(cert.RawIssuer, cert.RawSubject) {
|
||||||
|
intermediates = append(intermediates, block.Bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(intermediates) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the existing GetClientCertificate to append intermediate certs to
|
||||||
|
// the certificate chain returned during the TLS handshake.
|
||||||
|
origGetCert := tlsConf.GetClientCertificate
|
||||||
|
tlsConf.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
cert, err := origGetCert(info)
|
||||||
|
if err != nil {
|
||||||
|
return cert, err
|
||||||
|
}
|
||||||
|
cert.Certificate = append(cert.Certificate, intermediates...)
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
5
vendor/github.com/lib/pq/staticcheck.conf
generated
vendored
Normal file
5
vendor/github.com/lib/pq/staticcheck.conf
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
checks = [
|
||||||
|
'all',
|
||||||
|
'-ST1000', # "Must have at least one package comment"
|
||||||
|
'-ST1003', # "func EnableInfinityTs should be EnableInfinityTS"
|
||||||
|
]
|
||||||
150
vendor/github.com/lib/pq/stmt.go
generated
vendored
Normal file
150
vendor/github.com/lib/pq/stmt.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/lib/pq/internal/proto"
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stmt struct {
|
||||||
|
cn *conn
|
||||||
|
name string
|
||||||
|
rowsHeader
|
||||||
|
colFmtData []byte
|
||||||
|
paramTyps []oid.Oid
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) Close() error {
|
||||||
|
if st.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := st.cn.err.get(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := st.cn.writeBuf(proto.Close)
|
||||||
|
w.byte(proto.Sync)
|
||||||
|
w.string(st.name)
|
||||||
|
err := st.cn.send(w)
|
||||||
|
if err != nil {
|
||||||
|
return st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
err = st.cn.send(st.cn.writeBuf(proto.Sync))
|
||||||
|
if err != nil {
|
||||||
|
return st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, _, err := st.cn.recv1()
|
||||||
|
if err != nil {
|
||||||
|
return st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
if t != proto.CloseComplete {
|
||||||
|
st.cn.err.set(driver.ErrBadConn)
|
||||||
|
return fmt.Errorf("pq: unexpected close response: %q", t)
|
||||||
|
}
|
||||||
|
st.closed = true
|
||||||
|
|
||||||
|
t, r, err := st.cn.recv1()
|
||||||
|
if err != nil {
|
||||||
|
return st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
if t != proto.ReadyForQuery {
|
||||||
|
st.cn.err.set(driver.ErrBadConn)
|
||||||
|
return fmt.Errorf("pq: expected ready for query, but got: %q", t)
|
||||||
|
}
|
||||||
|
st.cn.processReadyForQuery(r)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
return st.query(toNamedValue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) query(v []driver.NamedValue) (*rows, error) {
|
||||||
|
if err := st.cn.err.get(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := st.exec(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, st.cn.handleError(err)
|
||||||
|
}
|
||||||
|
return &rows{
|
||||||
|
cn: st.cn,
|
||||||
|
rowsHeader: st.rowsHeader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) Exec(v []driver.Value) (driver.Result, error) {
|
||||||
|
return st.ExecContext(context.Background(), toNamedValue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) exec(v []driver.NamedValue) error {
|
||||||
|
if debugProto {
|
||||||
|
fmt.Fprintf(os.Stderr, " START stmt.exec\n")
|
||||||
|
defer fmt.Fprintf(os.Stderr, " END stmt.exec\n")
|
||||||
|
}
|
||||||
|
if len(v) >= 65536 {
|
||||||
|
return fmt.Errorf("pq: got %d parameters but PostgreSQL only supports 65535 parameters", len(v))
|
||||||
|
}
|
||||||
|
if len(v) != len(st.paramTyps) {
|
||||||
|
return fmt.Errorf("pq: got %d parameters but the statement requires %d", len(v), len(st.paramTyps))
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := st.cn
|
||||||
|
w := cn.writeBuf(proto.Bind)
|
||||||
|
w.byte(0) // unnamed portal
|
||||||
|
w.string(st.name)
|
||||||
|
|
||||||
|
if cn.cfg.BinaryParameters {
|
||||||
|
err := cn.sendBinaryParameters(w, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.int16(0)
|
||||||
|
w.int16(len(v))
|
||||||
|
for i, x := range v {
|
||||||
|
if x.Value == nil {
|
||||||
|
w.int32(-1)
|
||||||
|
} else {
|
||||||
|
b, err := encode(x.Value, st.paramTyps[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
w.int32(-1)
|
||||||
|
} else {
|
||||||
|
w.int32(len(b))
|
||||||
|
w.bytes(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytes(st.colFmtData)
|
||||||
|
|
||||||
|
w.next(proto.Execute)
|
||||||
|
w.byte(0)
|
||||||
|
w.int32(0)
|
||||||
|
|
||||||
|
w.next(proto.Sync)
|
||||||
|
err := cn.send(w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = cn.readBindResponse()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cn.postExecuteWorkaround()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) NumInput() int {
|
||||||
|
return len(st.paramTyps)
|
||||||
|
}
|
||||||
61
vendor/github.com/wneessen/go-mail/.gitignore
generated
vendored
Normal file
61
vendor/github.com/wneessen/go-mail/.gitignore
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Local testfiles and auth data
|
||||||
|
.auth
|
||||||
|
examples/*
|
||||||
|
|
||||||
|
# SonarQube
|
||||||
|
.scannerwork/
|
||||||
|
|
||||||
|
# IDEA specific ignores
|
||||||
|
# Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
## Coverage data
|
||||||
|
coverage.coverprofile
|
||||||
|
coverage.html
|
||||||
145
vendor/github.com/wneessen/go-mail/.golangci.toml
generated
vendored
Normal file
145
vendor/github.com/wneessen/go-mail/.golangci.toml
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
## SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
version = '2'
|
||||||
|
|
||||||
|
[run]
|
||||||
|
go = '1.24'
|
||||||
|
tests = true
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
enable = [
|
||||||
|
'containedctx',
|
||||||
|
'contextcheck',
|
||||||
|
'decorder',
|
||||||
|
'errname',
|
||||||
|
'errorlint',
|
||||||
|
'gosec',
|
||||||
|
'staticcheck',
|
||||||
|
'whitespace'
|
||||||
|
]
|
||||||
|
|
||||||
|
[linters.exclusions]
|
||||||
|
generated = 'lax'
|
||||||
|
presets = [
|
||||||
|
'comments',
|
||||||
|
'common-false-positives',
|
||||||
|
'legacy',
|
||||||
|
'std-error-handling'
|
||||||
|
]
|
||||||
|
paths = [
|
||||||
|
'examples',
|
||||||
|
'third_party$',
|
||||||
|
'builtin$',
|
||||||
|
'examples$'
|
||||||
|
]
|
||||||
|
|
||||||
|
## An overflow is impossible here
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'random.go'
|
||||||
|
text = 'G115:'
|
||||||
|
|
||||||
|
## These are tests which intentionally do not need any TLS settings
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'client_test.go'
|
||||||
|
text = 'G402:'
|
||||||
|
|
||||||
|
## These are tests which intentionally do not need any TLS settings
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/smtp_test.go'
|
||||||
|
text = 'G402:'
|
||||||
|
|
||||||
|
## We do not dictate a TLS minimum version in the smtp package. go-mail
|
||||||
|
## itself does set sane defaults
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/smtp.go'
|
||||||
|
text = 'G402:'
|
||||||
|
|
||||||
|
## The chance that we write +2 million tests is very low, I think we can
|
||||||
|
## ignore this for the time being
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'client_test.go'
|
||||||
|
text = 'G109:'
|
||||||
|
|
||||||
|
## The chance that we write +2 million tests is very low, I think we can
|
||||||
|
## ignore this for the time being
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/smtp_test.go'
|
||||||
|
text = 'G109:'
|
||||||
|
|
||||||
|
## We inform the user about the deprecated status of CRAM-MD5 and suggest
|
||||||
|
## to use SCRAM-SHA instead
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/auth_cram_md5.go'
|
||||||
|
text = 'G501:'
|
||||||
|
|
||||||
|
## Yes, SHA1 is weak, but in the context of SCRAM it is still considered
|
||||||
|
## secure for specific applications. The user is information about this
|
||||||
|
## in the documentation
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/auth_scram.go'
|
||||||
|
text = 'G505:'
|
||||||
|
|
||||||
|
## Test code for SCRAM-SHA1. Can be ignored.
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'smtp/smtp_test.go'
|
||||||
|
text = 'G505:'
|
||||||
|
|
||||||
|
## These are tests which intentionally do not need any TLS settings
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'quicksend_test.go'
|
||||||
|
text = 'G402:'
|
||||||
|
|
||||||
|
## These are tests which intentionally test SHA1 and SHA256
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
linters = [
|
||||||
|
'gosec'
|
||||||
|
]
|
||||||
|
path = 'internal/pbkdf2/pbkdf2_test.go'
|
||||||
|
text = 'G505:'
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
enable = [
|
||||||
|
'gofmt',
|
||||||
|
'gofumpt'
|
||||||
|
]
|
||||||
|
|
||||||
|
[formatters.exclusions]
|
||||||
|
generated = 'lax'
|
||||||
|
paths = [
|
||||||
|
'examples',
|
||||||
|
'third_party$',
|
||||||
|
'builtin$',
|
||||||
|
'examples$'
|
||||||
|
]
|
||||||
134
vendor/github.com/wneessen/go-mail/CODE_OF_CONDUCT.md
generated
vendored
Normal file
134
vendor/github.com/wneessen/go-mail/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Winni Neessen <winni@neessen.dev>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
github+coc@neessen.net.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
27
vendor/github.com/wneessen/go-mail/CONTRIBUTING.md
generated
vendored
Normal file
27
vendor/github.com/wneessen/go-mail/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
-->
|
||||||
|
|
||||||
|
# How to contribute
|
||||||
|
|
||||||
|
**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||||
|
|
||||||
|
## Guidelines for Pull Requests
|
||||||
|
|
||||||
|
How to get your contributions merged smoothly and quickly.
|
||||||
|
|
||||||
|
* Create **small PRs** that are narrowly focused on **addressing a single concern**. We often times receive PRs that are trying to fix several things at a time, but only one fix is considered acceptable, nothing gets merged and both author's & review's time is wasted. Create more PRs to address different concerns and everyone will be happy.
|
||||||
|
|
||||||
|
* For speculative changes, consider opening an issue and discussing it first.
|
||||||
|
|
||||||
|
* Provide a good **PR description** as a record of **what** change is being made and **why** it was made. Link to a github issue if it exists.
|
||||||
|
|
||||||
|
* Unless your PR is trivial, you should expect there will be reviewer comments that you'll need to address before merging. We expect you to be reasonably responsive to those comments, otherwise the PR will be closed after 2-3 weeks of inactivity.
|
||||||
|
|
||||||
|
* Maintain **clean commit history** and use **meaningful commit messages**. PRs with messy commit history are difficult to review and won't be merged. Use `rebase -i upstream/main` to curate your commit history and/or to bring in latest changes from main (but avoid rebasing in the middle of a code review).
|
||||||
|
|
||||||
|
* Keep your PR up to date with upstream/main (if there are merge conflicts, we can't really merge your change).
|
||||||
|
|
||||||
|
* Exceptions to the rules can be made if there's a compelling reason for doing so.
|
||||||
21
vendor/github.com/wneessen/go-mail/LICENSE
generated
vendored
Normal file
21
vendor/github.com/wneessen/go-mail/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022-2025 The go-mail Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
128
vendor/github.com/wneessen/go-mail/README.md
generated
vendored
Normal file
128
vendor/github.com/wneessen/go-mail/README.md
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
-->
|
||||||
|
|
||||||
|
# go-mail - Easy to use, yet comprehensive library for sending mails with Go
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/wneessen/go-mail)
|
||||||
|
[](https://codecov.io/gh/wneessen/go-mail)
|
||||||
|
[](https://goreportcard.com/report/github.com/wneessen/go-mail)
|
||||||
|
[](https://github.com/avelino/awesome-go)
|
||||||
|
[](https://discord.gg/ysQXkaccXk)
|
||||||
|
[](https://api.reuse.software/info/github.com/wneessen/go-mail)
|
||||||
|
[](https://www.bestpractices.dev/projects/8701)
|
||||||
|
[](https://securityscorecards.dev/viewer/?uri=github.com/wneessen/go-mail)
|
||||||
|
<a href="https://ko-fi.com/D1D24V9IX"><img src="https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/5cbed8a4ae2b88347c06c923_BuyMeACoffee_blue.png" height="20" alt="buy ma a coffee"></a>
|
||||||
|
|
||||||
|
<p style="text-align: center"><img src="./assets/gopher2.svg" width="250" alt="go-mail logo"/></p>
|
||||||
|
|
||||||
|
The main idea of this library was to provide a simple interface for sending mails to
|
||||||
|
my [JS-Mailer](https://github.com/wneessen/js-mailer) project. It quickly evolved into a full-fledged mail library.
|
||||||
|
|
||||||
|
go-mail follows idiomatic Go style and best practice. It has a small dependency footprint by mainly relying on the
|
||||||
|
Go Standard Library and the Go extended packages. It combines a lot of functionality from the standard library to
|
||||||
|
give easy and convenient access to mail and SMTP related tasks.
|
||||||
|
|
||||||
|
In the early days, parts of this library (especially some parts of [msgwriter.go](msgwriter.go)) had been
|
||||||
|
forked/ported from [go-mail/mail](https://github.com/go-mail/mail) and respectively [go-gomail/gomail](https://github.com/go-gomail/gomail). Today
|
||||||
|
most of the ported code has been refactored.
|
||||||
|
|
||||||
|
The `smtp` package of go-mail has been forked from the original Go stdlib's `net/smtp` package and has then been extended
|
||||||
|
by the go-mail team to fit the packages needs (more SMTP Auth methods, logging, concurrency-safety, etc.).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Here are some highlights of go-mail's featureset:
|
||||||
|
|
||||||
|
* [X] Very small dependency footprint (mainly Go Stdlib and Go extended packages)
|
||||||
|
* [X] Modern, idiomatic Go
|
||||||
|
* [X] Sane and secure defaults
|
||||||
|
* [X] Implicit SSL/TLS support
|
||||||
|
* [X] Explicit STARTTLS support with different policies
|
||||||
|
* [X] Makes use of contexts for a better control flow and timeout/cancelation handling
|
||||||
|
* [X] SMTP Auth support
|
||||||
|
* [X] CRAM-MD5
|
||||||
|
* [X] LOGIN
|
||||||
|
* [X] PLAIN
|
||||||
|
* [X] SCRAM-SHA-1/SCRAM-SHA-1-PLUS
|
||||||
|
* [X] SCRAM-SHA-256/SCRAM-SHA-256-PLUS
|
||||||
|
* [X] XOAUTH2
|
||||||
|
* [X] RFC5322 compliant mail address validation
|
||||||
|
* [X] Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, Priority, etc.)
|
||||||
|
* [X] Concurrency-safe reusing the same SMTP connection to send multiple mails
|
||||||
|
* [X] Support for attachments and inline embeds (from file system, `io.Reader`, `embed.FS` or `fs.FS`)
|
||||||
|
* [X] Support for different encodings
|
||||||
|
* [X] Middleware support for 3rd-party libraries to alter mail messages
|
||||||
|
* [X] Support sending mails via a local sendmail command
|
||||||
|
* [X] Support for requestng MDNs (RFC 8098) and DSNs (RFC 1891)
|
||||||
|
* [X] DKIM signature support via [go-mail-middlware](https://github.com/wneessen/go-mail-middleware)
|
||||||
|
* [X] Message object satisfies `io.WriterTo` and `io.Reader` interfaces
|
||||||
|
* [X] Support for Go's `html/template` and `text/template` (as message body, alternative part or attachment/emebed)
|
||||||
|
* [X] Output to file support which allows storing mail messages as e. g. `.eml` files to disk to open them in a MUA
|
||||||
|
* [X] Debug logging of SMTP traffic
|
||||||
|
* [X] Custom error types for delivery errors
|
||||||
|
* [X] Custom dial-context functions for more control over the connection (proxing, DNS hooking, etc.)
|
||||||
|
* [X] Output a go-mail message as EML file and parse EML file into a go-mail message
|
||||||
|
* [X] S/MIME message signing support (RSA and ECDSA)
|
||||||
|
* [X] UNIX domain socket support
|
||||||
|
* [X] Pluggable SMTP error registry for advanced handling of non-RFC-conforming servers
|
||||||
|
|
||||||
|
go-mail works like a programatic email client and provides lots of methods and functionalities you would consider
|
||||||
|
standard in a MUA.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
We aim for good GoDoc documenation in our library which gives you a full API reference. We also provide a more in-depth
|
||||||
|
documentation website at [go-mail.dev](https://go-mail.dev)
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Go evolves quickly and introduces valuable improvements with each release. To balance adopting new features with
|
||||||
|
ensuring stability for our users, we align our support with the official Go release policy. In practice, go-mail
|
||||||
|
will always support the same set of Go versions that the Go team actively maintains.
|
||||||
|
|
||||||
|
Since Go provides two releases per year, this translates into roughly one year of guaranteed compatibility for
|
||||||
|
any given Go version. We encourage users to stay current with supported Go versions to benefit from security
|
||||||
|
updates, performance improvements, and new language features.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
We have a support and general discussion channel on Discord. Find us at: [#go-mail](https://discord.gg/dbfQyC4s) alternatively find us
|
||||||
|
on the [Gophers Slack](https://gophers.slack.com) in #go-mail
|
||||||
|
|
||||||
|
## Middleware
|
||||||
|
The goal of go-mail is to keep it free from 3rd party dependencies and only focus on things a mail library should
|
||||||
|
fulfill. Yet, since version v0.2.8 we've added support for middleware on the `Msg` object, allowing 3rd parties to
|
||||||
|
alter a given mail message to their needs without relying on `go-mail` to support their specific need.
|
||||||
|
|
||||||
|
To get our users started with message middleware, we've created a collection of useful middlewares. It can be
|
||||||
|
found in a seperate repository: [go-mail-middlware](https://github.com/wneessen/go-mail-middleware).
|
||||||
|
|
||||||
|
## Merch
|
||||||
|
Thanks to our wonderful friends at [HelloTux](https://www.hellotux.com) we can offer great go-mail merchandising. All merch articles are embroidery
|
||||||
|
to provide the best and most long-lasting quality possible.
|
||||||
|
|
||||||
|
If you want to support the open source community and represent your favourite Go mail library with some cool drip, check out our merch shop at:
|
||||||
|
[https://www.hellotux.com/go-mail](https://www.hellotux.com/go-mail).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
We provide example code in both our GoDocs as well as on our official Website (see [Documentation](#documentation)). For a quick start into go-mail
|
||||||
|
check out our [Getting started](https://go-mail.dev/getting-started/introduction/) guide.
|
||||||
|
|
||||||
|
## Authors/Contributors
|
||||||
|
go-mail was initially created and developed by [Winni Neessen](https://github.com/wneessen/), but over time a lot of amazing people
|
||||||
|
contributed ot the project. Big thanks to all of them for improving the go-mail project (be it writing code, testing
|
||||||
|
code, reviewing code, writing documenation or helping to translate the website):
|
||||||
|
|
||||||
|
<a href="https://github.com/wneessen/go-mail/graphs/contributors">
|
||||||
|
<img alt="image of contributors" src="https://contrib.rocks/image?repo=wneessen/go-mail" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
A huge thank you also goes to [Maria Letta](https://github.com/MariaLetta) for designing our super cool go-mail logo!
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
We sincerely thank our amazing sponsors for their generous support! Your contributions do not go unnoticed and helps
|
||||||
|
keeping up the project!
|
||||||
|
|
||||||
|
* [kolaente](https://github.com/kolaente)
|
||||||
14
vendor/github.com/wneessen/go-mail/REUSE.toml
generated
vendored
Normal file
14
vendor/github.com/wneessen/go-mail/REUSE.toml
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# SPDX-FileCopyrightText: Copyright (c) The go-mail Authors
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
version = 1
|
||||||
|
SPDX-PackageName = "go-mail"
|
||||||
|
SPDX-PackageSupplier = "The go-mail Authors"
|
||||||
|
SPDX-PackageDownloadLocation = "https://github.com/wneessen/go-mail"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = ["testdata/**"]
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "The go-mail Authors"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
38
vendor/github.com/wneessen/go-mail/SECURITY.md
generated
vendored
Normal file
38
vendor/github.com/wneessen/go-mail/SECURITY.md
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
To report (possible) security issues in go-mail, please either send a mail to
|
||||||
|
[security@go-mail.dev](mailto:security@go-mail.dev) or use Github's
|
||||||
|
[private reporting feature](https://github.com/wneessen/go-mail/security/advisories/new).
|
||||||
|
Reports are always welcome. Even if you are not 100% certain that a specific issue you found
|
||||||
|
counts as a security issue, we'd love to hear the details, so we can figure out together if
|
||||||
|
the issue in question needds to be addressed.
|
||||||
|
|
||||||
|
Typically, you will receive an answer within a day or even within a few hours.
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
You can send OpenPGP/GPG encrpyted mails to the [security@go-mail.dev](mailto:security@go-mail.dev) address.
|
||||||
|
|
||||||
|
OpenPGP/GPG public key:
|
||||||
|
```
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
xjMEY8RwPBYJKwYBBAHaRw8BAQdAiLsW7pv+CCMq5Ol0hbIB1HnJI97u3zJw
|
||||||
|
Wslr7GJzgOzNK3NlY3VyaXR5QGdvLW1haWwuZGV2IDxzZWN1cml0eUBnby1t
|
||||||
|
YWlsLmRldj7CjAQQFgoAPgUCY8RwPAQLCQcICRCgTBOxf8keAAMVCAoEFgAC
|
||||||
|
AQIZAQIbAwIeARYhBAoWEB7Y0bE7zcIOuaBME7F/yR4AAAByugD9HabWXsyD
|
||||||
|
aPIDrIS97OBA1OLltB4NPT5ba9whKRxTEmMBALBiB2ML4ZTrjLqI6UbGkhJq
|
||||||
|
mWeMtvmU0chZT7WNBO0PzjgEY8RwPBIKKwYBBAGXVQEFAQEHQGDEccz6gvl5
|
||||||
|
t8cMMb/Dy2l0elRZL+Nd0gOhnbWMWlArAwEIB8J4BBgWCAAqBQJjxHA8CRCg
|
||||||
|
TBOxf8keAAIbDBYhBAoWEB7Y0bE7zcIOuaBME7F/yR4AAADaMwD9EvEA3NSN
|
||||||
|
NtdSaeL/euh6oRRiCjKzh5bIqZiQXqMlIOoBAJvPE2facs8MISwTtDoHW0sD
|
||||||
|
WdOs3yBpGlGCs5WEqvQH
|
||||||
|
=zn96
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
```
|
||||||
229
vendor/github.com/wneessen/go-mail/auth.go
generated
vendored
Normal file
229
vendor/github.com/wneessen/go-mail/auth.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMTPAuthType is a type wrapper for a string type. It represents the type of SMTP authentication
|
||||||
|
// mechanism to be used.
|
||||||
|
type SMTPAuthType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954.
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc4954/
|
||||||
|
//
|
||||||
|
// CRAM-MD5 is not secure by modern standards. The vulnerabilities of MD5 and the lack of
|
||||||
|
// advanced security features make it inappropriate for protecting sensitive communications
|
||||||
|
// today.
|
||||||
|
//
|
||||||
|
// It was recommended to deprecate the standard in 20 November 2008. As an alternative it
|
||||||
|
// recommends e.g. SCRAM or SASL Plain protected by TLS instead.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-sasl-crammd5-to-historic-00.html
|
||||||
|
SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5"
|
||||||
|
|
||||||
|
// SMTPAuthCustom is a custom SMTP AUTH mechanism provided by the user. If a user provides
|
||||||
|
// a custom smtp.Auth function to the Client, the Client will its smtpAuthType to this type.
|
||||||
|
//
|
||||||
|
// Do not use this SMTPAuthType without setting a custom smtp.Auth function on the Client.
|
||||||
|
SMTPAuthCustom SMTPAuthType = "CUSTOM"
|
||||||
|
|
||||||
|
// SMTPAuthLogin is the "LOGIN" SASL authentication mechanism. This authentication mechanism
|
||||||
|
// does not have an official RFC that could be followed. There is a spec by Microsoft and an
|
||||||
|
// IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which
|
||||||
|
// automatically matches the MS spec.
|
||||||
|
//
|
||||||
|
// Since the "LOGIN" SASL authentication mechanism transmits the username and password in
|
||||||
|
// plaintext over the internet connection, we only allow this mechanism over a TLS secured
|
||||||
|
// connection.
|
||||||
|
//
|
||||||
|
// https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
|
||||||
|
SMTPAuthLogin SMTPAuthType = "LOGIN"
|
||||||
|
|
||||||
|
// SMTPAuthLoginNoEnc is the "LOGIN" SASL authentication mechanism. This authentication mechanism
|
||||||
|
// does not have an official RFC that could be followed. There is a spec by Microsoft and an
|
||||||
|
// IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which
|
||||||
|
// automatically matches the MS spec.
|
||||||
|
//
|
||||||
|
// Since the "LOGIN" SASL authentication mechanism transmits the username and password in
|
||||||
|
// plaintext over the internet connection, by default we only allow this mechanism over
|
||||||
|
// a TLS secured connection. This authentiation mechanism overrides this default and will
|
||||||
|
// allow LOGIN authentication via an unencrypted channel. This can be useful if the
|
||||||
|
// connection has already been secured in a different way (e. g. a SSH tunnel)
|
||||||
|
//
|
||||||
|
// Note: Use this authentication method with caution. If used in the wrong way, you might
|
||||||
|
// expose your authentication information over unencrypted channels!
|
||||||
|
//
|
||||||
|
// https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
|
||||||
|
SMTPAuthLoginNoEnc SMTPAuthType = "LOGIN-NOENC"
|
||||||
|
|
||||||
|
// SMTPAuthNoAuth is equivalent to performing no authentication at all. It is a convenience
|
||||||
|
// option and should not be used. Instead, for mail servers that do no support/require
|
||||||
|
// authentication, the Client should not be passed the WithSMTPAuth option at all.
|
||||||
|
SMTPAuthNoAuth SMTPAuthType = "NOAUTH"
|
||||||
|
|
||||||
|
// SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616.
|
||||||
|
//
|
||||||
|
// Since the "PLAIN" SASL authentication mechanism transmits the username and password in
|
||||||
|
// plaintext over the internet connection, we only allow this mechanism over a TLS secured
|
||||||
|
// connection.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc4616/
|
||||||
|
SMTPAuthPlain SMTPAuthType = "PLAIN"
|
||||||
|
|
||||||
|
// SMTPAuthPlainNoEnc is the "PLAIN" authentication mechanism as described in RFC 4616.
|
||||||
|
//
|
||||||
|
// Since the "PLAIN" SASL authentication mechanism transmits the username and password in
|
||||||
|
// plaintext over the internet connection, by default we only allow this mechanism over
|
||||||
|
// a TLS secured connection. This authentiation mechanism overrides this default and will
|
||||||
|
// allow PLAIN authentication via an unencrypted channel. This can be useful if the
|
||||||
|
// connection has already been secured in a different way (e. g. a SSH tunnel)
|
||||||
|
//
|
||||||
|
// Note: Use this authentication method with caution. If used in the wrong way, you might
|
||||||
|
// expose your authentication information over unencrypted channels!
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc4616/
|
||||||
|
SMTPAuthPlainNoEnc SMTPAuthType = "PLAIN-NOENC"
|
||||||
|
|
||||||
|
// SMTPAuthXOAUTH2 is the "XOAUTH2" SASL authentication mechanism.
|
||||||
|
// https://developers.google.com/gmail/imap/xoauth2-protocol
|
||||||
|
SMTPAuthXOAUTH2 SMTPAuthType = "XOAUTH2"
|
||||||
|
|
||||||
|
// SMTPAuthSCRAMSHA1 is the "SCRAM-SHA-1" SASL authentication mechanism as described in RFC 5802.
|
||||||
|
//
|
||||||
|
// SCRAM-SHA-1 is still considered secure for certain applications, particularly when used as part
|
||||||
|
// of a challenge-response authentication mechanism (as we use it). However, it is generally
|
||||||
|
// recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known
|
||||||
|
// vulnerabilities in other contexts, although it remains effective in HMAC constructions.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5802
|
||||||
|
SMTPAuthSCRAMSHA1 SMTPAuthType = "SCRAM-SHA-1"
|
||||||
|
|
||||||
|
// SMTPAuthSCRAMSHA1PLUS is the "SCRAM-SHA-1-PLUS" SASL authentication mechanism as described in RFC 5802.
|
||||||
|
//
|
||||||
|
// SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and
|
||||||
|
// to guarantee that the integrity of the transport layer is preserved throughout the authentication
|
||||||
|
// process. Therefore we only allow this mechanism over a TLS secured connection.
|
||||||
|
//
|
||||||
|
// SCRAM-SHA-1-PLUS is still considered secure for certain applications, particularly when used as part
|
||||||
|
// of a challenge-response authentication mechanism (as we use it). However, it is generally
|
||||||
|
// recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known
|
||||||
|
// vulnerabilities in other contexts, although it remains effective in HMAC constructions.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5802
|
||||||
|
SMTPAuthSCRAMSHA1PLUS SMTPAuthType = "SCRAM-SHA-1-PLUS"
|
||||||
|
|
||||||
|
// SMTPAuthSCRAMSHA256 is the "SCRAM-SHA-256" SASL authentication mechanism as described in RFC 7677.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7677
|
||||||
|
SMTPAuthSCRAMSHA256 SMTPAuthType = "SCRAM-SHA-256"
|
||||||
|
|
||||||
|
// SMTPAuthSCRAMSHA256PLUS is the "SCRAM-SHA-256-PLUS" SASL authentication mechanism as described in RFC 7677.
|
||||||
|
//
|
||||||
|
// SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and
|
||||||
|
// to guarantee that the integrity of the transport layer is preserved throughout the authentication
|
||||||
|
// process. Therefore we only allow this mechanism over a TLS secured connection.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7677
|
||||||
|
SMTPAuthSCRAMSHA256PLUS SMTPAuthType = "SCRAM-SHA-256-PLUS"
|
||||||
|
|
||||||
|
// SMTPAuthAutoDiscover is a mechanism that dynamically discovers all authentication mechanisms
|
||||||
|
// supported by the SMTP server and selects the strongest available one.
|
||||||
|
//
|
||||||
|
// This type simplifies authentication by automatically negotiating the most secure mechanism
|
||||||
|
// offered by the server, based on a predefined security ranking. For instance, mechanisms like
|
||||||
|
// SCRAM-SHA-256(-PLUS) or XOAUTH2 are prioritized over weaker mechanisms such as CRAM-MD5 or PLAIN.
|
||||||
|
//
|
||||||
|
// The negotiation process ensures that mechanisms requiring additional capabilities (e.g.,
|
||||||
|
// SCRAM-SHA-X-PLUS with TLS channel binding) are only selected when the necessary prerequisites
|
||||||
|
// are in place, such as an active TLS-secured connection.
|
||||||
|
//
|
||||||
|
// By automating mechanism selection, SMTPAuthAutoDiscover minimizes configuration effort while
|
||||||
|
// maximizing security and compatibility with a wide range of SMTP servers.
|
||||||
|
SMTPAuthAutoDiscover SMTPAuthType = "AUTODISCOVER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMTP Auth related static errors
|
||||||
|
var (
|
||||||
|
// ErrPlainAuthNotSupported is returned when the server does not support the "PLAIN" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrPlainAuthNotSupported = errors.New("server does not support SMTP AUTH type: PLAIN")
|
||||||
|
|
||||||
|
// ErrLoginAuthNotSupported is returned when the server does not support the "LOGIN" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrLoginAuthNotSupported = errors.New("server does not support SMTP AUTH type: LOGIN")
|
||||||
|
|
||||||
|
// ErrCramMD5AuthNotSupported is returned when the server does not support the "CRAM-MD5" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrCramMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: CRAM-MD5")
|
||||||
|
|
||||||
|
// ErrXOauth2AuthNotSupported is returned when the server does not support the "XOAUTH2" schema.
|
||||||
|
ErrXOauth2AuthNotSupported = errors.New("server does not support SMTP AUTH type: XOAUTH2")
|
||||||
|
|
||||||
|
// ErrSCRAMSHA1AuthNotSupported is returned when the server does not support the "SCRAM-SHA-1" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrSCRAMSHA1AuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-1")
|
||||||
|
|
||||||
|
// ErrSCRAMSHA1PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-1-PLUS" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrSCRAMSHA1PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-1-PLUS")
|
||||||
|
|
||||||
|
// ErrSCRAMSHA256AuthNotSupported is returned when the server does not support the "SCRAM-SHA-256" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrSCRAMSHA256AuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256")
|
||||||
|
|
||||||
|
// ErrSCRAMSHA256PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-256-PLUS" SMTP
|
||||||
|
// authentication type.
|
||||||
|
ErrSCRAMSHA256PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256-PLUS")
|
||||||
|
|
||||||
|
// ErrNoSupportedAuthDiscovered is returned when the SMTP Auth AutoDiscover process fails to identify
|
||||||
|
// any supported authentication mechanisms offered by the server.
|
||||||
|
ErrNoSupportedAuthDiscovered = errors.New("SMTP Auth autodiscover was not able to detect a supported " +
|
||||||
|
"authentication mechanism")
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalString satisfies the fig.StringUnmarshaler interface for the SMTPAuthType type
|
||||||
|
// https://pkg.go.dev/github.com/kkyr/fig#StringUnmarshaler
|
||||||
|
func (sa *SMTPAuthType) UnmarshalString(value string) error {
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "auto", "autodiscover", "autodiscovery":
|
||||||
|
*sa = SMTPAuthAutoDiscover
|
||||||
|
case "cram-md5", "crammd5", "cram":
|
||||||
|
*sa = SMTPAuthCramMD5
|
||||||
|
case "custom":
|
||||||
|
*sa = SMTPAuthCustom
|
||||||
|
case "login":
|
||||||
|
*sa = SMTPAuthLogin
|
||||||
|
case "login-noenc":
|
||||||
|
*sa = SMTPAuthLoginNoEnc
|
||||||
|
case "none", "noauth", "no":
|
||||||
|
*sa = SMTPAuthNoAuth
|
||||||
|
case "plain":
|
||||||
|
*sa = SMTPAuthPlain
|
||||||
|
case "plain-noenc":
|
||||||
|
*sa = SMTPAuthPlainNoEnc
|
||||||
|
case "scram-sha-1", "scram-sha1", "scramsha1":
|
||||||
|
*sa = SMTPAuthSCRAMSHA1
|
||||||
|
case "scram-sha-1-plus", "scram-sha1-plus", "scramsha1plus":
|
||||||
|
*sa = SMTPAuthSCRAMSHA1PLUS
|
||||||
|
case "scram-sha-256", "scram-sha256", "scramsha256":
|
||||||
|
*sa = SMTPAuthSCRAMSHA256
|
||||||
|
case "scram-sha-256-plus", "scram-sha256-plus", "scramsha256plus":
|
||||||
|
*sa = SMTPAuthSCRAMSHA256PLUS
|
||||||
|
case "xoauth2", "oauth2":
|
||||||
|
*sa = SMTPAuthXOAUTH2
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported SMTP auth type: %s", value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
94
vendor/github.com/wneessen/go-mail/b64linebreaker.go
generated
vendored
Normal file
94
vendor/github.com/wneessen/go-mail/b64linebreaker.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newlineBytes is a byte slice representation of the SingleNewLine constant used for line breaking
|
||||||
|
// in encoding processes.
|
||||||
|
var newlineBytes = []byte(SingleNewLine)
|
||||||
|
|
||||||
|
// base64LineBreaker handles base64 encoding with the insertion of new lines after a certain number
|
||||||
|
// of characters.
|
||||||
|
//
|
||||||
|
// This struct is used to manage base64 encoding while ensuring that new lines are inserted after
|
||||||
|
// reaching a specific line length. It satisfies the io.WriteCloser interface.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// - https://datatracker.ietf.org/doc/html/rfc2045 (Base64 and line length limitations)
|
||||||
|
type base64LineBreaker struct {
|
||||||
|
line [MaxBodyLength]byte
|
||||||
|
used int
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes data to the base64LineBreaker, ensuring lines do not exceed MaxBodyLength.
|
||||||
|
//
|
||||||
|
// This method writes the provided data to the base64LineBreaker. It ensures that the written
|
||||||
|
// lines do not exceed the MaxBodyLength. If the data exceeds the limit, it handles the
|
||||||
|
// continuation by splitting the data and writing new lines as necessary.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - data: A byte slice containing the data to be written.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - numBytes: The number of bytes written.
|
||||||
|
// - err: An error if one occurred during the write operation.
|
||||||
|
func (l *base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
||||||
|
if l.out == nil {
|
||||||
|
err = errors.New("no io.Writer set for base64LineBreaker")
|
||||||
|
return numBytes, err
|
||||||
|
}
|
||||||
|
if l.used+len(data) < MaxBodyLength {
|
||||||
|
copy(l.line[l.used:], data)
|
||||||
|
l.used += len(data)
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = l.out.Write(l.line[0:l.used])
|
||||||
|
if err != nil {
|
||||||
|
return numBytes, err
|
||||||
|
}
|
||||||
|
excess := MaxBodyLength - l.used
|
||||||
|
l.used = 0
|
||||||
|
|
||||||
|
numBytes, err = l.out.Write(data[0:excess])
|
||||||
|
if err != nil {
|
||||||
|
return numBytes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = l.out.Write(newlineBytes)
|
||||||
|
if err != nil {
|
||||||
|
return numBytes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, err = l.Write(data[excess:]) // recurse
|
||||||
|
numBytes += n
|
||||||
|
return numBytes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close finalizes the base64LineBreaker, writing any remaining buffered data and appending a newline.
|
||||||
|
//
|
||||||
|
// This method ensures that any remaining data in the buffer is written to the output and appends
|
||||||
|
// a newline. It is used to finalize the base64LineBreaker and should be called when no more data
|
||||||
|
// is expected to be written.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - err: An error if one occurred during the final write operation.
|
||||||
|
func (l *base64LineBreaker) Close() (err error) {
|
||||||
|
if l.used > 0 {
|
||||||
|
_, err = l.out.Write(l.line[0:l.used])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = l.out.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
1655
vendor/github.com/wneessen/go-mail/client.go
generated
vendored
Normal file
1655
vendor/github.com/wneessen/go-mail/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/wneessen/go-mail/codecov.yml
generated
vendored
Normal file
22
vendor/github.com/wneessen/go-mail/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# SPDX-FileCopyrightText: The go-mail Authors
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
target: 95%
|
||||||
|
threshold: 2%
|
||||||
|
base: auto
|
||||||
|
if_ci_failed: error
|
||||||
|
only_pulls: false
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
target: 95%
|
||||||
|
base: auto
|
||||||
|
if_ci_failed: error
|
||||||
|
threshold: 2%
|
||||||
|
|
||||||
|
comment:
|
||||||
|
require_changes: true
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user