This commit is contained in:
Vicente Ferrari Smith 2026-04-08 21:34:29 +02:00
parent ffa5cd9cd7
commit f380dfe58d
238 changed files with 151817 additions and 41 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.envrc

View File

@ -1,4 +1,49 @@
.PHONY: run
run:
include .envrc
## 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"
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/api Executable file

Binary file not shown.

BIN
bin/linux_amd64/api Executable file

Binary file not shown.

View File

@ -4,10 +4,13 @@ import (
"context"
"log"
"strings"
"expvar"
"runtime"
//"html/template"
"flag"
"net/http"
"fmt"
"github.com/gorilla/websocket"
@ -23,7 +26,8 @@ import (
"party.at/party/internal/jsonlog"
)
const version = "1.0.0"
var version string
var buildTime string
type config struct {
port int
@ -102,8 +106,16 @@ func main() {
return nil
})
displayVersion := flag.Bool("version", false, "Display version and exit")
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)
log.Printf("%s\n", cfg.db.dsn)
@ -115,6 +127,20 @@ func main() {
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{
config: cfg,
logger: logger,

View File

@ -8,6 +8,10 @@ import (
"time"
"strings"
"errors"
"expvar"
"strconv"
"github.com/felixge/httpsnoop"
"golang.org/x/time/rate"
"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.
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)
})
}

View File

@ -2,6 +2,7 @@ package main
import (
"net/http"
"expvar"
"github.com/julienschmidt/httprouter"
)
@ -34,5 +35,7 @@ func (app *application) routes() http.Handler {
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
View File

@ -3,16 +3,13 @@ module party.at/party
go 1.25.0
require (
github.com/felixge/httpsnoop v1.0.4
github.com/gorilla/websocket v1.5.3
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 (
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
)
require golang.org/x/text v0.29.0 // indirect

29
go.sum
View File

@ -1,41 +1,16 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
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/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/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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/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/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/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/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
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
View File

19
vendor/github.com/felixge/httpsnoop/LICENSE.txt generated vendored Normal file
View 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
View 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
View 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.
[![Go Reference](https://pkg.go.dev/badge/github.com/felixge/httpsnoop.svg)](https://pkg.go.dev/github.com/felixge/httpsnoop)
[![Build Status](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml/badge.svg)](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
View 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
View 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

View 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
}
}

View 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
View 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
View 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
View 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
View File

@ -0,0 +1,33 @@
# Gorilla WebSocket
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](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
View 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
View 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

File diff suppressed because it is too large Load Diff

227
vendor/github.com/gorilla/websocket/doc.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,300 @@
# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.svg?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![Coverage Status](https://coveralls.io/repos/github/julienschmidt/httprouter/badge.svg?branch=master)](https://coveralls.io/github/julienschmidt/httprouter?branch=master) [![GoDoc](https://godoc.org/github.com/julienschmidt/httprouter?status.svg)](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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
*.sh text eol=lf

6
vendor/github.com/lib/pq/.gitignore generated vendored Normal file
View File

@ -0,0 +1,6 @@
.db
*.test
*~
*.swp
.idea
.vscode

242
vendor/github.com/lib/pq/CHANGELOG.md generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

226
vendor/github.com/lib/pq/conn_go18.go generated vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

337
vendor/github.com/lib/pq/copy.go generated vendored Normal file
View 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 := &copyin{
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
View 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
View 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
View 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
View 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
View 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))
}

View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
View 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()
}

View 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
View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
[![GoDoc](https://godoc.org/github.com/wneessen/go-mail?status.svg)](https://pkg.go.dev/github.com/wneessen/go-mail)
[![codecov](https://codecov.io/gh/wneessen/go-mail/branch/main/graph/badge.svg?token=37KWJV03MR)](https://codecov.io/gh/wneessen/go-mail)
[![Go Report Card](https://goreportcard.com/badge/github.com/wneessen/go-mail)](https://goreportcard.com/report/github.com/wneessen/go-mail)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go)
[![#go-mail on Discord](https://img.shields.io/badge/Discord-%23go%E2%80%93mail-blue.svg)](https://discord.gg/ysQXkaccXk)
[![REUSE status](https://api.reuse.software/badge/github.com/wneessen/go-mail)](https://api.reuse.software/info/github.com/wneessen/go-mail)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8701/badge)](https://www.bestpractices.dev/projects/8701)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/wneessen/go-mail/badge)](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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

22
vendor/github.com/wneessen/go-mail/codecov.yml generated vendored Normal file
View 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