Compare commits

..

6 Commits

Author SHA1 Message Date
WeidiDeng d26cd24a11 fix tests 2024-10-17 16:36:45 +08:00
WeidiDeng 8c803b503c check if body is less than anticipated when there is no error reported 2024-10-17 16:22:17 +08:00
WeidiDeng 7d483ff3ef set temp file limiter for buffered body 2024-10-17 15:58:53 +08:00
WeidiDeng 02b8640e44 set status for the error response 2024-10-17 15:53:26 +08:00
WeidiDeng d395633749 fix caddyfile parsing for php_fastcgi 2024-10-17 15:50:24 +08:00
WeidiDeng 4715bbfd64 initial implementation of buffering for requests with unknown content length for fastcgi 2024-10-17 15:34:13 +08:00
113 changed files with 1094 additions and 3122 deletions
+5 -5
View File
@@ -5,11 +5,11 @@ The Caddy project would like to make sure that it stays on top of all practicall
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |
| -------- | ----------| | ------- | ------------------ |
| 2.latest | ✔️ | | 2.x | ✔️ |
| 1.x | :x: | | 1.x | :x: |
| < 1.x | :x: | | < 1.x | :x: |
## Acceptable Scope ## Acceptable Scope
+2 -3
View File
@@ -143,7 +143,7 @@ jobs:
s390x-test: s390x-test:
name: test (s390x on IBM Z) name: test (s390x on IBM Z)
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps: steps:
- name: Checkout code - name: Checkout code
@@ -194,7 +194,6 @@ jobs:
goreleaser-check: goreleaser-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -217,4 +216,4 @@ jobs:
version: latest version: latest
args: build --single-target --snapshot args: build --single-target --snapshot
env: env:
TAG: ${{ github.head_ref || github.ref_name }} TAG: "master"
+1 -1
View File
@@ -70,4 +70,4 @@ jobs:
continue-on-error: true continue-on-error: true
working-directory: ./cmd/caddy working-directory: ./cmd/caddy
run: | run: |
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
-5
View File
@@ -83,8 +83,6 @@ builds:
- -s -w - -s -w
tags: tags:
- nobadger - nobadger
- nomysql
- nopgx
signs: signs:
- cmd: cosign - cmd: cosign
@@ -192,9 +190,6 @@ nfpms:
preremove: ./caddy-dist/scripts/preremove.sh preremove: ./caddy-dist/scripts/preremove.sh
postremove: ./caddy-dist/scripts/postremove.sh postremove: ./caddy-dist/scripts/postremove.sh
provides:
- httpd
release: release:
github: github:
owner: caddyserver owner: caddyserver
+1 -1
View File
@@ -131,7 +131,7 @@ $ xcaddy build
4. Initialize a Go module: `go mod init caddy` 4. Initialize a Go module: `go mod init caddy`
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name. 5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"` 6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build -tags=nobadger,nomysql,nopgx` 7. Compile: `go build`
+1 -1
View File
@@ -214,7 +214,7 @@ type AdminPermissions struct {
// newAdminHandler reads admin's config and returns an http.Handler suitable // newAdminHandler reads admin's config and returns an http.Handler suitable
// for use in an admin endpoint server, which will be listening on listenAddr. // for use in an admin endpoint server, which will be listening on listenAddr.
func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Context) adminHandler { func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, ctx Context) adminHandler {
muxWrap := adminHandler{mux: http.NewServeMux()} muxWrap := adminHandler{mux: http.NewServeMux()}
// secure the local or remote endpoint respectively // secure the local or remote endpoint respectively
+2 -4
View File
@@ -725,10 +725,8 @@ func Validate(cfg *Config) error {
// Errors are logged along the way, and an appropriate exit // Errors are logged along the way, and an appropriate exit
// code is emitted. // code is emitted.
func exitProcess(ctx context.Context, logger *zap.Logger) { func exitProcess(ctx context.Context, logger *zap.Logger) {
// let the rest of the program know we're quitting; only do it once // let the rest of the program know we're quitting
if !atomic.CompareAndSwapInt32(exiting, 0, 1) { atomic.StoreInt32(exiting, 1)
return
}
// give the OS or service/process manager our 2 weeks' notice: we quit // give the OS or service/process manager our 2 weeks' notice: we quit
if err := notify.Stopping(); err != nil { if err := notify.Stopping(); err != nil {
+2 -2
View File
@@ -423,7 +423,7 @@ func (p *parser) doImport(nesting int) error {
// make path relative to the file of the _token_ being processed rather // make path relative to the file of the _token_ being processed rather
// than current working directory (issue #867) and then use glob to get // than current working directory (issue #867) and then use glob to get
// list of matching filenames // list of matching filenames
absFile, err := caddy.FastAbs(p.Dispenser.File()) absFile, err := filepath.Abs(p.Dispenser.File())
if err != nil { if err != nil {
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err) return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err)
} }
@@ -622,7 +622,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// Tack the file path onto these tokens so errors show the imported file's name // Tack the file path onto these tokens so errors show the imported file's name
// (we use full, absolute path to avoid bugs: issue #1892) // (we use full, absolute path to avoid bugs: issue #1892)
filename, err := caddy.FastAbs(importFile) filename, err := filepath.Abs(importFile)
if err != nil { if err != nil {
return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err) return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err)
} }
+3 -7
View File
@@ -31,7 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server // mapAddressToServerBlocks returns a map of listener address to list of server
// blocks that will be served on that address. To do this, each server block is // blocks that will be served on that address. To do this, each server block is
// expanded so that each one is considered individually, although keys of a // expanded so that each one is considered individually, although keys of a
// server block that share the same address stay grouped together so the config // server block that share the same address stay grouped together so the config
@@ -329,12 +329,8 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
// use a map to prevent duplication // use a map to prevent duplication
listeners := map[string]map[string]struct{}{} listeners := map[string]map[string]struct{}{}
for _, lnCfgVal := range lnCfgVals { for _, lnCfgVal := range lnCfgVals {
for _, lnAddr := range lnCfgVal.addresses { for _, lnHost := range lnCfgVal.addresses {
lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr) networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
if err != nil {
return nil, fmt.Errorf("splitting listener address: %v", err)
}
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err) return nil, fmt.Errorf("parsing network address: %v", err)
} }
+3 -60
View File
@@ -24,7 +24,7 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// parseTLS parses the tls directive. Syntax: // parseTLS parses the tls directive. Syntax:
// //
// tls [<email>|internal|force_automate]|[<cert_file> <key_file>] { // tls [<email>|internal]|[<cert_file> <key_file>] {
// protocols <min> [<max>] // protocols <min> [<max>]
// ciphers <cipher_suites...> // ciphers <cipher_suites...>
// curves <curves...> // curves <curves...>
@@ -107,7 +107,6 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// dns_challenge_override_domain <domain> // dns_challenge_override_domain <domain>
// on_demand // on_demand
// reuse_private_keys // reuse_private_keys
// force_automate
// eab <key_id> <mac_key> // eab <key_id> <mac_key>
// issuer <module_name> [...] // issuer <module_name> [...]
// get_certificate <module_name> [...] // get_certificate <module_name> [...]
@@ -127,7 +126,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
var certManagers []certmagic.Manager var certManagers []certmagic.Manager
var onDemand bool var onDemand bool
var reusePrivateKeys bool var reusePrivateKeys bool
var forceAutomate bool
firstLine := h.RemainingArgs() firstLine := h.RemainingArgs()
switch len(firstLine) { switch len(firstLine) {
@@ -135,10 +133,8 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
case 1: case 1:
if firstLine[0] == "internal" { if firstLine[0] == "internal" {
internalIssuer = new(caddytls.InternalIssuer) internalIssuer = new(caddytls.InternalIssuer)
} else if firstLine[0] == "force_automate" {
forceAutomate = true
} else if !strings.Contains(firstLine[0], "@") { } else if !strings.Contains(firstLine[0], "@") {
return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address") return nil, h.Err("single argument must either be 'internal' or an email address")
} else { } else {
acmeIssuer = &caddytls.ACMEIssuer{ acmeIssuer = &caddytls.ACMEIssuer{
Email: firstLine[0], Email: firstLine[0],
@@ -573,15 +569,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}) })
} }
// if enabled, the names in the site addresses will be
// added to the automation policies
if forceAutomate {
configVals = append(configVals, ConfigValue{
Class: "tls.force_automate",
Value: true,
})
}
// custom certificate selection // custom certificate selection
if len(certSelector.AnyTag) > 0 { if len(certSelector.AnyTag) > 0 {
cp.CertSelection = &certSelector cp.CertSelection = &certSelector
@@ -994,50 +981,6 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
} }
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
case "sampling":
d := h.Dispenser.NewFromNextSegment()
for d.NextArg() {
// consume any tokens on the same line, if any.
}
sampling := &caddy.LogSampling{}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdir := d.Val()
switch subdir {
case "interval":
if !d.NextArg() {
return nil, d.ArgErr()
}
interval, err := time.ParseDuration(d.Val() + "ns")
if err != nil {
return nil, d.Errf("failed to parse interval: %v", err)
}
sampling.Interval = interval
case "first":
if !d.NextArg() {
return nil, d.ArgErr()
}
first, err := strconv.Atoi(d.Val())
if err != nil {
return nil, d.Errf("failed to parse first: %v", err)
}
sampling.First = first
case "thereafter":
if !d.NextArg() {
return nil, d.ArgErr()
}
thereafter, err := strconv.Atoi(d.Val())
if err != nil {
return nil, d.Errf("failed to parse thereafter: %v", err)
}
sampling.Thereafter = thereafter
default:
return nil, d.Errf("unrecognized subdirective: %s", subdir)
}
}
cl.Sampling = sampling
case "core": case "core":
if !h.NextArg() { if !h.NextArg() {
return nil, h.ArgErr() return nil, h.ArgErr()
@@ -62,20 +62,6 @@ func TestLogDirectiveSyntax(t *testing.T) {
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`, output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
expectError: false, expectError: false,
}, },
{
input: `:8080 {
log {
sampling {
interval 2
first 3
thereafter 4
}
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"sampling":{"interval":2,"first":3,"thereafter":4},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
} { } {
adapter := caddyfile.Adapter{ adapter := caddyfile.Adapter{
+28 -71
View File
@@ -15,7 +15,6 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@@ -187,25 +186,12 @@ func (st ServerType) Setup(
return nil, warnings, err return nil, warnings, err
} }
// hoist the metrics config from per-server to global
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
for _, s := range servers {
if s.Metrics != nil {
metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
metrics = &caddyhttp.Metrics{
PerHost: metrics.PerHost || s.Metrics.PerHost,
}
s.Metrics = nil // we don't need it anymore
}
}
// now that each server is configured, make the HTTP app // now that each server is configured, make the HTTP app
httpApp := caddyhttp.App{ httpApp := caddyhttp.App{
HTTPPort: tryInt(options["http_port"], &warnings), HTTPPort: tryInt(options["http_port"], &warnings),
HTTPSPort: tryInt(options["https_port"], &warnings), HTTPSPort: tryInt(options["https_port"], &warnings),
GracePeriod: tryDuration(options["grace_period"], &warnings), GracePeriod: tryDuration(options["grace_period"], &warnings),
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
Metrics: metrics,
Servers: servers, Servers: servers,
} }
@@ -706,16 +692,6 @@ func (st *ServerType) serversFromPairings(
return specificity(iLongestHost) > specificity(jLongestHost) return specificity(iLongestHost) > specificity(jLongestHost)
}) })
// collect all hosts that have a wildcard in them
wildcardHosts := []string{}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
@@ -763,14 +739,6 @@ func (st *ServerType) serversFromPairings(
} }
} }
// collect hosts that are forced to be automated
forceAutomatedNames := make(map[string]struct{})
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range hosts {
forceAutomatedNames[host] = struct{}{}
}
}
// tls: connection policies // tls: connection policies
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok { if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
// tls connection policies // tls connection policies
@@ -802,13 +770,20 @@ func (st *ServerType) serversFromPairings(
} }
// only append this policy if it actually changes something // only append this policy if it actually changes something
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) { if !cp.SettingsEmpty() {
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp) srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
hasCatchAllTLSConnPolicy = len(hosts) == 0 hasCatchAllTLSConnPolicy = len(hosts) == 0
} }
} }
} }
wildcardHosts := []string{}
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
for _, addr := range sblock.parsedKeys { for _, addr := range sblock.parsedKeys {
// if server only uses HTTP port, auto-HTTPS will not apply // if server only uses HTTP port, auto-HTTPS will not apply
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
@@ -824,6 +799,18 @@ func (st *ServerType) serversFromPairings(
} }
} }
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard && addr.Scheme == "https" {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
}
}
// If TLS is specified as directive, it will also result in 1 or more connection policy being created // If TLS is specified as directive, it will also result in 1 or more connection policy being created
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without // Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
// specifying prefix "https://" // specifying prefix "https://"
@@ -840,19 +827,6 @@ func (st *ServerType) serversFromPairings(
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) { (addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
addressQualifiesForTLS = true addressQualifiesForTLS = true
} }
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
}
}
// predict whether auto-HTTPS will add the conn policy for us; if so, we // predict whether auto-HTTPS will add the conn policy for us; if so, we
// may not need to add one for this server // may not need to add one for this server
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy && autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
@@ -1474,9 +1448,9 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
// iterate each pairing of host and path matchers and // iterate each pairing of host and path matchers and
// put them into a map for JSON encoding // put them into a map for JSON encoding
var matcherSets []map[string]caddyhttp.RequestMatcherWithError var matcherSets []map[string]caddyhttp.RequestMatcher
for _, mp := range matcherPairs { for _, mp := range matcherPairs {
matcherSet := make(map[string]caddyhttp.RequestMatcherWithError) matcherSet := make(map[string]caddyhttp.RequestMatcher)
if len(mp.hostm) > 0 { if len(mp.hostm) > 0 {
matcherSet["host"] = mp.hostm matcherSet["host"] = mp.hostm
} }
@@ -1535,17 +1509,12 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
if err != nil { if err != nil {
return err return err
} }
rm, ok := unm.(caddyhttp.RequestMatcher)
if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok { if !ok {
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
return nil
} }
// nolint:staticcheck matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
if rm, ok := unm.(caddyhttp.RequestMatcher); ok { return nil
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
}
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
// if the next token is quoted, we can assume it's not a matcher name // if the next token is quoted, we can assume it's not a matcher name
@@ -1589,7 +1558,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
return nil return nil
} }
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) { func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
msEncoded := make(caddy.ModuleMap) msEncoded := make(caddy.ModuleMap)
for matcherName, val := range matchers { for matcherName, val := range matchers {
jsonBytes, err := json.Marshal(val) jsonBytes, err := json.Marshal(val)
@@ -1669,18 +1638,6 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
return false return false
} }
func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
if len(m) == 0 || len(keys) == 0 {
return false
}
for _, key := range keys {
if _, ok := m[key]; ok {
return true
}
}
return false
}
// specificity returns len(s) minus any wildcards (*) and // specificity returns len(s) minus any wildcards (*) and
// placeholders ({...}). Basically, it's a length count // placeholders ({...}). Basically, it's a length count
// that penalizes the use of wildcards and placeholders. // that penalizes the use of wildcards and placeholders.
+2 -57
View File
@@ -19,12 +19,11 @@ import (
"strconv" "strconv"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
@@ -39,8 +38,7 @@ func init() {
RegisterGlobalOption("fallback_sni", parseOptSingleString) RegisterGlobalOption("fallback_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder) RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("storage", parseOptStorage) RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("storage_check", parseStorageCheck) RegisterGlobalOption("storage_clean_interval", parseOptDuration)
RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
RegisterGlobalOption("renew_interval", parseOptDuration) RegisterGlobalOption("renew_interval", parseOptDuration)
RegisterGlobalOption("ocsp_interval", parseOptDuration) RegisterGlobalOption("ocsp_interval", parseOptDuration)
RegisterGlobalOption("acme_ca", parseOptSingleString) RegisterGlobalOption("acme_ca", parseOptSingleString)
@@ -55,7 +53,6 @@ func init() {
RegisterGlobalOption("local_certs", parseOptTrue) RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString) RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS) RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("metrics", parseMetricsOptions)
RegisterGlobalOption("servers", parseServerOptions) RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions) RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("cert_lifetime", parseOptDuration) RegisterGlobalOption("cert_lifetime", parseOptDuration)
@@ -190,40 +187,6 @@ func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
return storage, nil return storage, nil
} }
func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val != "off" {
return "", d.Errf("storage_check must be 'off'")
}
return val, nil
}
func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val == "off" {
return false, nil
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err)
}
return caddy.Duration(dur), nil
}
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name if !d.Next() { // consume option name
return nil, d.ArgErr() return nil, d.ArgErr()
@@ -483,24 +446,6 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
return val, nil return val, nil
} }
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
d.Next() // consume option name
metrics := new(caddyhttp.Metrics)
for d.NextBlock(0) {
switch d.Val() {
case "per_host":
metrics.PerHost = true
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
return metrics, nil
}
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileMetricsOptions(d)
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) { func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d) return unmarshalCaddyfileServerOptions(d)
} }
@@ -240,7 +240,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
case "metrics": case "metrics":
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
serverOpts.Metrics = new(caddyhttp.Metrics) serverOpts.Metrics = new(caddyhttp.Metrics)
for nesting := d.Nesting(); d.NextBlock(nesting); { for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() { switch d.Val() {
+3 -11
View File
@@ -52,27 +52,19 @@ func NewShorthandReplacer() ShorthandReplacer {
// be used in the Caddyfile, and the right is the replacement. // be used in the Caddyfile, and the right is the replacement.
func placeholderShorthands() []string { func placeholderShorthands() []string {
return []string{ return []string{
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{host}", "{http.request.host}", "{host}", "{http.request.host}",
"{hostport}", "{http.request.hostport}", "{hostport}", "{http.request.hostport}",
"{port}", "{http.request.port}", "{port}", "{http.request.port}",
"{orig_method}", "{http.request.orig_method}",
"{orig_uri}", "{http.request.orig_uri}",
"{orig_path}", "{http.request.orig_uri.path}",
"{orig_dir}", "{http.request.orig_uri.path.dir}",
"{orig_file}", "{http.request.orig_uri.path.file}",
"{orig_query}", "{http.request.orig_uri.query}",
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
"{method}", "{http.request.method}", "{method}", "{http.request.method}",
"{uri}", "{http.request.uri}",
"{path}", "{http.request.uri.path}", "{path}", "{http.request.uri.path}",
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{query}", "{http.request.uri.query}", "{query}", "{http.request.uri.query}",
"{?query}", "{http.request.uri.prefixed_query}",
"{remote}", "{http.request.remote}", "{remote}", "{http.request.remote}",
"{remote_host}", "{http.request.remote.host}", "{remote_host}", "{http.request.remote.host}",
"{remote_port}", "{http.request.remote.port}", "{remote_port}", "{http.request.remote.port}",
"{scheme}", "{http.request.scheme}", "{scheme}", "{http.request.scheme}",
"{uri}", "{http.request.uri}",
"{uuid}", "{http.request.uuid}", "{uuid}", "{http.request.uuid}",
"{tls_cipher}", "{http.request.tls.cipher_suite}", "{tls_cipher}", "{http.request.tls.cipher_suite}",
"{tls_version}", "{http.request.tls.version}", "{tls_version}", "{http.request.tls.version}",
+33 -87
View File
@@ -25,7 +25,7 @@ import (
"strings" "strings"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -92,28 +92,6 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP) tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
} }
// collect all hosts that have a wildcard in them, and arent HTTP
wildcardHosts := []string{}
// hosts that have been explicitly marked to be automated,
// even if covered by another wildcard
forcedAutomatedNames := make(map[string]struct{})
for _, p := range pairings {
var addresses []string
for _, addressWithProtocols := range p.addressesWithProtocols {
addresses = append(addresses, addressWithProtocols.address)
}
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
continue
}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
}
for _, p := range pairings { for _, p := range pairings {
// avoid setting up TLS automation policies for a server that is HTTP-only // avoid setting up TLS automation policies for a server that is HTTP-only
var addresses []string var addresses []string
@@ -137,12 +115,6 @@ func (st ServerType) buildTLSApp(
return nil, warnings, err return nil, warnings, err
} }
// make a plain copy so we can compare whether we made any changes
apCopy, err := newBaseAutomationPolicy(options, warnings, true)
if err != nil {
return nil, warnings, err
}
sblockHosts := sblock.hostsFromKeys(false) sblockHosts := sblock.hostsFromKeys(false)
if len(sblockHosts) == 0 && catchAllAP != nil { if len(sblockHosts) == 0 && catchAllAP != nil {
ap = catchAllAP ap = catchAllAP
@@ -153,13 +125,6 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true ap.OnDemand = true
} }
// collect hosts that are forced to be automated
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range sblockHosts {
forcedAutomatedNames[host] = struct{}{}
}
}
// reuse private keys tls // reuse private keys tls
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok { if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
ap.ReusePrivateKeys = true ap.ReusePrivateKeys = true
@@ -252,21 +217,9 @@ func (st ServerType) buildTLSApp(
catchAllAP = ap catchAllAP = ap
} }
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
sort.Strings(hostsNotHTTP) // solely for deterministic test results
// if the we prefer wildcards and the AP is unchanged,
// then we can skip this AP because it should be covered
// by an AP with a wildcard
if slices.Contains(autoHTTPS, "prefer_wildcard") {
if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) &&
reflect.DeepEqual(ap, apCopy) {
continue
}
}
// associate our new automation policy with this server block's hosts // associate our new automation policy with this server block's hosts
ap.SubjectsRaw = hostsNotHTTP ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
// if a combination of public and internal names were given // if a combination of public and internal names were given
// for this same server block and no issuer was specified, we // for this same server block and no issuer was specified, we
@@ -305,7 +258,6 @@ func (st ServerType) buildTLSApp(
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)} ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
} }
} }
if tlsApp.Automation == nil { if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig) tlsApp.Automation = new(caddytls.AutomationConfig)
} }
@@ -359,16 +311,6 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.OnDemand = onDemand tlsApp.Automation.OnDemand = onDemand
} }
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
if sc, ok := options["storage_check"].(string); ok && sc == "off" {
tlsApp.DisableStorageCheck = true
}
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
if sci, ok := options["storage_clean_interval"].(bool); ok && !sci {
tlsApp.DisableStorageClean = true
}
// set the storage clean interval if configured // set the storage clean interval if configured
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok { if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil { if tlsApp.Automation == nil {
@@ -417,13 +359,6 @@ func (st ServerType) buildTLSApp(
} }
} }
} }
for name := range forcedAutomatedNames {
if slices.Contains(al, name) {
continue
}
al = append(al, name)
}
slices.Sort(al) // to stabilize the adapt output
if len(al) > 0 { if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings) tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
} }
@@ -483,7 +418,10 @@ func (st ServerType) buildTLSApp(
} }
// consolidate automation policies that are the exact same // consolidate automation policies that are the exact same
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies) tlsApp.Automation.Policies = consolidateAutomationPolicies(
tlsApp.Automation.Policies,
slices.Contains(autoHTTPS, "prefer_wildcard"),
)
// ensure automation policies don't overlap subjects (this should be // ensure automation policies don't overlap subjects (this should be
// an error at provision-time as well, but catch it in the adapt phase // an error at provision-time as well, but catch it in the adapt phase
@@ -629,7 +567,7 @@ func newBaseAutomationPolicy(
// consolidateAutomationPolicies combines automation policies that are the same, // consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output. // for a cleaner overall output.
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy { func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy, preferWildcard bool) []*caddytls.AutomationPolicy {
// sort from most specific to least specific; we depend on this ordering // sort from most specific to least specific; we depend on this ordering
sort.SliceStable(aps, func(i, j int) bool { sort.SliceStable(aps, func(i, j int) bool {
if automationPolicyIsSubset(aps[i], aps[j]) { if automationPolicyIsSubset(aps[i], aps[j]) {
@@ -714,6 +652,31 @@ outer:
j-- j--
} }
} }
if preferWildcard {
// remove subjects from i if they're covered by a wildcard in j
iSubjs := aps[i].SubjectsRaw
for iSubj := 0; iSubj < len(iSubjs); iSubj++ {
for jSubj := range aps[j].SubjectsRaw {
if !strings.HasPrefix(aps[j].SubjectsRaw[jSubj], "*.") {
continue
}
if certmagic.MatchWildcard(aps[i].SubjectsRaw[iSubj], aps[j].SubjectsRaw[jSubj]) {
iSubjs = slices.Delete(iSubjs, iSubj, iSubj+1)
iSubj--
break
}
}
}
aps[i].SubjectsRaw = iSubjs
// remove i if it has no subjects left
if len(aps[i].SubjectsRaw) == 0 {
aps = slices.Delete(aps, i, i+1)
i--
continue outer
}
}
} }
} }
@@ -785,20 +748,3 @@ func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
func isTailscaleDomain(name string) bool { func isTailscaleDomain(name string) bool {
return strings.HasSuffix(strings.ToLower(name), ".ts.net") return strings.HasSuffix(strings.ToLower(name), ".ts.net")
} }
func hostsCoveredByWildcard(hosts []string, wildcards []string) bool {
if len(hosts) == 0 || len(wildcards) == 0 {
return false
}
for _, host := range hosts {
for _, wildcard := range wildcards {
if strings.HasPrefix(host, "*.") {
continue
}
if certmagic.MatchWildcard(host, "*."+wildcard) {
return true
}
}
}
return false
}
+1 -1
View File
@@ -35,7 +35,7 @@ func init() {
// If the response is not a JSON config, a config adapter must be specified // If the response is not a JSON config, a config adapter must be specified
// either in the loader config (`adapter`), or in the Content-Type HTTP header // either in the loader config (`adapter`), or in the Content-Type HTTP header
// returned in the HTTP response from the server. The Content-Type header is // returned in the HTTP response from the server. The Content-Type header is
// read just like the admin API's `/load` endpoint. If you don't have control // read just like the admin API's `/load` endpoint. Uf you don't have control
// over the HTTP server (but can still trust its response), you can override // over the HTTP server (but can still trust its response), you can override
// the Content-Type header by setting the `adapter` property in this config. // the Content-Type header by setting the `adapter` property in this config.
type HTTPLoader struct { type HTTPLoader struct {
+4 -6
View File
@@ -6,7 +6,6 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@@ -14,11 +13,10 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3" "github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
smallstepacme "github.com/smallstep/certificates/acme" smallstepacme "github.com/smallstep/certificates/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
) )
const acmeChallengePort = 9081 const acmeChallengePort = 9081
@@ -50,7 +48,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -119,7 +117,7 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
+4 -6
View File
@@ -5,15 +5,13 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"log/slog"
"strings" "strings"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3" "github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v2/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {
@@ -78,7 +76,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -167,7 +165,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: slog.New(zapslog.NewHandler(logger.Core())), Logger: logger,
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -74,9 +74,6 @@ foo.example.com {
} }
], ],
"automatic_https": { "automatic_https": {
"skip_certificates": [
"foo.example.com"
],
"prefer_wildcard": true "prefer_wildcard": true
} }
} }
@@ -1,268 +0,0 @@
{
auto_https prefer_wildcard
}
# Covers two domains
*.one.example.com {
tls {
dns mock
}
respond "one fallback"
}
# Is covered, should not get its own AP
foo.one.example.com {
respond "foo one"
}
# This one has its own tls config so it doesn't get covered (escape hatch)
bar.one.example.com {
respond "bar one"
tls bar@bar.com
}
# Covers nothing but AP gets consolidated with the first
*.two.example.com {
tls {
dns mock
}
respond "two fallback"
}
# Is HTTP so it should not cover
http://*.three.example.com {
respond "three fallback"
}
# Has no wildcard coverage so it gets an AP
foo.three.example.com {
respond "foo three"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo three",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"foo.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"bar.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "bar one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "one fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.two.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "two fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.one.example.com",
"bar.one.example.com"
],
"prefer_wildcard": true
}
},
"srv1": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"host": [
"*.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "three fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"foo.three.example.com"
]
},
{
"subjects": [
"bar.one.example.com"
],
"issuers": [
{
"email": "bar@bar.com",
"module": "acme"
},
{
"ca": "https://acme.zerossl.com/v2/DV90",
"email": "bar@bar.com",
"module": "acme"
}
]
},
{
"subjects": [
"*.one.example.com",
"*.two.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
@@ -21,8 +21,6 @@ encode {
zstd zstd
gzip 5 gzip 5
} }
encode
---------- ----------
{ {
"apps": { "apps": {
@@ -78,17 +76,6 @@ encode
"zstd", "zstd",
"gzip" "gzip"
] ]
},
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode",
"prefer": [
"zstd",
"gzip"
]
} }
] ]
} }
@@ -1,36 +0,0 @@
:80
file_server {
browse {
file_limit 4000
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"browse": {
"file_limit": 4000
},
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
]
}
}
}
}
}
@@ -3,10 +3,6 @@
file_server { file_server {
precompressed zstd br gzip precompressed zstd br gzip
} }
file_server {
precompressed
}
---------- ----------
{ {
"apps": { "apps": {
@@ -34,22 +30,6 @@ file_server {
"br", "br",
"gzip" "gzip"
] ]
},
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"precompressed": {
"br": {},
"gzip": {},
"zstd": {}
},
"precompressed_order": [
"br",
"zstd",
"gzip"
]
} }
] ]
} }
@@ -1,6 +1,6 @@
app.example.com { app.example.com {
forward_auth authelia:9091 { forward_auth authelia:9091 {
uri /api/authz/forward-auth uri /api/verify?rd=https://authelia.example.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
} }
@@ -39,13 +39,6 @@ app.example.com {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@@ -54,104 +47,19 @@ app.example.com {
"set": { "set": {
"Remote-Email": [ "Remote-Email": [
"{http.reverse_proxy.header.Remote-Email}" "{http.reverse_proxy.header.Remote-Email}"
] ],
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Email}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Groups": [ "Remote-Groups": [
"{http.reverse_proxy.header.Remote-Groups}" "{http.reverse_proxy.header.Remote-Groups}"
] ],
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Groups}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Name": [ "Remote-Name": [
"{http.reverse_proxy.header.Remote-Name}" "{http.reverse_proxy.header.Remote-Name}"
] ],
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Name}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-User": [ "Remote-User": [
"{http.reverse_proxy.header.Remote-User}" "{http.reverse_proxy.header.Remote-User}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-User}": [
""
]
}
}
]
}
] ]
} }
] ]
@@ -172,7 +80,7 @@ app.example.com {
}, },
"rewrite": { "rewrite": {
"method": "GET", "method": "GET",
"uri": "/api/authz/forward-auth" "uri": "/api/verify?rd=https://authelia.example.com"
}, },
"upstreams": [ "upstreams": [
{ {
@@ -28,13 +28,6 @@ forward_auth localhost:9000 {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@@ -43,131 +36,22 @@ forward_auth localhost:9000 {
"set": { "set": {
"1": [ "1": [
"{http.reverse_proxy.header.A}" "{http.reverse_proxy.header.A}"
] ],
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.A}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"B": [
"{http.reverse_proxy.header.B}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.B}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"3": [ "3": [
"{http.reverse_proxy.header.C}" "{http.reverse_proxy.header.C}"
] ],
} "5": [
} "{http.reverse_proxy.header.E}"
} ],
], "B": [
"match": [ "{http.reverse_proxy.header.B}"
{ ],
"not": [
{
"vars": {
"{http.reverse_proxy.header.C}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"D": [ "D": [
"{http.reverse_proxy.header.D}" "{http.reverse_proxy.header.D}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.D}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"5": [
"{http.reverse_proxy.header.E}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.E}": [
""
]
}
}
]
}
] ]
} }
] ]
@@ -9,8 +9,6 @@
storage file_system { storage file_system {
root /data root /data
} }
storage_check off
storage_clean_interval off
acme_ca https://example.com acme_ca https://example.com
acme_ca_root /path/to/ca.crt acme_ca_root /path/to/ca.crt
ocsp_stapling off ocsp_stapling off
@@ -75,9 +73,7 @@
} }
} }
}, },
"disable_ocsp_stapling": true, "disable_ocsp_stapling": true
"disable_storage_check": true,
"disable_storage_clean": true
} }
} }
} }
@@ -1,23 +0,0 @@
{
log {
sampling {
interval 300
first 50
thereafter 40
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"sampling": {
"interval": 300,
"first": 50,
"thereafter": 40
}
}
}
}
}
@@ -12,14 +12,10 @@
@images path /images/* @images path /images/*
header @images { header @images {
Cache-Control "public, max-age=3600, stale-while-revalidate=86400" Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
match {
status 200
}
} }
header { header {
+Link "Foo" +Link "Foo"
+Link "Bar" +Link "Bar"
match status 200
} }
header >Set Defer header >Set Defer
header >Replace Deferred Replacement header >Replace Deferred Replacement
@@ -46,11 +42,6 @@
{ {
"handler": "headers", "handler": "headers",
"response": { "response": {
"require": {
"status_code": [
200
]
},
"set": { "set": {
"Cache-Control": [ "Cache-Control": [
"public, max-age=3600, stale-while-revalidate=86400" "public, max-age=3600, stale-while-revalidate=86400"
@@ -145,11 +136,6 @@
"Foo", "Foo",
"Bar" "Bar"
] ]
},
"require": {
"status_code": [
200
]
} }
} }
}, },
@@ -1,45 +0,0 @@
:80 {
log {
sampling {
interval 300
first 50
thereafter 40
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"sampling": {
"interval": 300,
"first": 50,
"thereafter": 40
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
@@ -1,39 +0,0 @@
{
metrics
servers :80 {
metrics {
per_host
}
}
}
:80 {
respond "Hello"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"body": "Hello",
"handler": "static_response"
}
]
}
]
}
},
"metrics": {
"per_host": true
}
}
}
}
@@ -26,11 +26,11 @@
} }
] ]
} }
] ],
"metrics": {
"per_host": true
}
} }
},
"metrics": {
"per_host": true
} }
} }
} }
@@ -8,7 +8,7 @@ route {
} }
not path */ not path */
} }
redir @canonicalPath {orig_path}/{orig_?query} 308 redir @canonicalPath {http.request.orig_uri.path}/ 308
# If the requested file does not exist, try index files # If the requested file does not exist, try index files
@indexFiles { @indexFiles {
@@ -17,7 +17,7 @@ route {
split_path .php split_path .php
} }
} }
rewrite @indexFiles {file_match.relative} rewrite @indexFiles {http.matchers.file.relative}
# Proxy PHP files to the FastCGI responder # Proxy PHP files to the FastCGI responder
@phpFiles { @phpFiles {
@@ -50,7 +50,7 @@ route {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -42,7 +42,7 @@
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -58,7 +58,6 @@
"{http.request.uri.path}/index.php", "{http.request.uri.path}/index.php",
"index.php" "index.php"
], ],
"try_policy": "first_exist_fallback",
"split_path": [ "split_path": [
".php" ".php"
] ]
@@ -33,7 +33,7 @@ php_fastcgi @test localhost:9000
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -73,8 +73,7 @@ php_fastcgi @test localhost:9000
"{http.request.uri.path}", "{http.request.uri.path}",
"{http.request.uri.path}/index.php", "{http.request.uri.path}/index.php",
"index.php" "index.php"
], ]
"try_policy": "first_exist_fallback"
} }
} }
] ]
@@ -43,7 +43,7 @@ php_fastcgi localhost:9000 {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -59,7 +59,6 @@ php_fastcgi localhost:9000 {
"{http.request.uri.path}/index.php5", "{http.request.uri.path}/index.php5",
"index.php5" "index.php5"
], ],
"try_policy": "first_exist_fallback",
"split_path": [ "split_path": [
".php", ".php",
".php5" ".php5"
@@ -46,7 +46,7 @@ php_fastcgi localhost:9000 {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" "{http.request.orig_uri.path}/"
] ]
}, },
"status_code": 308 "status_code": 308
@@ -1,95 +0,0 @@
:8884
php_fastcgi localhost:9000 {
# some php_fastcgi-specific subdirectives
split .php .php5
env VAR1 value1
env VAR2 value2
root /var/www
try_files {path} index.php
dial_timeout 3s
read_timeout 10s
write_timeout 20s
# passed through to reverse_proxy (directive order doesn't matter!)
lb_policy random
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"match": [
{
"file": {
"try_files": [
"{http.request.uri.path}",
"index.php"
],
"try_policy": "first_exist_fallback",
"split_path": [
".php",
".php5"
]
}
}
],
"handle": [
{
"handler": "rewrite",
"uri": "{http.matchers.file.relative}"
}
]
},
{
"match": [
{
"path": [
"*.php",
"*.php5"
]
}
],
"handle": [
{
"handler": "reverse_proxy",
"load_balancing": {
"selection_policy": {
"policy": "random"
}
},
"transport": {
"dial_timeout": 3000000000,
"env": {
"VAR1": "value1",
"VAR2": "value2"
},
"protocol": "fastcgi",
"read_timeout": 10000000000,
"root": "/var/www",
"split_path": [
".php",
".php5"
],
"write_timeout": 20000000000
},
"upstreams": [
{
"dial": "localhost:9000"
}
]
}
]
}
]
}
}
}
}
}
@@ -1,180 +0,0 @@
automated1.example.com {
tls force_automate
respond "Automated!"
}
automated2.example.com {
tls force_automate
respond "Automated!"
}
shadowed.example.com {
respond "Shadowed!"
}
*.example.com {
tls cert.pem key.pem
respond "Wildcard!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"automated1.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"automated2.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"shadowed.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Shadowed!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Wildcard!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"automated1.example.com"
]
}
},
{
"match": {
"sni": [
"automated2.example.com"
]
}
},
{
"match": {
"sni": [
"*.example.com"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"automate": [
"automated1.example.com",
"automated2.example.com"
],
"load_files": [
{
"certificate": "cert.pem",
"key": "key.pem",
"tags": [
"cert0"
]
}
]
}
}
}
}
@@ -1,102 +0,0 @@
subdomain.example.com {
respond "Subdomain!"
}
*.example.com {
tls cert.pem key.pem
respond "Wildcard!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"subdomain.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Subdomain!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Wildcard!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"*.example.com"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"load_files": [
{
"certificate": "cert.pem",
"key": "key.pem",
"tags": [
"cert0"
]
}
]
}
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil
} }
// AppendRecords appends DNS records to the zone. // AppendsRecords appends DNS records to the zone.
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
return nil, nil return nil, nil
} }
+5
View File
@@ -1,3 +1,8 @@
// The below line is required to enable post-quantum key agreement in Go 1.23
// by default without insisting on setting a minimum version of 1.23 in go.mod.
// See https://github.com/caddyserver/caddy/issues/6540#issuecomment-2313094905
//go:debug tlskyber=1
// Copyright 2015 Matthew Holt and The Caddy Authors // Copyright 2015 Matthew Holt and The Caddy Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
+2 -7
View File
@@ -560,15 +560,10 @@ func cmdValidateConfig(fl Flags) (int, error) {
func cmdFmt(fl Flags) (int, error) { func cmdFmt(fl Flags) (int, error) {
configFile := fl.Arg(0) configFile := fl.Arg(0)
configFlag := fl.String("config") if configFile == "" {
if (len(fl.Args()) > 1) || (configFlag != "" && configFile != "") {
return caddy.ExitCodeFailedStartup, fmt.Errorf("fmt does not support multiple files %s %s", configFlag, strings.Join(fl.Args(), " "))
}
if configFile == "" && configFlag == "" {
configFile = "Caddyfile" configFile = "Caddyfile"
} else if configFile == "" {
configFile = configFlag
} }
// as a special case, read from stdin if the file name is "-" // as a special case, read from stdin if the file name is "-"
if configFile == "-" { if configFile == "-" {
input, err := io.ReadAll(os.Stdin) input, err := io.ReadAll(os.Stdin)
+3 -5
View File
@@ -388,7 +388,6 @@ When reading from stdin, the --overwrite flag has no effect: the result
is always printed to stdout. is always printed to stdout.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Configuration file")
cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results") cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results")
cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output") cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output")
cmd.RunE = WrapCommandFuncForCobra(cmdFmt) cmd.RunE = WrapCommandFuncForCobra(cmdFmt)
@@ -410,13 +409,12 @@ latest versions. EXPERIMENTAL: May be changed or removed.
RegisterCommand(Command{ RegisterCommand(Command{
Name: "add-package", Name: "add-package",
Usage: "<package[@version]...>", Usage: "<packages...>",
Short: "Adds Caddy packages (EXPERIMENTAL)", Short: "Adds Caddy packages (EXPERIMENTAL)",
Long: ` Long: `
Downloads an updated Caddy binary with the specified packages (module/plugin) Downloads an updated Caddy binary with the specified packages (module/plugin)
added, with an optional version specified (e.g., "package@version"). Retains added. Retains existing packages. Returns an error if the any of packages are
existing packages. Returns an error if any of the specified packages are already already included. EXPERIMENTAL: May be changed or removed.
included. EXPERIMENTAL: May be changed or removed.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
+9 -49
View File
@@ -46,25 +46,6 @@ func cmdUpgrade(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
} }
func splitModule(arg string) (module, version string, err error) {
const versionSplit = "@"
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
// a version, otherwise we don't know if it's a version separator or part of the file path
lastVersionSplit := strings.LastIndex(arg, versionSplit)
if lastVersionSplit < 0 {
module = arg
} else {
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
}
if module == "" {
err = fmt.Errorf("module name is required")
}
return
}
func cmdAddPackage(fl Flags) (int, error) { func cmdAddPackage(fl Flags) (int, error) {
if len(fl.Args()) == 0 { if len(fl.Args()) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified") return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
@@ -79,15 +60,10 @@ func cmdAddPackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
module, version, err := splitModule(arg) if _, ok := pluginPkgs[arg]; ok {
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
// only allow a version to be specified if it's different from the existing version
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added") return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
} }
pluginPkgs[module] = pluginPackage{Version: version, Path: module} pluginPkgs[arg] = struct{}{}
} }
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
@@ -107,11 +83,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
module, _, err := splitModule(arg) if _, ok := pluginPkgs[arg]; !ok {
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
if _, ok := pluginPkgs[module]; !ok {
// package does not exist // package does not exist
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added") return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
} }
@@ -121,7 +93,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
} }
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) { func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
l := caddy.Log() l := caddy.Log()
thisExecPath, err := os.Executable() thisExecPath, err := os.Executable()
@@ -148,8 +120,8 @@ func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
"os": {runtime.GOOS}, "os": {runtime.GOOS},
"arch": {runtime.GOARCH}, "arch": {runtime.GOARCH},
} }
for _, pkgInfo := range pluginPkgs { for pkg := range pluginPkgs {
qs.Add("p", pkgInfo.String()) qs.Add("p", pkg)
} }
// initiate the build // initiate the build
@@ -304,14 +276,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
return resp, nil return resp, nil
} }
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) { func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) {
pluginPkgs := make(map[string]pluginPackage) pluginPkgs := make(map[string]struct{})
for _, mod := range modules { for _, mod := range modules {
if mod.goModule.Replace != nil { if mod.goModule.Replace != nil {
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s", return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
mod.goModule.Path, mod.goModule.Replace.Path) mod.goModule.Path, mod.goModule.Replace.Path)
} }
pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path} pluginPkgs[mod.goModule.Path] = struct{}{}
} }
return pluginPkgs, nil return pluginPkgs, nil
} }
@@ -340,15 +312,3 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er
} }
const downloadPath = "https://caddyserver.com/api/download" const downloadPath = "https://caddyserver.com/api/download"
type pluginPackage struct {
Version string
Path string
}
func (p pluginPackage) String() string {
if p.Version == "" {
return p.Path
}
return p.Path + "@" + p.Version
}
-9
View File
@@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
@@ -191,20 +190,12 @@ func cmdExportStorage(fl Flags) (int, error) {
for _, k := range keys { for _, k := range keys {
info, err := stor.Stat(ctx, k) info, err := stor.Stat(ctx, k)
if err != nil { if err != nil {
if errors.Is(err, fs.ErrNotExist) {
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
continue
}
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
if info.IsTerminal { if info.IsTerminal {
v, err := stor.Load(ctx, k) v, err := stor.Load(ctx, k)
if err != nil { if err != nil {
if errors.Is(err, fs.ErrNotExist) {
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
continue
}
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
+6 -4
View File
@@ -110,8 +110,6 @@ func (ctx *Context) GetMetricsRegistry() *prometheus.Registry {
func (ctx *Context) initMetrics() { func (ctx *Context) initMetrics() {
ctx.metricsRegistry.MustRegister( ctx.metricsRegistry.MustRegister(
collectors.NewBuildInfoCollector(), collectors.NewBuildInfoCollector(),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
adminMetrics.requestCount, adminMetrics.requestCount,
adminMetrics.requestErrors, adminMetrics.requestErrors,
globalMetrics.configSuccess, globalMetrics.configSuccess,
@@ -557,8 +555,12 @@ func (ctx Context) Slogger() *slog.Logger {
if mod == nil { if mod == nil {
return slog.New(zapslog.NewHandler(Log().Core(), nil)) return slog.New(zapslog.NewHandler(Log().Core(), nil))
} }
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
zapslog.WithName(string(mod.CaddyModule().ID)), return slog.New(zapslog.NewHandler(
ctx.cfg.Logging.Logger(mod).Core(),
&zapslog.HandlerOptions{
LoggerName: string(mod.CaddyModule().ID),
},
)) ))
} }
-39
View File
@@ -1,39 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !windows
package caddy
import (
"os"
"path/filepath"
)
// FastAbs is an optimized version of filepath.Abs for Unix systems,
// since we don't expect the working directory to ever change once
// Caddy is running. Avoid the os.Getwd() syscall overhead.
// It's overall the same as stdlib's implementation, the difference
// being cached working directory.
func FastAbs(path string) (string, error) {
if filepath.IsAbs(path) {
return filepath.Clean(path), nil
}
if wderr != nil {
return "", wderr
}
return filepath.Join(wd, path), nil
}
var wd, wderr = os.Getwd()
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy
import (
"path/filepath"
)
// FastAbs can't be optimized on Windows because there
// are special file paths that require the use of syscall.FullPath
// to handle correctly.
// Just call stdlib's implementation which uses that function.
func FastAbs(path string) (string, error) {
return filepath.Abs(path)
}
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy package caddy
import "io/fs" import "io/fs"
+32 -32
View File
@@ -9,17 +9,17 @@ require (
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.14.0 github.com/alecthomas/chroma/v2 v2.14.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/caddyserver/certmagic v0.21.6 github.com/caddyserver/certmagic v0.21.4
github.com/caddyserver/zerossl v0.1.3 github.com/caddyserver/zerossl v0.1.3
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/chi/v5 v5.0.12
github.com/google/cel-go v0.21.0 github.com/google/cel-go v0.21.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.17.11 github.com/klauspost/compress v1.17.10
github.com/klauspost/cpuid/v2 v2.2.9 github.com/klauspost/cpuid/v2 v2.2.8
github.com/mholt/acmez/v3 v3.0.0 github.com/mholt/acmez/v2 v2.0.3
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.48.2 github.com/quic-go/quic-go v0.48.0
github.com/smallstep/certificates v0.26.1 github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1 github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0 github.com/smallstep/truststore v0.13.0
@@ -27,22 +27,22 @@ require (
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.4
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk v1.21.0
go.uber.org/automaxprocs v1.6.0 go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.3.0 go.uber.org/zap/exp v0.2.0
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.27.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244
golang.org/x/net v0.33.0 golang.org/x/net v0.29.0
golang.org/x/sync v0.10.0 golang.org/x/sync v0.8.0
golang.org/x/term v0.27.0 golang.org/x/term v0.24.0
golang.org/x/time v0.7.0 golang.org/x/time v0.6.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -56,12 +56,12 @@ require (
github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-kit/log v0.2.1 // indirect github.com/go-kit/log v0.2.1 // indirect
github.com/golang/glog v1.2.2 // indirect github.com/golang/glog v1.2.0 // indirect
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
github.com/google/go-tpm v0.9.0 // indirect github.com/google/go-tpm v0.9.0 // indirect
github.com/google/go-tspi v0.3.0 // indirect github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect
@@ -76,8 +76,8 @@ require (
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
) )
require ( require (
@@ -86,9 +86,9 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.2.0
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect
@@ -99,7 +99,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/kit v0.13.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@@ -139,19 +139,19 @@ require (
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/urfave/cli v1.22.14 // indirect github.com/urfave/cli v1.22.14 // indirect
go.etcd.io/bbolt v1.3.9 // indirect go.etcd.io/bbolt v1.3.9 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 go.opentelemetry.io/otel/trace v1.24.0
go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.step.sm/cli-utils v0.9.0 // indirect go.step.sm/cli-utils v0.9.0 // indirect
go.step.sm/crypto v0.45.0 go.step.sm/crypto v0.45.0
go.step.sm/linkedca v0.20.1 // indirect go.step.sm/linkedca v0.20.1 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sys v0.28.0 golang.org/x/sys v0.25.0
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
) )
+71 -71
View File
@@ -7,9 +7,9 @@ cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
@@ -89,17 +89,17 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/certmagic v0.21.6 h1:1th6GfprVfsAtFNOu4StNMF5IxK5XiaI0yZhAHlZFPE= github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
github.com/caddyserver/certmagic v0.21.6/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw= github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
@@ -172,8 +172,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
@@ -186,8 +186,8 @@ github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPh
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
@@ -239,8 +239,8 @@ github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoF
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
@@ -302,10 +302,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -344,8 +344,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E= github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
@@ -392,11 +392,11 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@@ -506,8 +506,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
@@ -524,8 +524,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w= go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y= go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c= go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
@@ -536,20 +536,20 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWE
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0= go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk= go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
@@ -577,8 +577,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -595,10 +595,10 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA= golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244 h1:3uziZWNwkTfxhMOxJB13NpTR+svHLMMVDhTrEyZOd3k=
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/crypto/x509roots/fallback v0.0.0-20240930154113-a0819fbb0244/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -628,14 +628,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -644,8 +644,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -675,16 +675,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -695,12 +695,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -739,18 +739,18 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+1 -1
View File
@@ -30,7 +30,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func reuseUnixSocket(_, _ string) (any, error) { func reuseUnixSocket(network, addr string) (any, error) {
return nil, nil return nil, nil
} }
+38 -22
View File
@@ -139,7 +139,7 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
} }
// check to see if plugin provides listener // check to see if plugin provides listener
if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil { if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil {
return ln, err return ln, err
} }
@@ -305,6 +305,25 @@ func IsFdNetwork(netw string) bool {
return strings.HasPrefix(netw, "fd") return strings.HasPrefix(netw, "fd")
} }
// normally we would simply append the port,
// but if host is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but host might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
network, addr, ok := strings.Cut(host, "/")
if !ok {
addr = network
network = ""
}
addr = strings.Trim(addr, "[]") // IPv6
networkAddr := JoinNetworkAddress(network, addr, port)
return ParseNetworkAddress(networkAddr)
}
// ParseNetworkAddress parses addr into its individual // ParseNetworkAddress parses addr into its individual
// components. The input string is expected to be of // components. The input string is expected to be of
// the form "network/host:port-range" where any part is // the form "network/host:port-range" where any part is
@@ -380,28 +399,25 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
if slashFound { if slashFound {
network = strings.ToLower(strings.TrimSpace(beforeSlash)) network = strings.ToLower(strings.TrimSpace(beforeSlash))
a = afterSlash a = afterSlash
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
} }
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
host, port, err = net.SplitHostPort(a) host, port, err = net.SplitHostPort(a)
firstErr := err if err == nil || a == "" {
return
if err != nil { }
// in general, if there was an error, it was likely "missing port", // in general, if there was an error, it was likely "missing port",
// so try removing square brackets around an IPv6 host, adding a bogus // so try adding a bogus port to take advantage of standard library's
// port to take advantage of standard library's robust parser, then // robust parser, then strip the artificial port before returning
// strip the artificial port. // (don't overwrite original error though; might still be relevant)
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0")) var err2 error
host, port, err2 = net.SplitHostPort(a + ":0")
if err2 == nil {
err = nil
port = "" port = ""
} }
if err != nil {
err = errors.Join(firstErr, err)
}
return return
} }
@@ -658,11 +674,11 @@ var unixSocketsMu sync.Mutex
// getListenerFromPlugin returns a listener on the given network and address // getListenerFromPlugin returns a listener on the given network and address
// if a plugin has registered the network name. It may return (nil, nil) if // if a plugin has registered the network name. It may return (nil, nil) if
// no plugin can provide a listener. // no plugin can provide a listener.
func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) { func getListenerFromPlugin(ctx context.Context, network, addr string, config net.ListenConfig) (any, error) {
// get listener from plugin if network type is registered // get listener from plugin if network type is registered
if getListener, ok := networkTypes[network]; ok { if getListener, ok := networkTypes[network]; ok {
Log().Debug("getting listener from plugin", zap.String("network", network)) Log().Debug("getting listener from plugin", zap.String("network", network))
return getListener(ctx, network, host, port, portOffset, config) return getListener(ctx, network, addr, config)
} }
return nil, nil return nil, nil
@@ -676,7 +692,7 @@ func listenerKey(network, addr string) string {
// The listeners must be capable of overlapping: with Caddy, new configs are loaded // The listeners must be capable of overlapping: with Caddy, new configs are loaded
// before old ones are unloaded, so listeners may overlap briefly if the configs // before old ones are unloaded, so listeners may overlap briefly if the configs
// both need the same listener. EXPERIMENTAL and subject to change. // both need the same listener. EXPERIMENTAL and subject to change.
type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error) type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
var networkTypes = map[string]ListenerFunc{} var networkTypes = map[string]ListenerFunc{}
+5 -7
View File
@@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectHost: "", expectErr: true,
}, },
{ {
input: "foo", input: "foo",
@@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}, },
{ {
input: "::", input: "::",
expectHost: "::", expectErr: true,
}, },
{ {
input: "[::]", input: "[::]",
@@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
{ {
input: "udp/", input: "udp/",
expectNetwork: "udp", expectNetwork: "udp",
expectHost: "", expectErr: true,
}, },
{ {
input: "unix//foo/bar", input: "unix//foo/bar",
@@ -185,8 +185,7 @@ func TestParseNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{ expectErr: true,
},
}, },
{ {
input: ":", input: ":",
@@ -312,8 +311,7 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{ expectErr: true,
},
}, },
{ {
input: ":", input: ":",
+1 -1
View File
@@ -262,7 +262,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
return nil, false return nil, false
}) })
logger = logger.WithLazy(zap.Any("data", e.Data)) logger = logger.With(zap.Any("data", e.Data))
logger.Debug("event") logger.Debug("event")
+27 -64
View File
@@ -15,7 +15,6 @@
package caddyhttp package caddyhttp
import ( import (
"cmp"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
@@ -143,10 +142,6 @@ type App struct {
// affect functionality. // affect functionality.
Servers map[string]*Server `json:"servers,omitempty"` Servers map[string]*Server `json:"servers,omitempty"`
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
Metrics *Metrics `json:"metrics,omitempty"`
ctx caddy.Context ctx caddy.Context
logger *zap.Logger logger *zap.Logger
tlsApp *caddytls.TLS tlsApp *caddytls.TLS
@@ -189,10 +184,6 @@ func (app *App) Provision(ctx caddy.Context) error {
return err return err
} }
if app.Metrics != nil {
app.Metrics.init = sync.Once{}
app.Metrics.httpMetrics = &httpMetrics{}
}
// prepare each server // prepare each server
oldContext := ctx.Context oldContext := ctx.Context
for srvName, srv := range app.Servers { for srvName, srv := range app.Servers {
@@ -205,15 +196,6 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.errorLogger = app.logger.Named("log.error") srv.errorLogger = app.logger.Named("log.error")
srv.shutdownAtMu = new(sync.RWMutex) srv.shutdownAtMu = new(sync.RWMutex)
if srv.Metrics != nil {
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
init: sync.Once{},
httpMetrics: &httpMetrics{},
})
app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
}
// only enable access logs if configured // only enable access logs if configured
if srv.Logs != nil { if srv.Logs != nil {
srv.accessLogger = app.logger.Named("log.access") srv.accessLogger = app.logger.Named("log.access")
@@ -360,11 +342,16 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...) srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
} }
} }
// pre-compile the primary handler chain, and be sure to wrap it in our // pre-compile the primary handler chain, and be sure to wrap it in our
// route handler so that important security checks are done, etc. // route handler so that important security checks are done, etc.
primaryRoute := emptyHandler primaryRoute := emptyHandler
if srv.Routes != nil { if srv.Routes != nil {
err := srv.Routes.ProvisionHandlers(ctx, app.Metrics) if srv.Metrics != nil {
srv.Metrics.init = sync.Once{}
srv.Metrics.httpMetrics = &httpMetrics{}
}
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
} }
@@ -383,7 +370,7 @@ func (app *App) Provision(ctx caddy.Context) error {
// provision the named routes (they get compiled at runtime) // provision the named routes (they get compiled at runtime)
for name, route := range srv.NamedRoutes { for name, route := range srv.NamedRoutes {
err := route.Provision(ctx, app.Metrics) err := route.Provision(ctx, srv.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err) return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
} }
@@ -401,9 +388,6 @@ func (app *App) Provision(ctx caddy.Context) error {
if srv.IdleTimeout == 0 { if srv.IdleTimeout == 0 {
srv.IdleTimeout = defaultIdleTimeout srv.IdleTimeout = defaultIdleTimeout
} }
if srv.ReadHeaderTimeout == 0 {
srv.ReadHeaderTimeout = defaultReadHeaderTimeout // see #6663
}
} }
ctx.Context = oldContext ctx.Context = oldContext
return nil return nil
@@ -529,6 +513,21 @@ func (app *App) Start() error {
// enable TLS if there is a policy and if this is not the HTTP port // enable TLS if there is a policy and if this is not the HTTP port
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
// enable HTTP/3 if configured
if h3ok && useTLS {
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
}
if h3ok && !useTLS {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
if h1ok || h2ok && useTLS || h2cok { if h1ok || h2ok && useTLS || h2cok {
// create the listener for this socket // create the listener for this socket
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
@@ -599,33 +598,6 @@ func (app *App) Start() error {
zap.String("network", listenAddr.Network), zap.String("network", listenAddr.Network),
zap.String("addr", hostport)) zap.String("addr", hostport))
} }
if h3ok {
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
// a different transport mechanism... which is fine, but the OS doesn't
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
// are still one file on the system. So even though "unixpacket" and
// "unixgram" are different network types just as "tcp" and "udp" are,
// the OS will not let us use the same file as both STREAM and DGRAM.
if listenAddr.IsUnixNetwork() {
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
zap.String("file", hostport))
continue
}
if useTLS {
// enable HTTP/3 if configured
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
} else {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
}
} }
} }
@@ -785,20 +757,11 @@ func (app *App) httpsPort() int {
return app.HTTPSPort return app.HTTPSPort
} }
const ( // defaultIdleTimeout is the default HTTP server timeout
// defaultIdleTimeout is the default HTTP server timeout // for closing idle connections; useful to avoid resource
// for closing idle connections; useful to avoid resource // exhaustion behind hungry CDNs, for example (we've had
// exhaustion behind hungry CDNs, for example (we've had // several complaints without this).
// several complaints without this). const defaultIdleTimeout = caddy.Duration(5 * time.Minute)
defaultIdleTimeout = caddy.Duration(5 * time.Minute)
// defaultReadHeaderTimeout is the default timeout for
// reading HTTP headers from clients. Headers are generally
// small, often less than 1 KB, so it shouldn't take a
// long time even on legitimately slow connections or
// busy servers to read it.
defaultReadHeaderTimeout = caddy.Duration(time.Minute)
)
// Interface guards // Interface guards
var ( var (
-16
View File
@@ -36,26 +36,10 @@ func init() {
// RequestMatcher is a type that can match to a request. // RequestMatcher is a type that can match to a request.
// A route matcher MUST NOT modify the request, with the // A route matcher MUST NOT modify the request, with the
// only exception being its context. // only exception being its context.
//
// Deprecated: Matchers should now implement RequestMatcherWithError.
// You may remove any interface guards for RequestMatcher
// but keep your Match() methods for backwards compatibility.
type RequestMatcher interface { type RequestMatcher interface {
Match(*http.Request) bool Match(*http.Request) bool
} }
// RequestMatcherWithError is like RequestMatcher but can return an error.
// An error during matching will abort the request middleware chain and
// invoke the error middleware chain.
//
// This will eventually replace RequestMatcher. Matcher modules
// should implement both interfaces, and once all modules have
// been updated to use RequestMatcherWithError, the RequestMatcher
// interface may eventually be dropped.
type RequestMatcherWithError interface {
MatchWithError(*http.Request) (bool, error)
}
// Handler is like http.Handler except ServeHTTP may return an error. // Handler is like http.Handler except ServeHTTP may return an error.
// //
// If any handler encounters an error, it should be returned for proper // If any handler encounters an error, it should be returned for proper
+33 -102
View File
@@ -202,25 +202,17 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchExpression) Match(r *http.Request) bool { func (m MatchExpression) Match(r *http.Request) bool {
match, err := m.MatchWithError(r)
if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
celReq := celHTTPRequest{r} celReq := celHTTPRequest{r}
out, _, err := m.prg.Eval(celReq) out, _, err := m.prg.Eval(celReq)
if err != nil { if err != nil {
m.log.Error("evaluating expression", zap.Error(err)) m.log.Error("evaluating expression", zap.Error(err))
return false, err SetVar(r.Context(), MatcherErrorVarKey, err)
return false
} }
if outBool, ok := out.Value().(bool); ok { if outBool, ok := out.Value().(bool); ok {
return outBool, nil return outBool
} }
return false, nil return false
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@@ -388,7 +380,7 @@ type CELLibraryProducer interface {
// limited set of function signatures. For strong type validation you may need // limited set of function signatures. For strong type validation you may need
// to provide a custom macro which does a more detailed analysis of the CEL // to provide a custom macro which does a more detailed analysis of the CEL
// literal provided to the macro as an argument. // literal provided to the macro as an argument.
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) { func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac CELMatcherFactory) (cel.Library, error) {
requestType := cel.ObjectType("http.Request") requestType := cel.ObjectType("http.Request")
var macro parser.Macro var macro parser.Macro
switch len(matcherDataTypes) { switch len(matcherDataTypes) {
@@ -432,11 +424,7 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
} }
// CELMatcherFactory converts a constant CEL value into a RequestMatcher. // CELMatcherFactory converts a constant CEL value into a RequestMatcher.
// Deprecated: Use CELMatcherWithErrorFactory instead. type CELMatcherFactory func(data ref.Val) (RequestMatcher, error)
type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)
// CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.
type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)
// matcherCELLibrary is a simplistic configurable cel.Library implementation. // matcherCELLibrary is a simplistic configurable cel.Library implementation.
type matcherCELLibrary struct { type matcherCELLibrary struct {
@@ -464,7 +452,7 @@ func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
// that takes a single argument, and optimizes the implementation to precompile // that takes a single argument, and optimizes the implementation to precompile
// the matcher and return a function that references the precompiled and // the matcher and return a function that references the precompiled and
// provisioned matcher. // provisioned matcher.
func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator { func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.InterpretableDecorator {
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) { return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
call, ok := i.(interpreter.InterpretableCall) call, ok := i.(interpreter.InterpretableCall)
if !ok { if !ok {
@@ -493,92 +481,35 @@ func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDeco
// and matcher provisioning should be handled at dynamically. // and matcher provisioning should be handled at dynamically.
return i, nil return i, nil
} }
matcher, err := fac(matcherData.Value())
if factory, ok := fac.(CELMatcherWithErrorFactory); ok { if err != nil {
matcher, err := factory(matcherData.Value()) return nil, err
if err != nil {
return nil, err
}
return interpreter.NewCall(
i.ID(), funcName, funcName+"_opt",
[]interpreter.Interpretable{reqAttr},
func(args ...ref.Val) ref.Val {
// The request value, guaranteed to be of type celHTTPRequest
celReq := args[0]
// If needed this call could be changed to convert the value
// to a *http.Request using CEL's ConvertToNative method.
httpReq := celReq.Value().(celHTTPRequest)
match, err := matcher.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
},
), nil
} }
return interpreter.NewCall(
if factory, ok := fac.(CELMatcherFactory); ok { i.ID(), funcName, funcName+"_opt",
matcher, err := factory(matcherData.Value()) []interpreter.Interpretable{reqAttr},
if err != nil { func(args ...ref.Val) ref.Val {
return nil, err // The request value, guaranteed to be of type celHTTPRequest
} celReq := args[0]
return interpreter.NewCall( // If needed this call could be changed to convert the value
i.ID(), funcName, funcName+"_opt", // to a *http.Request using CEL's ConvertToNative method.
[]interpreter.Interpretable{reqAttr}, httpReq := celReq.Value().(celHTTPRequest)
func(args ...ref.Val) ref.Val { return types.Bool(matcher.Match(httpReq.Request))
// The request value, guaranteed to be of type celHTTPRequest },
celReq := args[0] ), nil
// If needed this call could be changed to convert the value
// to a *http.Request using CEL's ConvertToNative method.
httpReq := celReq.Value().(celHTTPRequest)
if m, ok := matcher.(RequestMatcherWithError); ok {
match, err := m.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
}
return types.Bool(matcher.Match(httpReq.Request))
},
), nil
}
return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
} }
} }
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher // CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
// is dynamically resolved rather than a set of static constant values. // is dynamically resolved rather than a set of static constant values.
func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp { func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions.BinaryOp {
return func(celReq, matcherData ref.Val) ref.Val { return func(celReq, matcherData ref.Val) ref.Val {
if factory, ok := fac.(CELMatcherWithErrorFactory); ok { matcher, err := fac(matcherData)
matcher, err := factory(matcherData) if err != nil {
if err != nil { return types.WrapErr(err)
return types.WrapErr(err)
}
httpReq := celReq.Value().(celHTTPRequest)
match, err := matcher.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
} }
if factory, ok := fac.(CELMatcherFactory); ok { httpReq := celReq.Value().(celHTTPRequest)
matcher, err := factory(matcherData) return types.Bool(matcher.Match(httpReq.Request))
if err != nil {
return types.WrapErr(err)
}
httpReq := celReq.Value().(celHTTPRequest)
if m, ok := matcher.(RequestMatcherWithError); ok {
match, err := m.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
}
return types.Bool(matcher.Match(httpReq.Request))
}
return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
} }
} }
@@ -802,9 +733,9 @@ const MatcherNameCtxKey = "matcher_name"
// Interface guards // Interface guards
var ( var (
_ caddy.Provisioner = (*MatchExpression)(nil) _ caddy.Provisioner = (*MatchExpression)(nil)
_ RequestMatcherWithError = (*MatchExpression)(nil) _ RequestMatcher = (*MatchExpression)(nil)
_ caddyfile.Unmarshaler = (*MatchExpression)(nil) _ caddyfile.Unmarshaler = (*MatchExpression)(nil)
_ json.Marshaler = (*MatchExpression)(nil) _ json.Marshaler = (*MatchExpression)(nil)
_ json.Unmarshaler = (*MatchExpression)(nil) _ json.Unmarshaler = (*MatchExpression)(nil)
) )
+2 -6
View File
@@ -489,11 +489,7 @@ func TestMatchExpressionMatch(t *testing.T) {
} }
} }
matches, err := tc.expression.MatchWithError(req) if tc.expression.Match(req) != tc.wantResult {
if err != nil {
t.Errorf("MatchExpression.Match() error = %v", err)
}
if matches != tc.wantResult {
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
} }
}) })
@@ -536,7 +532,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tc.expression.MatchWithError(req) tc.expression.Match(req)
} }
}) })
} }
+15 -21
View File
@@ -57,7 +57,21 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name d.Next() // consume directive name
prefer := []string{} prefer := []string{}
remainingArgs := d.RemainingArgs() for _, arg := range d.RemainingArgs() {
mod, err := caddy.GetModule("http.encoders." + arg)
if err != nil {
return d.Errf("finding encoder module '%s': %v", mod, err)
}
encoding, ok := mod.New().(Encoding)
if !ok {
return d.Errf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
prefer = append(prefer, arg)
}
responseMatchers := make(map[string]caddyhttp.ResponseMatcher) responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
for d.NextBlock(0) { for d.NextBlock(0) {
@@ -97,26 +111,6 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} }
} }
if len(prefer) == 0 && len(remainingArgs) == 0 {
remainingArgs = []string{"zstd", "gzip"}
}
for _, arg := range remainingArgs {
mod, err := caddy.GetModule("http.encoders." + arg)
if err != nil {
return d.Errf("finding encoder module '%s': %v", mod, err)
}
encoding, ok := mod.New().(Encoding)
if !ok {
return d.Errf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
prefer = append(prefer, arg)
}
// use the order in which the encoders were defined. // use the order in which the encoders were defined.
enc.Prefer = prefer enc.Prefer = prefer
+4 -77
View File
@@ -156,7 +156,7 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
if _, ok := enc.writerPools[encName]; !ok { if _, ok := enc.writerPools[encName]; !ok {
continue // encoding not offered continue // encoding not offered
} }
w = enc.openResponseWriter(encName, w, r.Method == http.MethodConnect) w = enc.openResponseWriter(encName, w)
defer w.(*responseWriter).Close() defer w.(*responseWriter).Close()
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding // to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
@@ -201,14 +201,14 @@ func (enc *Encode) addEncoding(e Encoding) error {
// openResponseWriter creates a new response writer that may (or may not) // openResponseWriter creates a new response writer that may (or may not)
// encode the response with encodingName. The returned response writer MUST // encode the response with encodingName. The returned response writer MUST
// be closed after the handler completes. // be closed after the handler completes.
func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter, isConnect bool) *responseWriter { func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter) *responseWriter {
var rw responseWriter var rw responseWriter
return enc.initResponseWriter(&rw, encodingName, w, isConnect) return enc.initResponseWriter(&rw, encodingName, w)
} }
// initResponseWriter initializes the responseWriter instance // initResponseWriter initializes the responseWriter instance
// allocated in openResponseWriter, enabling mid-stack inlining. // allocated in openResponseWriter, enabling mid-stack inlining.
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter, isConnect bool) *responseWriter { func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok { if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok {
rw.ResponseWriter = rww rw.ResponseWriter = rww
} else { } else {
@@ -216,7 +216,6 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
} }
rw.encodingName = encodingName rw.encodingName = encodingName
rw.config = enc rw.config = enc
rw.isConnect = isConnect
return rw return rw
} }
@@ -231,7 +230,6 @@ type responseWriter struct {
config *Encode config *Encode
statusCode int statusCode int
wroteHeader bool wroteHeader bool
isConnect bool
} }
// WriteHeader stores the status to write when the time comes // WriteHeader stores the status to write when the time comes
@@ -247,14 +245,6 @@ func (rw *responseWriter) WriteHeader(status int) {
rw.Header().Add("Vary", "Accept-Encoding") rw.Header().Add("Vary", "Accept-Encoding")
} }
// write status immediately if status is 2xx and the request is CONNECT
// since it means the response is successful.
// see: https://github.com/caddyserver/caddy/issues/6733#issuecomment-2525058845
if rw.isConnect && 200 <= status && status <= 299 {
rw.ResponseWriter.WriteHeader(status)
rw.wroteHeader = true
}
// write status immediately when status code is informational // write status immediately when status code is informational
// see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5 // see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5
if 100 <= status && status <= 199 { if 100 <= status && status <= 199 {
@@ -270,12 +260,6 @@ func (enc *Encode) Match(rw *responseWriter) bool {
// FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying // FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying
// ResponseWriterWrapper until headers were written. // ResponseWriterWrapper until headers were written.
func (rw *responseWriter) FlushError() error { func (rw *responseWriter) FlushError() error {
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
// otherwise, wait until header is written.
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
rw.WriteHeader(http.StatusOK)
}
if !rw.wroteHeader { if !rw.wroteHeader {
// flushing the underlying ResponseWriter will write header and status code, // flushing the underlying ResponseWriter will write header and status code,
// but we need to delay that until we can determine if we must encode and // but we need to delay that until we can determine if we must encode and
@@ -295,14 +279,6 @@ func (rw *responseWriter) FlushError() error {
return http.NewResponseController(rw.ResponseWriter).Flush() return http.NewResponseController(rw.ResponseWriter).Flush()
} }
// Flush calls FlushError() and simply discards any error. It is only implemented for backwards
// compatibility with legacy code that does not use FlushError; we know at least one sponsor
// needs this. It should not be relied upon as a stable part of the exported API, as it may be
// removed in the future.
func (rw *responseWriter) Flush() {
_ = rw.FlushError()
}
// Write writes to the response. If the response qualifies, // Write writes to the response. If the response qualifies,
// it is encoded using the encoder, which is initialized // it is encoded using the encoder, which is initialized
// if not done so already. // if not done so already.
@@ -312,12 +288,6 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
return 0, nil return 0, nil
} }
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
// otherwise, determine if the response should be compressed.
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
rw.WriteHeader(http.StatusOK)
}
// sniff content-type and determine content-length // sniff content-type and determine content-length
if !rw.wroteHeader && rw.config.MinLength > 0 { if !rw.wroteHeader && rw.config.MinLength > 0 {
var gtMinLength bool var gtMinLength bool
@@ -355,49 +325,6 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
} }
} }
// used to mask ReadFrom method
type writerOnly struct {
io.Writer
}
// copied from stdlib
const sniffLen = 512
// ReadFrom will try to use sendfile to copy from the reader to the response writer.
// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed.
// It's based on stdlin http1.1 response writer implementation.
// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586
func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) {
rf, ok := rw.ResponseWriter.(io.ReaderFrom)
// sendfile can't be used anyway
if !ok {
// mask ReadFrom to avoid infinite recursion
return io.Copy(writerOnly{rw}, r)
}
var ns int64
// try to sniff the content type and determine if the response should be compressed
if !rw.wroteHeader && rw.config.MinLength > 0 {
var (
err error
buf [sniffLen]byte
)
// mask ReadFrom to let Write determine if the response should be compressed
ns, err = io.CopyBuffer(writerOnly{rw}, io.LimitReader(r, sniffLen), buf[:])
if err != nil || ns < sniffLen {
return ns, err
}
}
// the response will be compressed, no sendfile support
if rw.w != nil {
nr, err := io.Copy(rw.w, r)
return nr + ns, err
}
nr, err := rf.ReadFrom(r)
return nr + ns, err
}
// Close writes any remaining buffered response and // Close writes any remaining buffered response and
// deallocates any active resources. // deallocates any active resources.
func (rw *responseWriter) Close() error { func (rw *responseWriter) Close() error {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func BenchmarkOpenResponseWriter(b *testing.B) { func BenchmarkOpenResponseWriter(b *testing.B) {
enc := new(Encode) enc := new(Encode)
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
enc.openResponseWriter("test", nil, false) enc.openResponseWriter("test", nil)
} }
} }
+1 -12
View File
@@ -66,15 +66,8 @@ type Browse struct {
// - `sort size` will sort by size in ascending order // - `sort size` will sort by size in ascending order
// The first option must be `sort_by` and the second option must be `order` (if exists). // The first option must be `sort_by` and the second option must be `order` (if exists).
SortOptions []string `json:"sort,omitempty"` SortOptions []string `json:"sort,omitempty"`
// FileLimit limits the number of up to n DirEntry values in directory order.
FileLimit int `json:"file_limit,omitempty"`
} }
const (
defaultDirEntryLimit = 10000
)
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil { if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil {
c.Write(zap.String("path", dirPath), zap.String("root", root)) c.Write(zap.String("path", dirPath), zap.String("root", root))
@@ -213,11 +206,7 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
} }
func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) { func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) {
dirLimit := defaultDirEntryLimit files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable
if fsrv.Browse.FileLimit != 0 {
dirLimit = fsrv.Browse.FileLimit
}
files, err := dir.ReadDir(dirLimit)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, err return nil, err
} }
+8 -21
View File
@@ -16,7 +16,6 @@ package fileserver
import ( import (
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@@ -79,7 +78,7 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr() return d.ArgErr()
} }
for nesting := d.Nesting(); d.NextBlock(nesting); { for d.NextBlock(0) {
switch d.Val() { switch d.Val() {
case "fs": case "fs":
if !d.NextArg() { if !d.NextArg() {
@@ -130,29 +129,15 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Errf("unknown sort option '%s'", dVal) return d.Errf("unknown sort option '%s'", dVal)
} }
} }
case "file_limit":
fileLimit := d.RemainingArgs()
if len(fileLimit) != 1 {
return d.Err("file_limit should have an integer value")
}
val, _ := strconv.Atoi(fileLimit[0])
if fsrv.Browse.FileLimit != 0 {
return d.Err("file_limit is already enabled")
}
fsrv.Browse.FileLimit = val
default: default:
return d.Errf("unknown subdirective '%s'", d.Val()) return d.Errf("unknown subdirective '%s'", d.Val())
} }
} }
case "precompressed": case "precompressed":
fsrv.PrecompressedOrder = d.RemainingArgs() var order []string
if len(fsrv.PrecompressedOrder) == 0 { for d.NextArg() {
fsrv.PrecompressedOrder = []string{"br", "zstd", "gzip"} modID := "http.precompressed." + d.Val()
}
for _, format := range fsrv.PrecompressedOrder {
modID := "http.precompressed." + format
mod, err := caddy.GetModule(modID) mod, err := caddy.GetModule(modID)
if err != nil { if err != nil {
return d.Errf("getting module named '%s': %v", modID, err) return d.Errf("getting module named '%s': %v", modID, err)
@@ -165,8 +150,10 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if fsrv.PrecompressedRaw == nil { if fsrv.PrecompressedRaw == nil {
fsrv.PrecompressedRaw = make(caddy.ModuleMap) fsrv.PrecompressedRaw = make(caddy.ModuleMap)
} }
fsrv.PrecompressedRaw[format] = caddyconfig.JSON(precompress, nil) fsrv.PrecompressedRaw[d.Val()] = caddyconfig.JSON(precompress, nil)
order = append(order, d.Val())
} }
fsrv.PrecompressedOrder = order
case "status": case "status":
if !d.NextArg() { if !d.NextArg() {
@@ -276,7 +263,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
tryPolicy = h.Val() tryPolicy = h.Val()
switch tryPolicy { switch tryPolicy {
case tryPolicyFirstExist, tryPolicyFirstExistFallback, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod: case tryPolicyFirstExist, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
default: default:
return nil, h.Errf("unrecognized try policy: %s", tryPolicy) return nil, h.Errf("unrecognized try policy: %s", tryPolicy)
} }
+1 -3
View File
@@ -66,7 +66,6 @@ respond with a file listing.`,
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering") cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log") cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
cmd.Flags().IntP("file-limit", "f", defaultDirEntryLimit, "Max directories to read")
cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression") cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression")
cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.") cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer) cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
@@ -92,7 +91,6 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
browse := fs.Bool("browse") browse := fs.Bool("browse")
templates := fs.Bool("templates") templates := fs.Bool("templates")
accessLog := fs.Bool("access-log") accessLog := fs.Bool("access-log")
fileLimit := fs.Int("file-limit")
debug := fs.Bool("debug") debug := fs.Bool("debug")
revealSymlinks := fs.Bool("reveal-symlinks") revealSymlinks := fs.Bool("reveal-symlinks")
compress := !fs.Bool("no-compress") compress := !fs.Bool("no-compress")
@@ -153,7 +151,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
} }
if browse { if browse {
handler.Browse = &Browse{RevealSymlinks: revealSymlinks, FileLimit: fileLimit} handler.Browse = &Browse{RevealSymlinks: revealSymlinks}
} }
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil)) handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
+29 -57
View File
@@ -90,7 +90,6 @@ type MatchFile struct {
// How to choose a file in TryFiles. Can be: // How to choose a file in TryFiles. Can be:
// //
// - first_exist // - first_exist
// - first_exist_fallback
// - smallest_size // - smallest_size
// - largest_size // - largest_size
// - most_recently_modified // - most_recently_modified
@@ -174,7 +173,7 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) { func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
requestType := cel.ObjectType("http.Request") requestType := cel.ObjectType("http.Request")
matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcherWithError, error) { matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcher, error) {
values, err := caddyhttp.CELValueToMapStrList(data) values, err := caddyhttp.CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -192,7 +191,7 @@ func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
var try_policy string var try_policy string
if len(values["try_policy"]) > 0 { if len(values["try_policy"]) > 0 {
try_policy = values["try_policy"][0] root = values["try_policy"][0]
} }
m := MatchFile{ m := MatchFile{
@@ -297,7 +296,6 @@ func (m MatchFile) Validate() error {
switch m.TryPolicy { switch m.TryPolicy {
case "", case "",
tryPolicyFirstExist, tryPolicyFirstExist,
tryPolicyFirstExistFallback,
tryPolicyLargestSize, tryPolicyLargestSize,
tryPolicySmallestSize, tryPolicySmallestSize,
tryPolicyMostRecentlyMod: tryPolicyMostRecentlyMod:
@@ -315,22 +313,12 @@ func (m MatchFile) Validate() error {
// - http.matchers.file.type: file or directory // - http.matchers.file.type: file or directory
// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured) // - http.matchers.file.remainder: Portion remaining after splitting file path (if configured)
func (m MatchFile) Match(r *http.Request) bool { func (m MatchFile) Match(r *http.Request) bool {
match, err := m.selectFile(r)
if err != nil {
// nolint:staticcheck
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchFile) MatchWithError(r *http.Request) (bool, error) {
return m.selectFile(r) return m.selectFile(r)
} }
// selectFile chooses a file according to m.TryPolicy by appending // selectFile chooses a file according to m.TryPolicy by appending
// the paths in m.TryFiles to m.Root, with placeholder replacements. // the paths in m.TryFiles to m.Root, with placeholder replacements.
func (m MatchFile) selectFile(r *http.Request) (bool, error) { func (m MatchFile) selectFile(r *http.Request) (matched bool) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
root := filepath.Clean(repl.ReplaceAll(m.Root, ".")) root := filepath.Clean(repl.ReplaceAll(m.Root, "."))
@@ -342,7 +330,7 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil { if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
c.Write(zap.String("fs", fsName)) c.Write(zap.String("fs", fsName))
} }
return false, nil return false
} }
type matchCandidate struct { type matchCandidate struct {
fullpath, relative, splitRemainder string fullpath, relative, splitRemainder string
@@ -417,13 +405,13 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
} }
// setPlaceholders creates the placeholders for the matched file // setPlaceholders creates the placeholders for the matched file
setPlaceholders := func(candidate matchCandidate, isDir bool) { setPlaceholders := func(candidate matchCandidate, info fs.FileInfo) {
repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative)) repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative))
repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath)) repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath))
repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder)) repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder))
fileType := "file" fileType := "file"
if isDir { if info.IsDir() {
fileType = "directory" fileType = "directory"
} }
repl.Set("http.matchers.file.type", fileType) repl.Set("http.matchers.file.type", fileType)
@@ -431,32 +419,17 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
// match file according to the configured policy // match file according to the configured policy
switch m.TryPolicy { switch m.TryPolicy {
case "", tryPolicyFirstExist, tryPolicyFirstExistFallback: case "", tryPolicyFirstExist:
maxI := -1 for _, pattern := range m.TryFiles {
if m.TryPolicy == tryPolicyFirstExistFallback {
maxI = len(m.TryFiles) - 1
}
for i, pattern := range m.TryFiles {
// If the pattern is a status code, emit an error,
// which short-circuits the middleware pipeline and
// writes an HTTP error response.
if err := parseErrorCode(pattern); err != nil { if err := parseErrorCode(pattern); err != nil {
return false, err caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
return
} }
candidates := makeCandidates(pattern) candidates := makeCandidates(pattern)
for _, c := range candidates { for _, c := range candidates {
// Skip the IO if using fallback policy and it's the latest item
if i == maxI {
setPlaceholders(c, false)
return true, nil
}
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists { if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
setPlaceholders(c, info.IsDir()) setPlaceholders(c, info)
return true, nil return true
} }
} }
} }
@@ -477,10 +450,10 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
} }
} }
if largestInfo == nil { if largestInfo == nil {
return false, nil return false
} }
setPlaceholders(largest, largestInfo.IsDir()) setPlaceholders(largest, largestInfo)
return true, nil return true
case tryPolicySmallestSize: case tryPolicySmallestSize:
var smallestSize int64 var smallestSize int64
@@ -498,10 +471,10 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
} }
} }
if smallestInfo == nil { if smallestInfo == nil {
return false, nil return false
} }
setPlaceholders(smallest, smallestInfo.IsDir()) setPlaceholders(smallest, smallestInfo)
return true, nil return true
case tryPolicyMostRecentlyMod: case tryPolicyMostRecentlyMod:
var recent matchCandidate var recent matchCandidate
@@ -518,13 +491,13 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
} }
} }
if recentInfo == nil { if recentInfo == nil {
return false, nil return false
} }
setPlaceholders(recent, recentInfo.IsDir()) setPlaceholders(recent, recentInfo)
return true, nil return true
} }
return false, nil return
} }
// parseErrorCode checks if the input is a status // parseErrorCode checks if the input is a status
@@ -722,16 +695,15 @@ var globSafeRepl = strings.NewReplacer(
) )
const ( const (
tryPolicyFirstExist = "first_exist" tryPolicyFirstExist = "first_exist"
tryPolicyFirstExistFallback = "first_exist_fallback" tryPolicyLargestSize = "largest_size"
tryPolicyLargestSize = "largest_size" tryPolicySmallestSize = "smallest_size"
tryPolicySmallestSize = "smallest_size" tryPolicyMostRecentlyMod = "most_recently_modified"
tryPolicyMostRecentlyMod = "most_recently_modified"
) )
// Interface guards // Interface guards
var ( var (
_ caddy.Validator = (*MatchFile)(nil) _ caddy.Validator = (*MatchFile)(nil)
_ caddyhttp.RequestMatcherWithError = (*MatchFile)(nil) _ caddyhttp.RequestMatcher = (*MatchFile)(nil)
_ caddyhttp.CELLibraryProducer = (*MatchFile)(nil) _ caddyhttp.CELLibraryProducer = (*MatchFile)(nil)
) )
+3 -34
View File
@@ -130,10 +130,7 @@ func TestFileMatcher(t *testing.T) {
req := &http.Request{URL: u} req := &http.Request{URL: u}
repl := caddyhttp.NewTestReplacer(req) repl := caddyhttp.NewTestReplacer(req)
result, err := m.MatchWithError(req) result := m.Match(req)
if err != nil {
t.Errorf("Test %d: unexpected error: %v", i, err)
}
if result != tc.matched { if result != tc.matched {
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
} }
@@ -243,10 +240,7 @@ func TestPHPFileMatcher(t *testing.T) {
req := &http.Request{URL: u} req := &http.Request{URL: u}
repl := caddyhttp.NewTestReplacer(req) repl := caddyhttp.NewTestReplacer(req)
result, err := m.MatchWithError(req) result := m.Match(req)
if err != nil {
t.Errorf("Test %d: unexpected error: %v", i, err)
}
if result != tc.matched { if result != tc.matched {
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
} }
@@ -295,7 +289,6 @@ var expressionTests = []struct {
wantErr bool wantErr bool
wantResult bool wantResult bool
clientCertificate []byte clientCertificate []byte
expectedPath string
}{ }{
{ {
name: "file error no args (MatchFile)", name: "file error no args (MatchFile)",
@@ -361,15 +354,6 @@ var expressionTests = []struct {
urlTarget: "https://example.com/nopenope.txt", urlTarget: "https://example.com/nopenope.txt",
wantResult: false, wantResult: false,
}, },
{
name: "file match long pattern foo.txt with try_policy (MatchFile)",
expression: &caddyhttp.MatchExpression{
Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`,
},
urlTarget: "https://example.com/",
wantResult: true,
expectedPath: "/large.txt",
},
} }
func TestMatchExpressionMatch(t *testing.T) { func TestMatchExpressionMatch(t *testing.T) {
@@ -395,24 +379,9 @@ func TestMatchExpressionMatch(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
matches, err := tc.expression.MatchWithError(req) if tc.expression.Match(req) != tc.wantResult {
if err != nil {
t.Errorf("MatchExpression.Match() error = %v", err)
return
}
if matches != tc.wantResult {
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
} }
if tc.expectedPath != "" {
path, ok := repl.Get("http.matchers.file.relative")
if !ok {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath)
}
if path != tc.expectedPath {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path)
}
}
}) })
} }
} }
+3 -3
View File
@@ -204,7 +204,7 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
// absolute paths before the server starts for very slight performance improvement // absolute paths before the server starts for very slight performance improvement
for i, h := range fsrv.Hide { for i, h := range fsrv.Hide {
if !strings.Contains(h, "{") && strings.Contains(h, separator) { if !strings.Contains(h, "{") && strings.Contains(h, separator) {
if abs, err := caddy.FastAbs(h); err == nil { if abs, err := filepath.Abs(h); err == nil {
fsrv.Hide[i] = abs fsrv.Hide[i] = abs
} }
} }
@@ -636,7 +636,7 @@ func (fsrv *FileServer) transformHidePaths(repl *caddy.Replacer) []string {
for i := range fsrv.Hide { for i := range fsrv.Hide {
hide[i] = repl.ReplaceAll(fsrv.Hide[i], "") hide[i] = repl.ReplaceAll(fsrv.Hide[i], "")
if strings.Contains(hide[i], separator) { if strings.Contains(hide[i], separator) {
abs, err := caddy.FastAbs(hide[i]) abs, err := filepath.Abs(hide[i])
if err == nil { if err == nil {
hide[i] = abs hide[i] = abs
} }
@@ -655,7 +655,7 @@ func fileHidden(filename string, hide []string) bool {
} }
// all path comparisons use the complete absolute path if possible // all path comparisons use the complete absolute path if possible
filenameAbs, err := caddy.FastAbs(filename) filenameAbs, err := filepath.Abs(filename)
if err == nil { if err == nil {
filename = filenameAbs filename = filenameAbs
} }
-3
View File
@@ -1,3 +0,0 @@
This is a file with more content than the other files in this directory
such that tests using the largest_size policy pick this file, or the
smallest_size policy avoids this file.
-10
View File
@@ -99,16 +99,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
handler.Response.Deferred = true handler.Response.Deferred = true
continue continue
} }
if field == "match" {
responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
err := caddyhttp.ParseNamedResponseMatcher(h.NewFromNextSegment(), responseMatchers)
if err != nil {
return nil, err
}
matcher := responseMatchers["match"]
handler.Response.Require = &matcher
continue
}
if hasArgs { if hasArgs {
return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird
} }
+3 -1
View File
@@ -200,7 +200,9 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
for _, fieldName := range ops.Delete { for _, fieldName := range ops.Delete {
fieldName = repl.ReplaceKnown(fieldName, "") fieldName = repl.ReplaceKnown(fieldName, "")
if fieldName == "*" { if fieldName == "*" {
clear(hdr) for existingField := range hdr {
delete(hdr, existingField)
}
} }
} }
-22
View File
@@ -143,28 +143,6 @@ func TestHandler(t *testing.T) {
"Cache-Control": []string{"no-cache"}, "Cache-Control": []string{"no-cache"},
}, },
}, },
{ // same as above, but checks that response headers are left alone when "Require" conditions are unmet
handler: Handler{
Response: &RespHeaderOps{
Require: &caddyhttp.ResponseMatcher{
Headers: http.Header{
"Cache-Control": nil,
},
},
HeaderOps: &HeaderOps{
Add: http.Header{
"Cache-Control": []string{"no-cache"},
},
},
},
},
respHeader: http.Header{
"Cache-Control": []string{"something"},
},
expectedRespHeader: http.Header{
"Cache-Control": []string{"something"},
},
},
{ {
handler: Handler{ handler: Handler{
Response: &RespHeaderOps{ Response: &RespHeaderOps{
+16 -44
View File
@@ -108,7 +108,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -145,23 +145,9 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchRemoteIP) Match(r *http.Request) bool { func (m MatchRemoteIP) Match(r *http.Request) bool {
match, err := m.MatchWithError(r)
if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
// if handshake is not finished, we infer 0-RTT that has
// not verified remote IP; could be spoofed, so we throw
// HTTP 425 status to tell the client to try again after
// the handshake is complete
if r.TLS != nil && !r.TLS.HandshakeComplete { if r.TLS != nil && !r.TLS.HandshakeComplete {
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified")) return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
} }
address := r.RemoteAddr address := r.RemoteAddr
clientIP, zoneID, err := parseIPZoneFromString(address) clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil { if err != nil {
@@ -169,7 +155,7 @@ func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
c.Write(zap.Error(err)) c.Write(zap.Error(err))
} }
return false, nil return false
} }
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
if !matches && !zoneFilter { if !matches && !zoneFilter {
@@ -177,7 +163,7 @@ func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
c.Write(zap.String("zone", zoneID)) c.Write(zap.String("zone", zoneID))
} }
} }
return matches, nil return matches
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
@@ -221,7 +207,7 @@ func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -252,34 +238,20 @@ func (m *MatchClientIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchClientIP) Match(r *http.Request) bool { func (m MatchClientIP) Match(r *http.Request) bool {
match, err := m.MatchWithError(r)
if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
// if handshake is not finished, we infer 0-RTT that has
// not verified remote IP; could be spoofed, so we throw
// HTTP 425 status to tell the client to try again after
// the handshake is complete
if r.TLS != nil && !r.TLS.HandshakeComplete { if r.TLS != nil && !r.TLS.HandshakeComplete {
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified")) return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
} }
address := GetVar(r.Context(), ClientIPVarKey).(string) address := GetVar(r.Context(), ClientIPVarKey).(string)
clientIP, zoneID, err := parseIPZoneFromString(address) clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil { if err != nil {
m.logger.Error("getting client IP", zap.Error(err)) m.logger.Error("getting client IP", zap.Error(err))
return false, nil return false
} }
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
if !matches && !zoneFilter { if !matches && !zoneFilter {
m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID)) m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID))
} }
return matches, nil return matches
} }
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) { func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
@@ -354,13 +326,13 @@ func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefi
// Interface guards // Interface guards
var ( var (
_ RequestMatcherWithError = (*MatchRemoteIP)(nil) _ RequestMatcher = (*MatchRemoteIP)(nil)
_ caddy.Provisioner = (*MatchRemoteIP)(nil) _ caddy.Provisioner = (*MatchRemoteIP)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ CELLibraryProducer = (*MatchRemoteIP)(nil) _ CELLibraryProducer = (*MatchRemoteIP)(nil)
_ RequestMatcherWithError = (*MatchClientIP)(nil) _ RequestMatcher = (*MatchClientIP)(nil)
_ caddy.Provisioner = (*MatchClientIP)(nil) _ caddy.Provisioner = (*MatchClientIP)(nil)
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil) _ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
_ CELLibraryProducer = (*MatchClientIP)(nil) _ CELLibraryProducer = (*MatchClientIP)(nil)
) )
-3
View File
@@ -51,9 +51,6 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
Header: r.Header, Header: r.Header,
ShouldLogCredentials: r.ShouldLogCredentials, ShouldLogCredentials: r.ShouldLogCredentials,
}) })
if r.TransferEncoding != nil {
enc.AddArray("transfer_encoding", LoggableStringArray(r.TransferEncoding))
}
if r.TLS != nil { if r.TLS != nil {
enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) enc.AddObject("tls", LoggableTLSConnState(*r.TLS))
} }
+74 -161
View File
@@ -296,12 +296,6 @@ func (m MatchHost) Provision(_ caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHost) Match(r *http.Request) bool { func (m MatchHost) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
reqHost, _, err := net.SplitHostPort(r.Host) reqHost, _, err := net.SplitHostPort(r.Host)
if err != nil { if err != nil {
// OK; probably didn't have a port // OK; probably didn't have a port
@@ -321,7 +315,7 @@ func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
return m[i] >= reqHost return m[i] >= reqHost
}) })
if pos < len(m) && m[pos] == reqHost { if pos < len(m) && m[pos] == reqHost {
return true, nil return true
} }
} }
@@ -352,13 +346,13 @@ outer:
continue outer continue outer
} }
} }
return true, nil return true
} else if strings.EqualFold(reqHost, host) { } else if strings.EqualFold(reqHost, host) {
return true, nil return true
} }
} }
return false, nil return false
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@@ -372,7 +366,7 @@ func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"host", "host",
"host_match_request_list", "host_match_request_list",
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -417,12 +411,6 @@ func (m MatchPath) Provision(_ caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPath) Match(r *http.Request) bool { func (m MatchPath) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// Even though RFC 9110 says that path matching is case-sensitive // Even though RFC 9110 says that path matching is case-sensitive
// (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3), // (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
// we do case-insensitive matching to mitigate security issues // we do case-insensitive matching to mitigate security issues
@@ -448,7 +436,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// special case: whole path is wildcard; this is unnecessary // special case: whole path is wildcard; this is unnecessary
// as it matches all requests, which is the same as no matcher // as it matches all requests, which is the same as no matcher
if matchPattern == "*" { if matchPattern == "*" {
return true, nil return true
} }
// Clean the path, merge doubled slashes, etc. // Clean the path, merge doubled slashes, etc.
@@ -476,7 +464,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
if strings.Contains(matchPattern, "%") { if strings.Contains(matchPattern, "%") {
reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes) reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes)
if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) { if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
return true, nil return true
} }
// doing prefix/suffix/substring matches doesn't make sense // doing prefix/suffix/substring matches doesn't make sense
@@ -495,7 +483,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
strings.HasPrefix(matchPattern, "*") && strings.HasPrefix(matchPattern, "*") &&
strings.HasSuffix(matchPattern, "*") { strings.HasSuffix(matchPattern, "*") {
if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) { if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
return true, nil return true
} }
continue continue
} }
@@ -507,7 +495,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// treat it as a fast suffix match // treat it as a fast suffix match
if strings.HasPrefix(matchPattern, "*") { if strings.HasPrefix(matchPattern, "*") {
if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) { if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
return true, nil return true
} }
continue continue
} }
@@ -516,7 +504,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// treat it as a fast prefix match // treat it as a fast prefix match
if strings.HasSuffix(matchPattern, "*") { if strings.HasSuffix(matchPattern, "*") {
if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) { if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
return true, nil return true
} }
continue continue
} }
@@ -527,10 +515,10 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// because we can't handle it anyway // because we can't handle it anyway
matches, _ := path.Match(matchPattern, reqPathForPattern) matches, _ := path.Match(matchPattern, reqPathForPattern)
if matches { if matches {
return true, nil return true
} }
} }
return false, nil return false
} }
func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool { func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
@@ -654,7 +642,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -689,12 +677,6 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPathRE) Match(r *http.Request) bool { func (m MatchPathRE) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
// Clean the path, merges doubled slashes, etc. // Clean the path, merges doubled slashes, etc.
@@ -702,7 +684,7 @@ func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
// the path matcher. See #4407 // the path matcher. See #4407
cleanedPath := cleanPath(r.URL.Path) cleanedPath := cleanPath(r.URL.Path)
return m.MatchRegexp.Match(cleanedPath, repl), nil return m.MatchRegexp.Match(cleanedPath, repl)
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@@ -716,7 +698,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"path_regexp", "path_regexp",
"path_regexp_request_string", "path_regexp_request_string",
[]*cel.Type{cel.StringType}, []*cel.Type{cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
pattern := data.(types.String) pattern := data.(types.String)
matcher := MatchPathRE{MatchRegexp{ matcher := MatchPathRE{MatchRegexp{
Name: ctx.Value(MatcherNameCtxKey).(string), Name: ctx.Value(MatcherNameCtxKey).(string),
@@ -733,7 +715,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"path_regexp", "path_regexp",
"path_regexp_request_string_string", "path_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -782,13 +764,7 @@ func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchMethod) Match(r *http.Request) bool { func (m MatchMethod) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r) return slices.Contains(m, r.Method)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
return slices.Contains(m, r.Method), nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@@ -802,7 +778,7 @@ func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
"method", "method",
"method_request_list", "method_request_list",
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -847,17 +823,10 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. An empty m matches an empty query string. // Match returns true if r matches m. An empty m matches an empty query string.
func (m MatchQuery) Match(r *http.Request) bool { func (m MatchQuery) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
// An empty m matches an empty query string.
func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
// If no query keys are configured, this only // If no query keys are configured, this only
// matches an empty query string. // matches an empty query string.
if len(m) == 0 { if len(m) == 0 {
return len(r.URL.Query()) == 0, nil return len(r.URL.Query()) == 0
} }
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
@@ -874,7 +843,7 @@ func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
// "Relying on parser alignment for security is doomed." Overall conclusion is that // "Relying on parser alignment for security is doomed." Overall conclusion is that
// splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;. // splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;.
// We regard the Go team's decision as sound and thus reject malformed query strings. // We regard the Go team's decision as sound and thus reject malformed query strings.
return false, nil return false
} }
// Count the amount of matched keys, to ensure we AND // Count the amount of matched keys, to ensure we AND
@@ -885,7 +854,7 @@ func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
param = repl.ReplaceAll(param, "") param = repl.ReplaceAll(param, "")
paramVal, found := parsed[param] paramVal, found := parsed[param]
if !found { if !found {
return false, nil return false
} }
for _, v := range vals { for _, v := range vals {
v = repl.ReplaceAll(v, "") v = repl.ReplaceAll(v, "")
@@ -895,7 +864,7 @@ func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
} }
} }
} }
return matchedKeys == len(m), nil return matchedKeys == len(m)
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@@ -909,7 +878,7 @@ func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
"query", "query",
"query_matcher_request_map", "query_matcher_request_map",
[]*cel.Type{CELTypeJSON}, []*cel.Type{CELTypeJSON},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
mapStrListStr, err := CELValueToMapStrList(data) mapStrListStr, err := CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -971,14 +940,8 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHeader) Match(r *http.Request) bool { func (m MatchHeader) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
return matchHeaders(r.Header, http.Header(m), r.Host, r.TransferEncoding, repl), nil return matchHeaders(r.Header, http.Header(m), r.Host, repl)
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@@ -993,7 +956,7 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
"header", "header",
"header_matcher_request_map", "header_matcher_request_map",
[]*cel.Type{CELTypeJSON}, []*cel.Type{CELTypeJSON},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
mapStrListStr, err := CELValueToMapStrList(data) mapStrListStr, err := CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1004,26 +967,22 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
} }
// getHeaderFieldVals returns the field values for the given fieldName from input. // getHeaderFieldVals returns the field values for the given fieldName from input.
// The host parameter should be obtained from the http.Request.Host field, and the // The host parameter should be obtained from the http.Request.Host field since
// transferEncoding from http.Request.TransferEncoding, since net/http removes them // net/http removes it from the header map.
// from the header map. func getHeaderFieldVals(input http.Header, fieldName, host string) []string {
func getHeaderFieldVals(input http.Header, fieldName, host string, transferEncoding []string) []string {
fieldName = textproto.CanonicalMIMEHeaderKey(fieldName) fieldName = textproto.CanonicalMIMEHeaderKey(fieldName)
if fieldName == "Host" && host != "" { if fieldName == "Host" && host != "" {
return []string{host} return []string{host}
} }
if fieldName == "Transfer-Encoding" && input[fieldName] == nil {
return transferEncoding
}
return input[fieldName] return input[fieldName]
} }
// matchHeaders returns true if input matches the criteria in against without regex. // matchHeaders returns true if input matches the criteria in against without regex.
// The host parameter should be obtained from the http.Request.Host field since // The host parameter should be obtained from the http.Request.Host field since
// net/http removes it from the header map. // net/http removes it from the header map.
func matchHeaders(input, against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool { func matchHeaders(input, against http.Header, host string, repl *caddy.Replacer) bool {
for field, allowedFieldVals := range against { for field, allowedFieldVals := range against {
actualFieldVals := getHeaderFieldVals(input, field, host, transferEncoding) actualFieldVals := getHeaderFieldVals(input, field, host)
if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil { if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil {
// a non-nil but empty list of allowed values means // a non-nil but empty list of allowed values means
// match if the header field exists at all // match if the header field exists at all
@@ -1116,14 +1075,8 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHeaderRE) Match(r *http.Request) bool { func (m MatchHeaderRE) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
for field, rm := range m { for field, rm := range m {
actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host, r.TransferEncoding) actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host)
match := false match := false
fieldVal: fieldVal:
for _, actualFieldVal := range actualFieldVals { for _, actualFieldVal := range actualFieldVals {
@@ -1134,10 +1087,10 @@ func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
} }
} }
if !match { if !match {
return false, nil return false
} }
} }
return true, nil return true
} }
// Provision compiles m's regular expressions. // Provision compiles m's regular expressions.
@@ -1173,7 +1126,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp", "header_regexp",
"header_regexp_request_string_string", "header_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -1196,7 +1149,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp", "header_regexp",
"header_regexp_request_string_string_string", "header_regexp_request_string_string_string",
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@@ -1234,37 +1187,31 @@ func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchProtocol) Match(r *http.Request) bool { func (m MatchProtocol) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
switch string(m) { switch string(m) {
case "grpc": case "grpc":
return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc"), nil return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc")
case "https": case "https":
return r.TLS != nil, nil return r.TLS != nil
case "http": case "http":
return r.TLS == nil, nil return r.TLS == nil
case "http/1.0": case "http/1.0":
return r.ProtoMajor == 1 && r.ProtoMinor == 0, nil return r.ProtoMajor == 1 && r.ProtoMinor == 0
case "http/1.0+": case "http/1.0+":
return r.ProtoAtLeast(1, 0), nil return r.ProtoAtLeast(1, 0)
case "http/1.1": case "http/1.1":
return r.ProtoMajor == 1 && r.ProtoMinor == 1, nil return r.ProtoMajor == 1 && r.ProtoMinor == 1
case "http/1.1+": case "http/1.1+":
return r.ProtoAtLeast(1, 1), nil return r.ProtoAtLeast(1, 1)
case "http/2": case "http/2":
return r.ProtoMajor == 2, nil return r.ProtoMajor == 2
case "http/2+": case "http/2+":
return r.ProtoAtLeast(2, 0), nil return r.ProtoAtLeast(2, 0)
case "http/3": case "http/3":
return r.ProtoMajor == 3, nil return r.ProtoMajor == 3
case "http/3+": case "http/3+":
return r.ProtoAtLeast(3, 0), nil return r.ProtoAtLeast(3, 0)
} }
return false, nil return false
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@@ -1291,7 +1238,7 @@ func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
"protocol", "protocol",
"protocol_request_string", "protocol_request_string",
[]*cel.Type{cel.StringType}, []*cel.Type{cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) { func(data ref.Val) (RequestMatcher, error) {
protocolStr, ok := data.(types.String) protocolStr, ok := data.(types.String)
if !ok { if !ok {
return nil, errors.New("protocol argument was not a string") return nil, errors.New("protocol argument was not a string")
@@ -1311,22 +1258,16 @@ func (MatchTLS) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchTLS) Match(r *http.Request) bool { func (m MatchTLS) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
if r.TLS == nil { if r.TLS == nil {
return false, nil return false
} }
if m.HandshakeComplete != nil { if m.HandshakeComplete != nil {
if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) || if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
(*m.HandshakeComplete && !r.TLS.HandshakeComplete) { (*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
return false, nil return false
} }
} }
return true, nil return true
} }
// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax: // UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
@@ -1396,15 +1337,7 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
for _, modMap := range matcherSets.([]map[string]any) { for _, modMap := range matcherSets.([]map[string]any) {
var ms MatcherSet var ms MatcherSet
for _, modIface := range modMap { for _, modIface := range modMap {
if mod, ok := modIface.(RequestMatcherWithError); ok { ms = append(ms, modIface.(RequestMatcher))
ms = append(ms, mod)
continue
}
if mod, ok := modIface.(RequestMatcher); ok {
ms = append(ms, mod)
continue
}
return fmt.Errorf("module is not a request matcher: %T", modIface)
} }
m.MatcherSets = append(m.MatcherSets, ms) m.MatcherSets = append(m.MatcherSets, ms)
} }
@@ -1415,24 +1348,12 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
// the embedded matchers, false is returned if any of its matcher // the embedded matchers, false is returned if any of its matcher
// sets return true. // sets return true.
func (m MatchNot) Match(r *http.Request) bool { func (m MatchNot) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m. Since this matcher
// negates the embedded matchers, false is returned if any of its
// matcher sets return true.
func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {
for _, ms := range m.MatcherSets { for _, ms := range m.MatcherSets {
matches, err := ms.MatchWithError(r) if ms.Match(r) {
if err != nil { return false
return false, err
}
if matches {
return false, nil
} }
} }
return true, nil return true
} }
// MatchRegexp is an embedable type for matching // MatchRegexp is an embedable type for matching
@@ -1548,7 +1469,7 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested // ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested
// matcher set, and returns its raw module map value. // matcher set, and returns its raw module map value.
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
matcherMap := make(map[string]any) matcherMap := make(map[string]RequestMatcher)
// in case there are multiple instances of the same matcher, concatenate // in case there are multiple instances of the same matcher, concatenate
// their tokens (we expect that UnmarshalCaddyfile should be able to // their tokens (we expect that UnmarshalCaddyfile should be able to
@@ -1573,15 +1494,11 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rm, ok := unm.(RequestMatcherWithError); ok { rm, ok := unm.(RequestMatcher)
matcherMap[matcherName] = rm if !ok {
continue return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
if rm, ok := unm.(RequestMatcher); ok { matcherMap[matcherName] = rm
matcherMap[matcherName] = rm
continue
}
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
// we should now have a functional matcher, but we also // we should now have a functional matcher, but we also
@@ -1607,28 +1524,24 @@ const regexpPlaceholderPrefix = "http.regexp"
// holds an optional error emitted from a request matcher, // holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot // to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface. // return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling it
// through the vars map.
const MatcherErrorVarKey = "matchers.error" const MatcherErrorVarKey = "matchers.error"
// Interface guards // Interface guards
var ( var (
_ RequestMatcherWithError = (*MatchHost)(nil) _ RequestMatcher = (*MatchHost)(nil)
_ caddy.Provisioner = (*MatchHost)(nil) _ caddy.Provisioner = (*MatchHost)(nil)
_ RequestMatcherWithError = (*MatchPath)(nil) _ RequestMatcher = (*MatchPath)(nil)
_ RequestMatcherWithError = (*MatchPathRE)(nil) _ RequestMatcher = (*MatchPathRE)(nil)
_ caddy.Provisioner = (*MatchPathRE)(nil) _ caddy.Provisioner = (*MatchPathRE)(nil)
_ RequestMatcherWithError = (*MatchMethod)(nil) _ RequestMatcher = (*MatchMethod)(nil)
_ RequestMatcherWithError = (*MatchQuery)(nil) _ RequestMatcher = (*MatchQuery)(nil)
_ RequestMatcherWithError = (*MatchHeader)(nil) _ RequestMatcher = (*MatchHeader)(nil)
_ RequestMatcherWithError = (*MatchHeaderRE)(nil) _ RequestMatcher = (*MatchHeaderRE)(nil)
_ caddy.Provisioner = (*MatchHeaderRE)(nil) _ caddy.Provisioner = (*MatchHeaderRE)(nil)
_ RequestMatcherWithError = (*MatchProtocol)(nil) _ RequestMatcher = (*MatchProtocol)(nil)
_ RequestMatcherWithError = (*MatchNot)(nil) _ RequestMatcher = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchNot)(nil) _ caddy.Provisioner = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchRegexp)(nil) _ caddy.Provisioner = (*MatchRegexp)(nil)
_ caddyfile.Unmarshaler = (*MatchHost)(nil) _ caddyfile.Unmarshaler = (*MatchHost)(nil)
_ caddyfile.Unmarshaler = (*MatchPath)(nil) _ caddyfile.Unmarshaler = (*MatchPath)(nil)
+13 -40
View File
@@ -158,10 +158,7 @@ func TestHostMatcher(t *testing.T) {
t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err) t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err)
} }
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@@ -433,10 +430,7 @@ func TestPathMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@@ -457,10 +451,7 @@ func TestPathMatcherWindows(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
match := MatchPath{"*.php"} match := MatchPath{"*.php"}
matched, err := match.MatchWithError(req) matched := match.Match(req)
if err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
if !matched { if !matched {
t.Errorf("Expected to match; should ignore trailing dots and spaces") t.Errorf("Expected to match; should ignore trailing dots and spaces")
} }
@@ -564,10 +555,7 @@ func TestPathREMatcher(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match.Pattern, tc.expect, actual, tc.input) i, tc.match.Pattern, tc.expect, actual, tc.input)
@@ -703,10 +691,7 @@ func TestHeaderMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@@ -833,10 +818,7 @@ func TestQueryMatcher(t *testing.T) {
repl.Set("http.vars.debug", "1") repl.Set("http.vars.debug", "1")
repl.Set("http.vars.key", "somekey") repl.Set("http.vars.key", "somekey")
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@@ -905,10 +887,7 @@ func TestHeaderREMatcher(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match, tc.expect, actual, tc.input) i, tc.match, tc.expect, actual, tc.input)
@@ -948,7 +927,7 @@ func BenchmarkHeaderREMatcher(b *testing.B) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
for run := 0; run < b.N; run++ { for run := 0; run < b.N; run++ {
match.MatchWithError(req) match.Match(req)
} }
} }
@@ -1019,10 +998,7 @@ func TestVarREMatcher(t *testing.T) {
tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler) tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match, tc.expect, actual, tc.input) i, tc.match, tc.expect, actual, tc.input)
@@ -1147,10 +1123,7 @@ func TestNotMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual, err := tc.match.MatchWithError(req) actual := tc.match.Match(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path) t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
continue continue
@@ -1182,7 +1155,7 @@ func BenchmarkLargeHostMatcher(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
matcher.MatchWithError(req) matcher.Match(req)
} }
} }
@@ -1196,7 +1169,7 @@ func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
match.MatchWithError(req) match.Match(req)
} }
} }
@@ -1214,6 +1187,6 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
match.MatchWithError(req) match.Match(req)
} }
} }
+2 -3
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
@@ -134,8 +133,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
if h.metrics.PerHost { if h.metrics.PerHost {
labels["host"] = strings.ToLower(r.Host) labels["host"] = r.Host
statusLabels["host"] = strings.ToLower(r.Host) statusLabels["host"] = r.Host
} }
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)
-11
View File
@@ -186,11 +186,6 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return path.Ext(req.URL.Path), true return path.Ext(req.URL.Path), true
case "http.request.uri.query": case "http.request.uri.query":
return req.URL.RawQuery, true return req.URL.RawQuery, true
case "http.request.uri.prefixed_query":
if req.URL.RawQuery == "" {
return "", true
}
return "?" + req.URL.RawQuery, true
case "http.request.duration": case "http.request.duration":
start := GetVar(req.Context(), "start_time").(time.Time) start := GetVar(req.Context(), "start_time").(time.Time)
return time.Since(start), true return time.Since(start), true
@@ -244,12 +239,6 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
case "http.request.orig_uri.query": case "http.request.orig_uri.query":
or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
return or.URL.RawQuery, true return or.URL.RawQuery, true
case "http.request.orig_uri.prefixed_query":
or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
if or.URL.RawQuery == "" {
return "", true
}
return "?" + or.URL.RawQuery, true
} }
// remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24") // remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24")
+1 -3
View File
@@ -15,7 +15,6 @@
package requestbody package requestbody
import ( import (
"errors"
"io" "io"
"net/http" "net/http"
"time" "time"
@@ -95,8 +94,7 @@ type errorWrapper struct {
func (ew errorWrapper) Read(p []byte) (n int, err error) { func (ew errorWrapper) Read(p []byte) (n int, err error) {
n, err = ew.ReadCloser.Read(p) n, err = ew.ReadCloser.Read(p)
var mbe *http.MaxBytesError if err != nil && err.Error() == "http: request body too large" {
if errors.As(err, &mbe) {
err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err) err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err)
} }
return return
+1 -1
View File
@@ -41,7 +41,7 @@ func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool {
if !rm.matchStatusCode(statusCode) { if !rm.matchStatusCode(statusCode) {
return false return false
} }
return matchHeaders(hdr, rm.Headers, "", []string{}, nil) return matchHeaders(hdr, rm.Headers, "", nil)
} }
func (rm ResponseMatcher) matchStatusCode(statusCode int) bool { func (rm ResponseMatcher) matchStatusCode(statusCode int) bool {
@@ -1504,7 +1504,6 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Errf("bad delay value '%s': %v", d.Val(), err) return d.Errf("bad delay value '%s': %v", d.Val(), err)
} }
u.FallbackDelay = caddy.Duration(dur) u.FallbackDelay = caddy.Duration(dur)
case "grace_period": case "grace_period":
if !d.NextArg() { if !d.NextArg() {
return d.ArgErr() return d.ArgErr()
@@ -18,7 +18,8 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/dustin/go-humanize"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -44,7 +45,10 @@ func init() {
// dial_timeout <duration> // dial_timeout <duration>
// read_timeout <duration> // read_timeout <duration>
// write_timeout <duration> // write_timeout <duration>
// capture_stderr // body_buffer_disabled
// body_buffer_memory_limit <size>
// file_buffer_size_limit <size>
// file_buffer_filepath <path>
// } // }
func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume transport name d.Next() // consume transport name
@@ -114,6 +118,35 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} }
t.CaptureStderr = true t.CaptureStderr = true
case "body_buffer_disabled":
t.BodyBufferDisabled = true
case "body_buffer_memory_limit":
if !d.NextArg() {
return d.ArgErr()
}
size, err := humanize.ParseBytes(d.Val())
if err != nil {
return d.Errf("bad buffer size %s: %v", d.Val(), err)
}
t.BodyBufferMemoryLimit = int64(size)
case "file_buffer_size_limit":
if !d.NextArg() {
return d.ArgErr()
}
size, err := humanize.ParseBytes(d.Val())
if err != nil {
return d.Errf("bad buffer size %s: %v", d.Val(), err)
}
t.FileBufferSizeLimit = int64(size)
case "file_buffer_filepath":
if !d.NextArg() {
return d.ArgErr()
}
t.FileBufferFilepath = d.Val()
default: default:
return d.Errf("unrecognized subdirective %s", d.Val()) return d.Errf("unrecognized subdirective %s", d.Val())
} }
@@ -180,7 +213,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
indexFile := "index.php" indexFile := "index.php"
// set up for explicitly overriding try_files // set up for explicitly overriding try_files
var tryFiles []string tryFiles := []string{}
// if the user specified a matcher token, use that // if the user specified a matcher token, use that
// matcher in a route that wraps both of our routes; // matcher in a route that wraps both of our routes;
@@ -295,6 +328,40 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
args := dispenser.RemainingArgs() args := dispenser.RemainingArgs()
dispenser.DeleteN(len(args) + 1) dispenser.DeleteN(len(args) + 1)
fcgiTransport.CaptureStderr = true fcgiTransport.CaptureStderr = true
case "body_buffer_disabled":
args := dispenser.RemainingArgs()
dispenser.DeleteN(len(args) + 1)
fcgiTransport.BodyBufferDisabled = true
case "body_buffer_memory_limit":
if !dispenser.NextArg() {
return nil, dispenser.ArgErr()
}
size, err := humanize.ParseBytes(dispenser.Val())
if err != nil {
return nil, dispenser.Errf("bad buffer size %s: %v", dispenser.Val(), err)
}
fcgiTransport.BodyBufferMemoryLimit = int64(size)
dispenser.DeleteN(2)
case "file_buffer_size_limit":
if !dispenser.NextArg() {
return nil, dispenser.ArgErr()
}
size, err := humanize.ParseBytes(dispenser.Val())
if err != nil {
return nil, dispenser.Errf("bad buffer size %s: %v", dispenser.Val(), err)
}
fcgiTransport.FileBufferSizeLimit = int64(size)
dispenser.DeleteN(2)
case "file_buffer_filepath":
if !dispenser.NextArg() {
return nil, dispenser.ArgErr()
}
fcgiTransport.FileBufferFilepath = dispenser.Val()
dispenser.DeleteN(2)
} }
} }
} }
@@ -311,60 +378,37 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// if the index is turned off, we skip the redirect and try_files // if the index is turned off, we skip the redirect and try_files
if indexFile != "off" { if indexFile != "off" {
dirRedir := false // route to redirect to canonical path if index PHP file
dirIndex := "{http.request.uri.path}/" + indexFile redirMatcherSet := caddy.ModuleMap{
tryPolicy := "first_exist_fallback" "file": h.JSON(fileserver.MatchFile{
TryFiles: []string{"{http.request.uri.path}/" + indexFile},
}),
"not": h.JSON(caddyhttp.MatchNot{
MatcherSetsRaw: []caddy.ModuleMap{
{
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
},
},
}),
}
redirHandler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/"}},
}
redirRoute := caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
// if tryFiles wasn't overridden, use a reasonable default // if tryFiles wasn't overridden, use a reasonable default
if len(tryFiles) == 0 { if len(tryFiles) == 0 {
tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile} tryFiles = []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}
dirRedir = true
} else {
if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") {
// use first_exist strategy if the last file is not a PHP file
tryPolicy = ""
}
for _, tf := range tryFiles {
if tf == dirIndex {
dirRedir = true
break
}
}
}
if dirRedir {
// route to redirect to canonical path if index PHP file
redirMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{
TryFiles: []string{dirIndex},
}),
"not": h.JSON(caddyhttp.MatchNot{
MatcherSetsRaw: []caddy.ModuleMap{
{
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
},
},
}),
}
redirHandler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"}},
}
redirRoute := caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
routes = append(routes, redirRoute)
} }
// route to rewrite to PHP index file // route to rewrite to PHP index file
rewriteMatcherSet := caddy.ModuleMap{ rewriteMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{ "file": h.JSON(fileserver.MatchFile{
TryFiles: tryFiles, TryFiles: tryFiles,
TryPolicy: tryPolicy,
SplitPath: extensions, SplitPath: extensions,
}), }),
} }
@@ -376,7 +420,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
} }
routes = append(routes, rewriteRoute) routes = append(routes, redirRoute, rewriteRoute)
} }
// route to actually reverse proxy requests to PHP files; // route to actually reverse proxy requests to PHP files;
@@ -41,8 +41,6 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// FCGIListenSockFileno describes listen socket file number. // FCGIListenSockFileno describes listen socket file number.
@@ -130,23 +128,16 @@ var pad [maxPad]byte
type client struct { type client struct {
rwc net.Conn rwc net.Conn
// keepAlive bool // TODO: implement // keepAlive bool // TODO: implement
reqID uint16 reqID uint16
stderr bool stderr bool
logger *zap.Logger logger *zap.Logger
buffer bool
bufferFunc func(io.Reader) (int64, io.ReadCloser, error)
} }
// Do made the request and returns a io.Reader that translates the data read // Do made the request and returns a io.Reader that translates the data read
// from fcgi responder out of fcgi packet before returning it. // from fcgi responder out of fcgi packet before returning it.
func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
// check for CONTENT_LENGTH, since the lack of it or wrong value will cause the backend to hang
if clStr, ok := p["CONTENT_LENGTH"]; !ok {
return nil, caddyhttp.Error(http.StatusLengthRequired, nil)
} else if _, err := strconv.ParseUint(clStr, 10, 64); err != nil {
// stdlib won't return a negative Content-Length, but we check just in case,
// the most likely cause is from a missing content length, which is -1
return nil, caddyhttp.Error(http.StatusLengthRequired, err)
}
writer := &streamWriter{c: c} writer := &streamWriter{c: c}
writer.buf = bufPool.Get().(*bytes.Buffer) writer.buf = bufPool.Get().(*bytes.Buffer)
writer.buf.Reset() writer.buf.Reset()
@@ -169,6 +160,10 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// body length mismatch
if lr, ok := req.(*io.LimitedReader); ok && lr.N > 0 {
return nil, io.ErrUnexpectedEOF
}
} }
err = writer.FlushStream() err = writer.FlushStream()
if err != nil { if err != nil {
@@ -208,9 +203,68 @@ func (f clientCloser) Close() error {
return f.rwc.Close() return f.rwc.Close()
} }
// create a response that describes the error message, it's passed to the client.
// caller should close the connection to fastcgi server
func newErrorResponse(status int) *http.Response {
statusText := http.StatusText(status)
resp := &http.Response{
Status: statusText,
StatusCode: status,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(statusText)),
ContentLength: int64(len(statusText)),
}
resp.Header.Set("Content-Length", strconv.FormatInt(resp.ContentLength, 10))
resp.Header.Set("Content-Type", "text/plain; charset=utf-8")
resp.Header.Set("X-Content-Type-Options", "nosniff")
return resp
}
// check for invalid content_length to determine if the request should be buffered
func checkContentLength(p map[string]string) (int64, bool) {
clStr, ok := p["CONTENT_LENGTH"]
if !ok {
return 0, false
}
cl, err := strconv.ParseInt(clStr, 10, 64)
if err != nil || cl < 0 {
return 0, false
}
return cl, true
}
// Request returns a HTTP Response with Header and Body // Request returns a HTTP Response with Header and Body
// from fcgi responder // from fcgi responder
func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
// defer closing the request body if it's an io.Closer
if closer, ok := req.(io.Closer); ok {
defer closer.Close()
}
// check for content_length and buffer the request if needed
cl, ok := checkContentLength(p)
if !ok {
// buffering disabled
if !c.buffer {
c.rwc.Close()
return newErrorResponse(http.StatusLengthRequired), nil
} else {
// buffer the request
size, rc, err := c.bufferFunc(req)
if err != nil {
if err == errFileBufferExceeded {
c.rwc.Close()
return newErrorResponse(http.StatusRequestEntityTooLarge), nil
}
return nil, err
}
defer rc.Close()
p["CONTENT_LENGTH"] = strconv.FormatInt(size, 10)
}
} else {
req = io.LimitReader(req, cl)
}
r, err := c.Do(p, req) r, err := c.Do(p, req)
if err != nil { if err != nil {
return return
@@ -313,7 +367,7 @@ func (c *client) Post(p map[string]string, method string, bodyType string, body
// PostForm issues a POST to the fcgi responder, with form // PostForm issues a POST to the fcgi responder, with form
// as a string key to a list values (url.Values) // as a string key to a list values (url.Values)
func (c *client) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) { func (c *client) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) {
body := bytes.NewReader([]byte(data.Encode())) body := strings.NewReader(data.Encode())
return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len())) return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len()))
} }
@@ -15,10 +15,14 @@
package fastcgi package fastcgi
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@@ -80,8 +84,18 @@ type Transport struct {
// be used instead. // be used instead.
CaptureStderr bool `json:"capture_stderr,omitempty"` CaptureStderr bool `json:"capture_stderr,omitempty"`
serverSoftware string // disable buffering of the request body that doesn't have a content length
logger *zap.Logger BodyBufferDisabled bool `json:"body_buffer_disabled,omitempty"`
// memory limit for buffering the request body, the rest will be buffered by temporary files
BodyBufferMemoryLimit int64 `json:"body_buffer_memory_limit,omitempty"`
// total disk storage allowed by the request body buffer
FileBufferSizeLimit int64 `json:"file_buffer_size_limit,omitempty"`
// the path to store the temporary files for the request body buffer
FileBufferFilepath string `json:"file_buffer_filepath,omitempty"`
serverSoftware string
logger *zap.Logger
tempFileLimiter *fileQuotaLimiter
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
@@ -92,6 +106,15 @@ func (Transport) CaddyModule() caddy.ModuleInfo {
} }
} }
const (
defaultDialTimeout = 3 * time.Second
// nginx default for 64bit platforms
// https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size
defaultMemBufferSize = 1 << 14 // 16KB
// nginx doesn't have an option to limit the total file buffer size
defaultFileBufferSize = 100 << 20 // 100MB
)
// Provision sets up t. // Provision sets up t.
func (t *Transport) Provision(ctx caddy.Context) error { func (t *Transport) Provision(ctx caddy.Context) error {
t.logger = ctx.Logger() t.logger = ctx.Logger()
@@ -106,12 +129,144 @@ func (t *Transport) Provision(ctx caddy.Context) error {
// Set a relatively short default dial timeout. // Set a relatively short default dial timeout.
// This is helpful to make load-balancer retries more speedy. // This is helpful to make load-balancer retries more speedy.
if t.DialTimeout == 0 { if t.DialTimeout == 0 {
t.DialTimeout = caddy.Duration(3 * time.Second) t.DialTimeout = caddy.Duration(defaultDialTimeout)
}
if !t.BodyBufferDisabled {
if t.FileBufferFilepath == "" {
t.FileBufferFilepath = os.TempDir()
}
// test if temporary file can be created
file, err := os.CreateTemp(t.FileBufferFilepath, "caddy-fastcgi-buffer-")
if err != nil {
return fmt.Errorf("failed to create temporary file: %v", err)
}
file.Close()
os.Remove(file.Name())
if t.BodyBufferMemoryLimit == 0 {
t.BodyBufferMemoryLimit = defaultMemBufferSize
}
if t.FileBufferSizeLimit == 0 {
t.FileBufferSizeLimit = defaultFileBufferSize
}
t.tempFileLimiter = newFileQuotaLimiter(t.FileBufferSizeLimit)
} }
return nil return nil
} }
type bufferedBody struct {
memBuf *bytes.Buffer
fileBuf *os.File
filesize int64
tempFileLimiter *fileQuotaLimiter
}
func (b *bufferedBody) Read(p []byte) (int, error) {
if b.memBuf != nil {
if b.memBuf.Len() != 0 {
return b.memBuf.Read(p)
}
bufPool.Put(b.memBuf)
b.memBuf = nil
}
if b.fileBuf != nil {
n, err := b.fileBuf.Read(p)
if err != nil {
// close the file and remove it
b.fileBuf.Close()
os.Remove(b.fileBuf.Name())
b.tempFileLimiter.release(b.filesize)
b.fileBuf = nil
return n, err
}
}
return 0, io.EOF
}
func (b *bufferedBody) Close() error {
if b.memBuf != nil {
bufPool.Put(b.memBuf)
b.memBuf = nil
}
if b.fileBuf != nil {
b.fileBuf.Close()
os.Remove(b.fileBuf.Name())
b.tempFileLimiter.release(b.filesize)
b.fileBuf = nil
}
return nil
}
var errFileBufferExceeded = errors.New("temporary file buffer limit exceeded")
func (t Transport) bufferBodyToFile(file *os.File, req io.Reader) (int64, error) {
buf := streamingBufPool.Get().(*[]byte)
defer streamingBufPool.Put(buf)
var size int64
for {
reserved := t.tempFileLimiter.acquire(readBufSize)
if !reserved {
return size, errFileBufferExceeded
}
n, er := req.Read(*buf)
if n > 0 {
nw, ew := file.Write((*buf)[:n])
size += int64(nw)
t.tempFileLimiter.release(int64(readBufSize - nw))
if ew != nil {
return size, ew
}
}
if er != nil {
if er == io.EOF {
return size, nil
}
return size, er
}
}
}
func (t Transport) bufferBody(req io.Reader) (int64, io.ReadCloser, error) {
if closer, ok := req.(io.Closer); ok {
defer closer.Close()
}
memBuf := bufPool.Get().(*bytes.Buffer)
memBuf.Reset()
size, err := io.CopyN(memBuf, req, t.BodyBufferMemoryLimit)
var body bufferedBody // should be closed in case buffering fails
body.memBuf = memBuf
body.tempFileLimiter = t.tempFileLimiter
// error while reading the body
if err != nil {
// fully buffered in memory
if err == io.EOF {
return size, &body, nil
}
body.Close()
return 0, nil, err
}
// temporary file is needed here.
fileBuf, err := os.CreateTemp(t.FileBufferFilepath, "caddy-fastcgi-buffer-")
if err != nil {
body.Close()
return 0, nil, err
}
body.fileBuf = fileBuf
// buffer the rest of the body to the file
fSize, err := t.bufferBodyToFile(fileBuf, req)
body.filesize = fSize
if err != nil {
body.Close()
return 0, nil, err
}
return size + fSize, &body, nil
}
// RoundTrip implements http.RoundTripper. // RoundTrip implements http.RoundTripper.
func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
@@ -171,10 +326,12 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
// create the client that will facilitate the protocol // create the client that will facilitate the protocol
client := client{ client := client{
rwc: conn, rwc: conn,
reqID: 1, reqID: 1,
logger: logger, logger: logger,
stderr: t.CaptureStderr, stderr: t.CaptureStderr,
buffer: !t.BodyBufferDisabled,
bufferFunc: t.bufferBody,
} }
// read/write timeouts // read/write timeouts
@@ -228,7 +385,7 @@ func (t Transport) buildEnv(r *http.Request) (envVars, error) {
ip = strings.Replace(ip, "]", "", 1) ip = strings.Replace(ip, "]", "", 1)
// make sure file root is absolute // make sure file root is absolute
root, err := caddy.FastAbs(repl.ReplaceAll(t.Root, ".")) root, err := filepath.Abs(repl.ReplaceAll(t.Root, "."))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -24,3 +24,16 @@ var bufPool = sync.Pool{
return new(bytes.Buffer) return new(bytes.Buffer)
}, },
} }
const readBufSize = 4096
var streamingBufPool = sync.Pool{
New: func() any {
// The Pool's New function should generally only return pointer
// types, since a pointer can be put into the return interface
// value without an allocation
// - (from the package docs)
b := make([]byte, readBufSize)
return &b
},
}
@@ -0,0 +1,34 @@
package fastcgi
import "sync"
type fileQuotaLimiter struct {
maxUsage int64
currentUsage int64
mu sync.Mutex
}
func newFileQuotaLimiter(maxUsage int64) *fileQuotaLimiter {
return &fileQuotaLimiter{
maxUsage: maxUsage,
}
}
func (l *fileQuotaLimiter) acquire(n int64) bool {
l.mu.Lock()
defer l.mu.Unlock()
if l.currentUsage+n > l.maxUsage {
return false
}
l.currentUsage += n
return true
}
func (l *fileQuotaLimiter) release(n int64) {
l.mu.Lock()
defer l.mu.Unlock()
l.currentUsage -= n
}
@@ -17,7 +17,6 @@ package forwardauth
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"sort"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@@ -171,66 +170,42 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
return nil, dispenser.Errf("the 'uri' subdirective is required") return nil, dispenser.Errf("the 'uri' subdirective is required")
} }
// Set up handler for good responses; when a response has 2xx status, // set up handler for good responses; when a response
// then we will copy some headers from the response onto the original // has 2xx status, then we will copy some headers from
// request, and allow handling to continue down the middleware chain, // the response onto the original request, and allow
// by _not_ executing a terminal handler. We must have at least one // handling to continue down the middleware chain,
// route in the response handler, even if it's no-op, so that the // by _not_ executing a terminal handler.
// response handling logic in reverse_proxy doesn't skip this entry.
goodResponseHandler := caddyhttp.ResponseHandler{ goodResponseHandler := caddyhttp.ResponseHandler{
Match: &caddyhttp.ResponseMatcher{ Match: &caddyhttp.ResponseMatcher{
StatusCode: []int{2}, StatusCode: []int{2},
}, },
Routes: []caddyhttp.Route{ Routes: []caddyhttp.Route{},
{ }
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
&caddyhttp.VarsMiddleware{}, handler := &headers.Handler{
"handler", Request: &headers.HeaderOps{
"vars", Set: http.Header{},
nil,
)},
},
}, },
} }
// Sort the headers so that the order in the JSON output is deterministic. // the list of headers to copy may be empty, but that's okay; we
sortedHeadersToCopy := make([]string, 0, len(headersToCopy)) // need at least one handler in the routes for the response handling
for k := range headersToCopy { // logic in reverse_proxy to not skip this entry as empty.
sortedHeadersToCopy = append(sortedHeadersToCopy, k) for from, to := range headersToCopy {
handler.Request.Set.Set(to, "{http.reverse_proxy.header."+http.CanonicalHeaderKey(from)+"}")
} }
sort.Strings(sortedHeadersToCopy)
// Set up handlers to copy headers from the auth response onto the goodResponseHandler.Routes = append(
// original request. We use vars matchers to test that the placeholder goodResponseHandler.Routes,
// values aren't empty, because the header handler would not replace caddyhttp.Route{
// placeholders which have no value.
copyHeaderRoutes := []caddyhttp.Route{}
for _, from := range sortedHeadersToCopy {
to := http.CanonicalHeaderKey(headersToCopy[from])
placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from)
handler := &headers.Handler{
Request: &headers.HeaderOps{
Set: http.Header{
to: []string{"{" + placeholderName + "}"},
},
},
}
copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{{
"not": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{
"vars": h.JSON(caddyhttp.VarsMatcher{"{" + placeholderName + "}": []string{""}}),
}}}),
}},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject( HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
handler, handler,
"handler", "handler",
"headers", "headers",
nil, nil,
)}, )},
}) },
} )
goodResponseHandler.Routes = append(goodResponseHandler.Routes, copyHeaderRoutes...)
// note that when a response has any other status than 2xx, then we // note that when a response has any other status than 2xx, then we
// use the reverse proxy's default behaviour of copying the response // use the reverse proxy's default behaviour of copying the response
@@ -72,7 +72,7 @@ type HealthChecks struct {
// health checks (that is, health checks which occur in a // health checks (that is, health checks which occur in a
// background goroutine independently). // background goroutine independently).
type ActiveHealthChecks struct { type ActiveHealthChecks struct {
// Deprecated: Use 'uri' instead. This field will be removed. TODO: remove this field // DEPRECATED: Use 'uri' instead. This field will be removed. TODO: remove this field
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
// The URI (path and query) to use for health checks // The URI (path and query) to use for health checks
-3
View File
@@ -283,6 +283,3 @@ const proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info"
type ProxyProtocolInfo struct { type ProxyProtocolInfo struct {
AddrPort netip.AddrPort AddrPort netip.AddrPort
} }
// proxyVarKey is the key used that indicates the proxy server used for a request.
const proxyVarKey = "reverse_proxy.proxy"
+19 -21
View File
@@ -236,15 +236,15 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
} }
dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { dialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
// The network is usually tcp, and the address is the host in http.Request.URL.Host // For unix socket upstreams, we need to recover the dial info from
// and that's been overwritten in directRequest // the request's context, because the Host on the request's URL
// However, if proxy is used according to http.ProxyFromEnvironment or proxy providers, // will have been modified by directing the request, overwriting
// address will be the address of the proxy server. // the unix socket filename.
// Also, we need to avoid overwriting the address at this point
// This means we can safely use the address in dialInfo if proxy is not used (the address and network will be same any way) // when not necessary, because http.ProxyFromEnvironment may have
// or if the upstream is unix (because there is no way socks or http proxy can be used for unix address). // modified the address according to the user's env proxy config.
if dialInfo, ok := GetDialInfo(ctx); ok { if dialInfo, ok := GetDialInfo(ctx); ok {
if caddyhttp.GetVar(ctx, proxyVarKey) == nil || strings.HasPrefix(dialInfo.Network, "unix") { if strings.HasPrefix(dialInfo.Network, "unix") {
network = dialInfo.Network network = dialInfo.Network
address = dialInfo.Address address = dialInfo.Address
} }
@@ -339,19 +339,9 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
} else { } else {
proxy = http.ProxyFromEnvironment proxy = http.ProxyFromEnvironment
} }
// we need to keep track if a proxy is used for a request
proxyWrapper := func(req *http.Request) (*url.URL, error) {
u, err := proxy(req)
if u == nil || err != nil {
return u, err
}
// there must be a proxy for this request
caddyhttp.SetVar(req.Context(), proxyVarKey, u)
return u, nil
}
rt := &http.Transport{ rt := &http.Transport{
Proxy: proxyWrapper, Proxy: proxy,
DialContext: dialContext, DialContext: dialContext,
MaxConnsPerHost: h.MaxConnsPerHost, MaxConnsPerHost: h.MaxConnsPerHost,
ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout), ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout),
@@ -380,6 +370,14 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout) rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
} }
// The proxy protocol header can only be sent once right after opening the connection.
// So single connection must not be used for multiple requests, which can potentially
// come from different clients.
if !rt.DisableKeepAlives && h.ProxyProtocol != "" {
caddyCtx.Logger().Warn("disabling keepalives, they are incompatible with using PROXY protocol")
rt.DisableKeepAlives = true
}
if h.Compression != nil { if h.Compression != nil {
rt.DisableCompression = !*h.Compression rt.DisableCompression = !*h.Compression
} }
@@ -547,11 +545,11 @@ type TLSConfig struct {
// Certificate authority module which provides the certificate pool of trusted certificates // Certificate authority module which provides the certificate pool of trusted certificates
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"` CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead. // DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
// Optional list of base64-encoded DER-encoded CA certificates to trust. // Optional list of base64-encoded DER-encoded CA certificates to trust.
RootCAPool []string `json:"root_ca_pool,omitempty"` RootCAPool []string `json:"root_ca_pool,omitempty"`
// Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead. // DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
// List of PEM-encoded CA certificate files to add to the same trust // List of PEM-encoded CA certificate files to add to the same trust
// store as RootCAPool (or root_ca_pool in the JSON). // store as RootCAPool (or root_ca_pool in the JSON).
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"` RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
+33 -110
View File
@@ -17,8 +17,6 @@ package reverseproxy
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -110,6 +108,11 @@ type Handler struct {
// response is recognized as a streaming response, or if its // response is recognized as a streaming response, or if its
// content length is -1; for such responses, writes are flushed // content length is -1; for such responses, writes are flushed
// to the client immediately. // to the client immediately.
//
// Normally, a request will be canceled if the client disconnects
// before the response is received from the backend. If explicitly
// set to -1, client disconnection will be ignored and the request
// will be completed to help facilitate low-latency streaming.
FlushInterval caddy.Duration `json:"flush_interval,omitempty"` FlushInterval caddy.Duration `json:"flush_interval,omitempty"`
// A list of IP ranges (supports CIDR notation) from which // A list of IP ranges (supports CIDR notation) from which
@@ -243,19 +246,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
return fmt.Errorf("loading transport: %v", err) return fmt.Errorf("loading transport: %v", err)
} }
h.Transport = mod.(http.RoundTripper) h.Transport = mod.(http.RoundTripper)
// enable request buffering for fastcgi if not configured
// This is because most fastcgi servers are php-fpm that require the content length to be set to read the body, golang
// std has fastcgi implementation that doesn't need this value to process the body, but we can safely assume that's
// not used.
// http3 requests have a negative content length for GET and HEAD requests, if that header is not sent.
// see: https://github.com/caddyserver/caddy/issues/6678#issuecomment-2472224182
// Though it appears even if CONTENT_LENGTH is invalid, php-fpm can handle just fine if the body is empty (no Stdin records sent).
// php-fpm will hang if there is any data in the body though, https://github.com/caddyserver/caddy/issues/5420#issuecomment-2415943516
// TODO: better default buffering for fastcgi requests without content length, in theory a value of 1 should be enough, make it bigger anyway
if module, ok := h.Transport.(caddy.Module); ok && module.CaddyModule().ID.Name() == "fastcgi" && h.RequestBuffers == 0 {
h.RequestBuffers = 4096
}
} }
if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil { if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil {
mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw") mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw")
@@ -409,23 +399,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
return caddyhttp.Error(http.StatusInternalServerError, return caddyhttp.Error(http.StatusInternalServerError,
fmt.Errorf("preparing request for upstream round-trip: %v", err)) fmt.Errorf("preparing request for upstream round-trip: %v", err))
} }
// websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade
// TODO: once we can reliably detect backend support this, it can be removed for those backends
if r.ProtoMajor == 2 && r.Method == http.MethodConnect && r.Header.Get(":protocol") == "websocket" {
clonedReq.Header.Del(":protocol")
// keep the body for later use. http1.1 upgrade uses http.NoBody
caddyhttp.SetVar(clonedReq.Context(), "h2_websocket_body", clonedReq.Body)
clonedReq.Body = http.NoBody
clonedReq.Method = http.MethodGet
clonedReq.Header.Set("Upgrade", "websocket")
clonedReq.Header.Set("Connection", "Upgrade")
key := make([]byte, 16)
_, randErr := rand.Read(key)
if randErr != nil {
return randErr
}
clonedReq.Header["Sec-WebSocket-Key"] = []string{base64.StdEncoding.EncodeToString(key)}
}
// we will need the original headers and Host value if // we will need the original headers and Host value if
// header operations are configured; this is so that each // header operations are configured; this is so that each
@@ -494,17 +467,6 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
// get the updated list of upstreams // get the updated list of upstreams
upstreams := h.Upstreams upstreams := h.Upstreams
if h.DynamicUpstreams != nil { if h.DynamicUpstreams != nil {
if retries > 0 {
// after a failure (and thus during a retry), give dynamic upstream modules an opportunity
// to purge their relevant cache entries so we don't keep retrying bad upstreams
if cachingDynamicUpstreams, ok := h.DynamicUpstreams.(CachingUpstreamSource); ok {
if err := cachingDynamicUpstreams.ResetCache(r); err != nil {
if c := h.logger.Check(zapcore.ErrorLevel, "failed clearing dynamic upstream source's cache"); c != nil {
c.Write(zap.Error(err))
}
}
}
}
dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r) dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r)
if err != nil { if err != nil {
if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil { if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil {
@@ -534,7 +496,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
if proxyErr == nil { if proxyErr == nil {
proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream) proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream)
} }
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) { if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
return true, proxyErr return true, proxyErr
} }
return false, proxyErr return false, proxyErr
@@ -592,7 +554,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
// ding the health status of the upstream (an error can still // ding the health status of the upstream (an error can still
// occur after the roundtrip if, for example, a response handler // occur after the roundtrip if, for example, a response handler
// after the roundtrip returns an error) // after the roundtrip returns an error)
if succ, ok := proxyErr.(roundtripSucceededError); ok { if succ, ok := proxyErr.(roundtripSucceeded); ok {
return true, succ.error return true, succ.error
} }
@@ -600,7 +562,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
h.countFailure(upstream) h.countFailure(upstream)
// if we've tried long enough, break // if we've tried long enough, break
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) { if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r) {
return true, proxyErr return true, proxyErr
} }
@@ -663,8 +625,7 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
if h.RequestBuffers != 0 && req.Body != nil { if h.RequestBuffers != 0 && req.Body != nil {
var readBytes int64 var readBytes int64
req.Body, readBytes = h.bufferedBody(req.Body, h.RequestBuffers) req.Body, readBytes = h.bufferedBody(req.Body, h.RequestBuffers)
// set Content-Length when body is fully buffered if h.RequestBuffers == -1 {
if b, ok := req.Body.(bodyReadCloser); ok && b.body == nil {
req.ContentLength = readBytes req.ContentLength = readBytes
req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10)) req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10))
} }
@@ -846,44 +807,37 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials
// Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164 // Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164
var (
roundTripMutex sync.Mutex
roundTripDone bool
)
trace := &httptrace.ClientTrace{ trace := &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error { Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
roundTripMutex.Lock()
defer roundTripMutex.Unlock()
if roundTripDone {
// If RoundTrip has returned, don't try to further modify
// the ResponseWriter's header map.
return nil
}
h := rw.Header() h := rw.Header()
copyHeader(h, http.Header(header)) copyHeader(h, http.Header(header))
rw.WriteHeader(code) rw.WriteHeader(code)
// Clear headers coming from the backend // Clear headers coming from the backend
// (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses) // (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses)
clear(h) for k := range header {
delete(h, k)
}
return nil return nil
}, },
} }
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
// do the round-trip // if FlushInterval is explicitly configured to -1 (i.e. flush continuously to achieve
// low-latency streaming), don't let the transport cancel the request if the client
// disconnects: user probably wants us to finish sending the data to the upstream
// regardless, and we should expect client disconnection in low-latency streaming
// scenarios (see issue #4922)
if h.FlushInterval == -1 {
req = req.WithContext(context.WithoutCancel(req.Context()))
}
// do the round-trip; emit debug log with values we know are
// safe, or if there is no error, emit fuller log entry
start := time.Now() start := time.Now()
res, err := h.Transport.RoundTrip(req) res, err := h.Transport.RoundTrip(req)
duration := time.Since(start) duration := time.Since(start)
// record that the round trip is done for the 1xx response handler
roundTripMutex.Lock()
roundTripDone = true
roundTripMutex.Unlock()
// emit debug log with values we know are safe,
// or if there is no error, emit fuller log entry
logger := h.logger.With( logger := h.logger.With(
zap.String("upstream", di.Upstream.String()), zap.String("upstream", di.Upstream.String()),
zap.Duration("duration", duration), zap.Duration("duration", duration),
@@ -997,10 +951,10 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
res.Body.Close() res.Body.Close()
} }
// wrap any route error in roundtripSucceededError so caller knows that // wrap any route error in roundtripSucceeded so caller knows that
// the roundtrip was successful and to not retry // the roundtrip was successful and to not retry
if routeErr != nil { if routeErr != nil {
return roundtripSucceededError{routeErr} return roundtripSucceeded{routeErr}
} }
// we're done handling the response, and we don't want to // we're done handling the response, and we don't want to
@@ -1119,7 +1073,7 @@ func (h *Handler) finalizeResponse(
// If true is returned, it has already blocked long enough before // If true is returned, it has already blocked long enough before
// the next retry (i.e. no more sleeping is needed). If false is // the next retry (i.e. no more sleeping is needed). If false is
// returned, the handler should stop trying to proxy the request. // returned, the handler should stop trying to proxy the request.
func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request, logger *zap.Logger) bool { func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request) bool {
// no retries are configured // no retries are configured
if lb.TryDuration == 0 && lb.Retries == 0 { if lb.TryDuration == 0 && lb.Retries == 0 {
return false return false
@@ -1154,12 +1108,7 @@ func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int
return false return false
} }
match, err := lb.RetryMatch.AnyMatchWithError(req) if !lb.RetryMatch.AnyMatch(req) {
if err != nil {
logger.Error("error matching request for retry", zap.Error(err))
return false
}
if !match {
return false return false
} }
} }
@@ -1186,7 +1135,7 @@ func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int
// directRequest modifies only req.URL so that it points to the upstream // directRequest modifies only req.URL so that it points to the upstream
// in the given DialInfo. It must modify ONLY the request URL. // in the given DialInfo. It must modify ONLY the request URL.
func (h *Handler) directRequest(req *http.Request, di DialInfo) { func (Handler) directRequest(req *http.Request, di DialInfo) {
// we need a host, so set the upstream's host address // we need a host, so set the upstream's host address
reqHost := di.Address reqHost := di.Address
@@ -1197,13 +1146,6 @@ func (h *Handler) directRequest(req *http.Request, di DialInfo) {
reqHost = di.Host reqHost = di.Host
} }
// add client address to the host to let transport differentiate requests from different clients
if ht, ok := h.Transport.(*HTTPTransport); ok && ht.ProxyProtocol != "" {
if proxyProtocolInfo, ok := caddyhttp.GetVar(req.Context(), proxyProtocolInfoVarKey).(ProxyProtocolInfo); ok {
reqHost = proxyProtocolInfo.AddrPort.String() + "->" + reqHost
}
}
req.URL.Host = reqHost req.URL.Host = reqHost
} }
@@ -1247,14 +1189,13 @@ func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadC
buf := bufPool.Get().(*bytes.Buffer) buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() buf.Reset()
if limit > 0 { if limit > 0 {
var err error n, err := io.CopyN(buf, originalBody, limit)
written, err = io.CopyN(buf, originalBody, limit) if (err != nil && err != io.EOF) || n == limit {
if (err != nil && err != io.EOF) || written == limit {
return bodyReadCloser{ return bodyReadCloser{
Reader: io.MultiReader(buf, originalBody), Reader: io.MultiReader(buf, originalBody),
buf: buf, buf: buf,
body: originalBody, body: originalBody,
}, written }, n
} }
} else { } else {
written, _ = io.Copy(buf, originalBody) written, _ = io.Copy(buf, originalBody)
@@ -1446,28 +1387,10 @@ type Selector interface {
// may be called during each retry, multiple times per request, and as // may be called during each retry, multiple times per request, and as
// such, needs to be instantaneous. The returned slice will not be // such, needs to be instantaneous. The returned slice will not be
// modified. // modified.
//
// For upstream sources that cache results, implement the
// [CachingUpstreamSource] interface for optimal performance.
type UpstreamSource interface { type UpstreamSource interface {
GetUpstreams(*http.Request) ([]*Upstream, error) GetUpstreams(*http.Request) ([]*Upstream, error)
} }
// CachingUpstreamSource is an upstream source that caches its upstreams.
// The relevant cache entry can be cleared/reset for a given request during
// retries if a request fails. This can help ensure that failing backends
// are not retried.
//
// EXPERIMENTAL: Subject to change.
type CachingUpstreamSource interface {
UpstreamSource
// ResetCache clears any cache entry related to the given request.
// The next time GetUpstreams is called, it should have new upstream
// information for the given request.
ResetCache(*http.Request) error
}
// Hop-by-hop headers. These are removed when sent to the backend. // Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the // As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the // Connection header field. These are the headers defined by the
@@ -1503,9 +1426,9 @@ type TLSTransport interface {
EnableTLS(base *TLSConfig) error EnableTLS(base *TLSConfig) error
} }
// roundtripSucceededError is an error type that is returned if the // roundtripSucceeded is an error type that is returned if the
// roundtrip succeeded, but an error occurred after-the-fact. // roundtrip succeeded, but an error occurred after-the-fact.
type roundtripSucceededError struct{ error } type roundtripSucceeded struct{ error }
// bodyReadCloser is a reader that, upon closing, will return // bodyReadCloser is a reader that, upon closing, will return
// its buffer to the pool and close the underlying body reader. // its buffer to the pool and close the underlying body reader.
@@ -111,8 +111,8 @@ func (r *WeightedRoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser)
if err != nil { if err != nil {
return d.Errf("invalid weight value '%s': %v", weight, err) return d.Errf("invalid weight value '%s': %v", weight, err)
} }
if weightInt < 0 { if weightInt < 1 {
return d.Errf("invalid weight value '%s': weight should be non-negative", weight) return d.Errf("invalid weight value '%s': weight should be non-zero and positive", weight)
} }
r.Weights = append(r.Weights, weightInt) r.Weights = append(r.Weights, weightInt)
} }
@@ -136,15 +136,8 @@ func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *http.Request,
return pool[0] return pool[0]
} }
var index, totalWeight int var index, totalWeight int
var weights []int
for _, w := range r.Weights {
if w > 0 {
weights = append(weights, w)
}
}
currentWeight := int(atomic.AddUint32(&r.index, 1)) % r.totalWeight currentWeight := int(atomic.AddUint32(&r.index, 1)) % r.totalWeight
for i, weight := range weights { for i, weight := range r.Weights {
totalWeight += weight totalWeight += weight
if currentWeight < totalWeight { if currentWeight < totalWeight {
index = i index = i
@@ -152,9 +145,9 @@ func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *http.Request,
} }
} }
upstreams := make([]*Upstream, 0, len(weights)) upstreams := make([]*Upstream, 0, len(r.Weights))
for i, upstream := range pool { for _, upstream := range pool {
if !upstream.Available() || r.Weights[i] == 0 { if !upstream.Available() {
continue continue
} }
upstreams = append(upstreams, upstream) upstreams = append(upstreams, upstream)
@@ -131,58 +131,6 @@ func TestWeightedRoundRobinPolicy(t *testing.T) {
} }
} }
func TestWeightedRoundRobinPolicyWithZeroWeight(t *testing.T) {
pool := testPool()
wrrPolicy := WeightedRoundRobinSelection{
Weights: []int{0, 2, 1},
totalWeight: 3,
}
req, _ := http.NewRequest("GET", "/", nil)
h := wrrPolicy.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected first weighted round robin host to be second host in the pool.")
}
h = wrrPolicy.Select(pool, req, nil)
if h != pool[2] {
t.Error("Expected second weighted round robin host to be third host in the pool.")
}
h = wrrPolicy.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected third weighted round robin host to be second host in the pool.")
}
// mark second host as down
pool[1].setHealthy(false)
h = wrrPolicy.Select(pool, req, nil)
if h != pool[2] {
t.Error("Expect select next available host.")
}
h = wrrPolicy.Select(pool, req, nil)
if h != pool[2] {
t.Error("Expect select only available host.")
}
// mark second host as up
pool[1].setHealthy(true)
h = wrrPolicy.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expect select first host on availability.")
}
// test next select in full cycle
expected := []*Upstream{pool[1], pool[2], pool[1], pool[1], pool[2], pool[1]}
for i, want := range expected {
got := wrrPolicy.Select(pool, req, nil)
if want != got {
t.Errorf("Selection %d: got host[%s], want host[%s]", i+1, got, want)
}
}
}
func TestLeastConnPolicy(t *testing.T) { func TestLeastConnPolicy(t *testing.T) {
pool := testPool() pool := testPool()
lcPolicy := LeastConnSelection{} lcPolicy := LeastConnSelection{}
+14 -70
View File
@@ -19,7 +19,6 @@
package reverseproxy package reverseproxy
import ( import (
"bufio"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -34,29 +33,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
type h2ReadWriteCloser struct {
io.ReadCloser
http.ResponseWriter
}
func (rwc h2ReadWriteCloser) Write(p []byte) (n int, err error) {
n, err = rwc.ResponseWriter.Write(p)
if err != nil {
return 0, err
}
//nolint:bodyclose
err = http.NewResponseController(rwc.ResponseWriter).Flush()
if err != nil {
return 0, err
}
return n, nil
}
func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, rw http.ResponseWriter, req *http.Request, res *http.Response) { func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, rw http.ResponseWriter, req *http.Request, res *http.Response) {
reqUpType := upgradeType(req.Header) reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header) resUpType := upgradeType(res.Header)
@@ -89,58 +67,24 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
// like the rest of handler chain. // like the rest of handler chain.
copyHeader(rw.Header(), res.Header) copyHeader(rw.Header(), res.Header)
normalizeWebsocketHeaders(rw.Header()) normalizeWebsocketHeaders(rw.Header())
rw.WriteHeader(res.StatusCode)
var ( logger.Debug("upgrading connection")
conn io.ReadWriteCloser
brw *bufio.ReadWriter
)
// websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade
// TODO: once we can reliably detect backend support this, it can be removed for those backends
if body, ok := caddyhttp.GetVar(req.Context(), "h2_websocket_body").(io.ReadCloser); ok {
req.Body = body
rw.Header().Del("Upgrade")
rw.Header().Del("Connection")
delete(rw.Header(), "Sec-WebSocket-Accept")
rw.WriteHeader(http.StatusOK)
if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil { //nolint:bodyclose
c.Write(zap.Int("http_version", 2)) conn, brw, hijackErr := http.NewResponseController(rw).Hijack()
if errors.Is(hijackErr, http.ErrNotSupported) {
if c := logger.Check(zapcore.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil {
c.Write(zap.String("type", fmt.Sprintf("%T", rw)))
} }
return
}
//nolint:bodyclose if hijackErr != nil {
flushErr := http.NewResponseController(rw).Flush() if c := logger.Check(zapcore.ErrorLevel, "hijack failed on protocol switch"); c != nil {
if flushErr != nil { c.Write(zap.Error(hijackErr))
if c := h.logger.Check(zap.ErrorLevel, "failed to flush http2 websocket response"); c != nil {
c.Write(zap.Error(flushErr))
}
return
}
conn = h2ReadWriteCloser{req.Body, rw}
// bufio is not needed, use minimal buffer
brw = bufio.NewReadWriter(bufio.NewReaderSize(conn, 1), bufio.NewWriterSize(conn, 1))
} else {
rw.WriteHeader(res.StatusCode)
if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil {
c.Write(zap.Int("http_version", req.ProtoMajor))
}
var hijackErr error
//nolint:bodyclose
conn, brw, hijackErr = http.NewResponseController(rw).Hijack()
if errors.Is(hijackErr, http.ErrNotSupported) {
if c := h.logger.Check(zap.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil {
c.Write(zap.String("type", fmt.Sprintf("%T", rw)))
}
return
}
if hijackErr != nil {
if c := h.logger.Check(zap.ErrorLevel, "hijack failed on protocol switch"); c != nil {
c.Write(zap.Error(hijackErr))
}
return
} }
return
} }
// adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5 // adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5
@@ -159,7 +103,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
start := time.Now() start := time.Now()
defer func() { defer func() {
conn.Close() conn.Close()
if c := logger.Check(zapcore.DebugLevel, "connection closed"); c != nil { if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil {
c.Write(zap.Duration("duration", time.Since(start))) c.Write(zap.Duration("duration", time.Since(start)))
} }
}() }()
+4 -25
View File
@@ -70,11 +70,6 @@ type SRVUpstreams struct {
// A negative value disables this. // A negative value disables this.
FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"` FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
// Specific network to dial when connecting to the upstream(s)
// provided by SRV records upstream. See Go's net package for
// accepted values. For example, to restrict to IPv4, use "tcp4".
DialNetwork string `json:"dial_network,omitempty"`
resolver *net.Resolver resolver *net.Resolver
logger *zap.Logger logger *zap.Logger
@@ -119,18 +114,6 @@ func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
return nil return nil
} }
func (su *SRVUpstreams) ResetCache(r *http.Request) error {
srvsMu.Lock()
if r == nil {
srvs = make(map[string]srvLookup)
} else {
suAddr, _, _, _ := su.expandedAddr(r)
delete(srvs, suAddr)
}
srvsMu.Unlock()
return nil
}
func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
suAddr, service, proto, name := su.expandedAddr(r) suAddr, service, proto, name := su.expandedAddr(r)
@@ -194,9 +177,6 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
) )
} }
addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port))) addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port)))
if su.DialNetwork != "" {
addr = su.DialNetwork + "/" + addr
}
upstreams[i] = Upstream{Dial: addr} upstreams[i] = Upstream{Dial: addr}
} }
@@ -566,9 +546,8 @@ var (
// Interface guards // Interface guards
var ( var (
_ caddy.Provisioner = (*SRVUpstreams)(nil) _ caddy.Provisioner = (*SRVUpstreams)(nil)
_ UpstreamSource = (*SRVUpstreams)(nil) _ UpstreamSource = (*SRVUpstreams)(nil)
_ CachingUpstreamSource = (*SRVUpstreams)(nil) _ caddy.Provisioner = (*AUpstreams)(nil)
_ caddy.Provisioner = (*AUpstreams)(nil) _ UpstreamSource = (*AUpstreams)(nil)
_ UpstreamSource = (*AUpstreams)(nil)
) )
+3
View File
@@ -110,6 +110,9 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
return nil, h.ArgErr() return nil, h.ArgErr()
} }
rewr.StripPathPrefix = args[1] rewr.StripPathPrefix = args[1]
if !strings.HasPrefix(rewr.StripPathPrefix, "/") {
rewr.StripPathPrefix = "/" + rewr.StripPathPrefix
}
case "strip_suffix": case "strip_suffix":
if len(args) != 2 { if len(args) != 2 {
-3
View File
@@ -259,9 +259,6 @@ func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
// strip path prefix or suffix // strip path prefix or suffix
if rewr.StripPathPrefix != "" { if rewr.StripPathPrefix != "" {
prefix := repl.ReplaceAll(rewr.StripPathPrefix, "") prefix := repl.ReplaceAll(rewr.StripPathPrefix, "")
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
mergeSlashes := !strings.Contains(prefix, "//") mergeSlashes := !strings.Contains(prefix, "//")
changePath(r, func(escapedPath string) string { changePath(r, func(escapedPath string) string {
escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes) escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes)
@@ -235,11 +235,6 @@ func TestRewrite(t *testing.T) {
input: newRequest(t, "GET", "/prefix/foo/bar"), input: newRequest(t, "GET", "/prefix/foo/bar"),
expect: newRequest(t, "GET", "/foo/bar"), expect: newRequest(t, "GET", "/foo/bar"),
}, },
{
rule: Rewrite{StripPathPrefix: "prefix"},
input: newRequest(t, "GET", "/prefix/foo/bar"),
expect: newRequest(t, "GET", "/foo/bar"),
},
{ {
rule: Rewrite{StripPathPrefix: "/prefix"}, rule: Rewrite{StripPathPrefix: "/prefix"},
input: newRequest(t, "GET", "/prefix"), input: newRequest(t, "GET", "/prefix"),
+19 -78
View File
@@ -254,13 +254,18 @@ func wrapRoute(route Route) Middleware {
nextCopy := next nextCopy := next
// route must match at least one of the matcher sets // route must match at least one of the matcher sets
matches, err := route.MatcherSets.AnyMatchWithError(req) if !route.MatcherSets.AnyMatch(req) {
if err != nil {
// allow matchers the opportunity to short circuit // allow matchers the opportunity to short circuit
// the request and trigger the error handling chain // the request and trigger the error handling chain
return err err, ok := GetVar(req.Context(), MatcherErrorVarKey).(error)
} if ok {
if !matches { // clear out the error from context, otherwise
// it will cascade to the error routes (#4916)
SetVar(req.Context(), MatcherErrorVarKey, nil)
// return the matcher's error
return err
}
// call the next handler, and skip this one, // call the next handler, and skip this one,
// since the matcher didn't match // since the matcher didn't match
return nextCopy.ServeHTTP(rw, req) return nextCopy.ServeHTTP(rw, req)
@@ -336,58 +341,19 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) M
// MatcherSet is a set of matchers which // MatcherSet is a set of matchers which
// must all match in order for the request // must all match in order for the request
// to be matched successfully. // to be matched successfully.
type MatcherSet []any type MatcherSet []RequestMatcher
// Match returns true if the request matches all // Match returns true if the request matches all
// matchers in mset or if there are no matchers. // matchers in mset or if there are no matchers.
func (mset MatcherSet) Match(r *http.Request) bool { func (mset MatcherSet) Match(r *http.Request) bool {
for _, m := range mset { for _, m := range mset {
if me, ok := m.(RequestMatcherWithError); ok { if !m.Match(r) {
match, _ := me.MatchWithError(r) return false
if !match {
return false
}
continue
} }
if me, ok := m.(RequestMatcher); ok {
if !me.Match(r) {
return false
}
continue
}
return false
} }
return true return true
} }
// MatchWithError returns true if r matches m.
func (mset MatcherSet) MatchWithError(r *http.Request) (bool, error) {
for _, m := range mset {
if me, ok := m.(RequestMatcherWithError); ok {
match, err := me.MatchWithError(r)
if err != nil || !match {
return match, err
}
continue
}
if me, ok := m.(RequestMatcher); ok {
if !me.Match(r) {
// for backwards compatibility
err, ok := GetVar(r.Context(), MatcherErrorVarKey).(error)
if ok {
// clear out the error from context since we've consumed it
SetVar(r.Context(), MatcherErrorVarKey, nil)
return false, err
}
return false, nil
}
continue
}
return false, fmt.Errorf("matcher is not a RequestMatcher or RequestMatcherWithError: %#v", m)
}
return true, nil
}
// RawMatcherSets is a group of matcher sets // RawMatcherSets is a group of matcher sets
// in their raw, JSON form. // in their raw, JSON form.
type RawMatcherSets []caddy.ModuleMap type RawMatcherSets []caddy.ModuleMap
@@ -400,50 +366,25 @@ type MatcherSets []MatcherSet
// AnyMatch returns true if req matches any of the // AnyMatch returns true if req matches any of the
// matcher sets in ms or if there are no matchers, // matcher sets in ms or if there are no matchers,
// in which case the request always matches. // in which case the request always matches.
//
// Deprecated: Use AnyMatchWithError instead.
func (ms MatcherSets) AnyMatch(req *http.Request) bool { func (ms MatcherSets) AnyMatch(req *http.Request) bool {
for _, m := range ms { for _, m := range ms {
match, err := m.MatchWithError(req) if m.Match(req) {
if err != nil { return true
SetVar(req.Context(), MatcherErrorVarKey, err)
return false
}
if match {
return match
} }
} }
return len(ms) == 0 return len(ms) == 0
} }
// AnyMatchWithError returns true if req matches any of the
// matcher sets in ms or if there are no matchers, in which
// case the request always matches. If any matcher returns
// an error, we cut short and return the error.
func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, error) {
for _, m := range ms {
match, err := m.MatchWithError(req)
if err != nil || match {
return match, err
}
}
return len(ms) == 0, nil
}
// FromInterface fills ms from an 'any' value obtained from LoadModule. // FromInterface fills ms from an 'any' value obtained from LoadModule.
func (ms *MatcherSets) FromInterface(matcherSets any) error { func (ms *MatcherSets) FromInterface(matcherSets any) error {
for _, matcherSetIfaces := range matcherSets.([]map[string]any) { for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
var matcherSet MatcherSet var matcherSet MatcherSet
for _, matcher := range matcherSetIfaces { for _, matcher := range matcherSetIfaces {
if m, ok := matcher.(RequestMatcherWithError); ok { reqMatcher, ok := matcher.(RequestMatcher)
matcherSet = append(matcherSet, m) if !ok {
continue return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher)
} }
if m, ok := matcher.(RequestMatcher); ok { matcherSet = append(matcherSet, reqMatcher)
matcherSet = append(matcherSet, m)
continue
}
return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher)
} }
*ms = append(*ms, matcherSet) *ms = append(*ms, matcherSet)
} }

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