mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-13 22:36:27 +03:00
Merge branch 'master' into filesystem-changes
This commit is contained in:
commit
cf637dbaa6
28 changed files with 664 additions and 76 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -99,7 +99,7 @@ jobs:
|
|||
env:
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
go build -tags nobdger -trimpath -ldflags="-w -s" -v
|
||||
go build -tags nobadger -trimpath -ldflags="-w -s" -v
|
||||
|
||||
- name: Smoke test Caddy
|
||||
working-directory: ./cmd/caddy
|
||||
|
@ -150,6 +150,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
- name: Run Tests
|
||||
run: |
|
||||
set +e
|
||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||
|
||||
# short sha is enough?
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
||||
ignoretests: true
|
||||
exclude-functions:
|
||||
- fmt.*
|
||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||
gci:
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
|
@ -130,13 +132,14 @@ linters:
|
|||
run:
|
||||
# default concurrency is a available CPU number.
|
||||
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
||||
deadline: 5m
|
||||
timeout: 5m
|
||||
issues-exit-code: 1
|
||||
tests: false
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
format: 'colored-line-number'
|
||||
formats:
|
||||
- format: 'colored-line-number'
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
|
||||
|
@ -166,3 +169,6 @@ issues:
|
|||
- path: modules/logging/filters.go
|
||||
linters:
|
||||
- dupl
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
:80
|
||||
|
||||
file_server browse {
|
||||
sort size desc
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"browse": {},
|
||||
"handler": "file_server",
|
||||
"hide": [
|
||||
"./Caddyfile"
|
||||
],
|
||||
"sort": [
|
||||
"size",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
:8884
|
||||
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
health_uri /health
|
||||
health_request_body "test body"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"active": {
|
||||
"body": "test body",
|
||||
"uri": "/health"
|
||||
}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt
vendored
Normal file
2
caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
foo
|
||||
|
1
caddytest/integration/testdata/foo_with_trailing_newline.txt
vendored
Normal file
1
caddytest/integration/testdata/foo_with_trailing_newline.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo
|
|
@ -74,6 +74,10 @@ func cmdStart(fl Flags) (int, error) {
|
|||
// sure by giving it some random bytes and having it echo
|
||||
// them back to us)
|
||||
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
|
||||
// we should be able to run caddy in relative paths
|
||||
if errors.Is(cmd.Err, exec.ErrDot) {
|
||||
cmd.Err = nil
|
||||
}
|
||||
if configFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--config", configFlag)
|
||||
}
|
||||
|
|
18
go.mod
18
go.mod
|
@ -19,7 +19,7 @@ require (
|
|||
github.com/klauspost/cpuid/v2 v2.2.7
|
||||
github.com/mholt/acmez/v2 v2.0.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.44.0
|
||||
github.com/quic-go/quic-go v0.46.0
|
||||
github.com/smallstep/certificates v0.26.1
|
||||
github.com/smallstep/nosql v0.6.1
|
||||
github.com/smallstep/truststore v0.13.0
|
||||
|
@ -37,11 +37,11 @@ require (
|
|||
go.uber.org/automaxprocs v1.5.3
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap/exp v0.2.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/term v0.23.0
|
||||
golang.org/x/time v0.5.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -123,7 +123,7 @@ require (
|
|||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
|
@ -147,9 +147,9 @@ require (
|
|||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
golang.org/x/sys v0.23.0
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
|
|
36
go.sum
36
go.sum
|
@ -320,8 +320,8 @@ github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P
|
|||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E=
|
||||
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -339,8 +339,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
|||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
|
||||
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
|
||||
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
|
||||
github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
|
@ -510,8 +510,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
|
@ -532,15 +532,15 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -567,8 +567,8 @@ 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.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -576,8 +576,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||
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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -588,8 +588,8 @@ golang.org/x/text v0.4.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -603,8 +603,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
14
internal/ranges.go
Normal file
14
internal/ranges.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package internal
|
||||
|
||||
// PrivateRangesCIDR returns a list of private CIDR range
|
||||
// strings, which can be used as a configuration shortcut.
|
||||
func PrivateRangesCIDR() []string {
|
||||
return []string{
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1",
|
||||
}
|
||||
}
|
|
@ -266,6 +266,14 @@ func (rw *responseWriter) FlushError() error {
|
|||
// to rw.Write (see bug in #4314)
|
||||
return nil
|
||||
}
|
||||
// also flushes the encoder, if any
|
||||
// see: https://github.com/jjiang-stripe/caddy-slow-gzip
|
||||
if rw.w != nil {
|
||||
err := rw.w.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
//nolint:bodyclose
|
||||
return http.NewResponseController(rw.ResponseWriter).Flush()
|
||||
}
|
||||
|
@ -475,6 +483,7 @@ type encodingPreference struct {
|
|||
type Encoder interface {
|
||||
io.WriteCloser
|
||||
Reset(io.Writer)
|
||||
Flush() error // encoder by default buffers data to maximize compressing rate
|
||||
}
|
||||
|
||||
// Encoding is a type which can create encoders of its kind
|
||||
|
|
|
@ -206,11 +206,34 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs
|
|||
// browseApplyQueryParams applies query parameters to the listing.
|
||||
// It mutates the listing and may set cookies.
|
||||
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
|
||||
var orderParam, sortParam string
|
||||
|
||||
// The configs in Caddyfile have lower priority than Query params,
|
||||
// so put it at first.
|
||||
for idx, item := range fsrv.SortOptions {
|
||||
// Only `sort` & `order`, 2 params are allowed
|
||||
if idx >= 2 {
|
||||
break
|
||||
}
|
||||
switch item {
|
||||
case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
|
||||
sortParam = item
|
||||
case sortOrderAsc, sortOrderDesc:
|
||||
orderParam = item
|
||||
}
|
||||
}
|
||||
|
||||
layoutParam := r.URL.Query().Get("layout")
|
||||
sortParam := r.URL.Query().Get("sort")
|
||||
orderParam := r.URL.Query().Get("order")
|
||||
limitParam := r.URL.Query().Get("limit")
|
||||
offsetParam := r.URL.Query().Get("offset")
|
||||
sortParamTmp := r.URL.Query().Get("sort")
|
||||
if sortParamTmp != "" {
|
||||
sortParam = sortParamTmp
|
||||
}
|
||||
orderParamTmp := r.URL.Query().Get("order")
|
||||
if orderParamTmp != "" {
|
||||
orderParam = orderParamTmp
|
||||
}
|
||||
|
||||
switch layoutParam {
|
||||
case "list", "grid", "":
|
||||
|
@ -233,11 +256,11 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
|
|||
// then figure out the order
|
||||
switch orderParam {
|
||||
case "":
|
||||
orderParam = "asc"
|
||||
orderParam = sortOrderAsc
|
||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||
orderParam = orderCookie.Value
|
||||
}
|
||||
case "asc", "desc":
|
||||
case sortOrderAsc, sortOrderDesc:
|
||||
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
|
||||
}
|
||||
|
||||
|
|
|
@ -373,4 +373,7 @@ const (
|
|||
sortByNameDirFirst = "namedirfirst"
|
||||
sortBySize = "size"
|
||||
sortByTime = "time"
|
||||
|
||||
sortOrderAsc = "asc"
|
||||
sortOrderDesc = "desc"
|
||||
)
|
||||
|
|
|
@ -171,6 +171,17 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
}
|
||||
fsrv.EtagFileExtensions = etagFileExtensions
|
||||
|
||||
case "sort":
|
||||
for d.NextArg() {
|
||||
dVal := d.Val()
|
||||
switch dVal {
|
||||
case sortByName, sortBySize, sortByTime, sortOrderAsc, sortOrderDesc:
|
||||
fsrv.SortOptions = append(fsrv.SortOptions, dVal)
|
||||
default:
|
||||
return d.Errf("unknown sort option '%s'", dVal)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||
}
|
||||
|
|
|
@ -153,6 +153,16 @@ type FileServer struct {
|
|||
// a 404 error. By default, this is false (disabled).
|
||||
PassThru bool `json:"pass_thru,omitempty"`
|
||||
|
||||
// Override the default sort.
|
||||
// It includes the following options:
|
||||
// - sort_by: name(default), namedirfirst, size, time
|
||||
// - order: asc(default), desc
|
||||
// eg.:
|
||||
// - `sort time desc` will sort by time in descending 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).
|
||||
SortOptions []string `json:"sort,omitempty"`
|
||||
|
||||
// Selection of encoders to use to check for precompressed files.
|
||||
PrecompressedRaw caddy.ModuleMap `json:"precompressed,omitempty" caddy:"namespace=http.precompressed"`
|
||||
|
||||
|
@ -236,6 +246,22 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
|
|||
fsrv.precompressors[ae] = p
|
||||
}
|
||||
|
||||
// check sort options
|
||||
for idx, sortOption := range fsrv.SortOptions {
|
||||
switch idx {
|
||||
case 0:
|
||||
if sortOption != sortByName && sortOption != sortByNameDirFirst && sortOption != sortBySize && sortOption != sortByTime {
|
||||
return fmt.Errorf("the first option must be one of the following: %s, %s, %s, %s, but got %s", sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOption)
|
||||
}
|
||||
case 1:
|
||||
if sortOption != sortOrderAsc && sortOption != sortOrderDesc {
|
||||
return fmt.Errorf("the second option must be one of the following: %s, %s, but got %s", sortOrderAsc, sortOrderDesc, sortOption)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("only max 2 sort options are allowed, but got %d", idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
// MatchRemoteIP matches requests by the remote IP address,
|
||||
|
@ -79,7 +80,7 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
|
||||
}
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
|
@ -173,7 +174,7 @@ func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
for d.Next() {
|
||||
for d.NextArg() {
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
|
@ -250,7 +251,9 @@ func (m MatchClientIP) Match(r *http.Request) bool {
|
|||
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
|
||||
cidrs := []*netip.Prefix{}
|
||||
zones := []string{}
|
||||
repl := caddy.NewReplacer()
|
||||
for _, str := range ranges {
|
||||
str = repl.ReplaceAll(str, "")
|
||||
// Exclude the zone_id from the IP
|
||||
if strings.Contains(str, "%") {
|
||||
split := strings.Split(str, "%")
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -92,7 +93,7 @@ func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
}
|
||||
for d.NextArg() {
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
|
@ -121,22 +122,16 @@ func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) {
|
|||
return prefix, nil
|
||||
}
|
||||
|
||||
// PrivateRangesCIDR returns a list of private CIDR range
|
||||
// strings, which can be used as a configuration shortcut.
|
||||
func PrivateRangesCIDR() []string {
|
||||
return []string{
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1",
|
||||
}
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*StaticIPRange)(nil)
|
||||
_ caddyfile.Unmarshaler = (*StaticIPRange)(nil)
|
||||
_ IPRangeSource = (*StaticIPRange)(nil)
|
||||
)
|
||||
|
||||
// PrivateRangesCIDR returns a list of private CIDR range
|
||||
// strings, which can be used as a configuration shortcut.
|
||||
// Note: this function is used at least by mholt/caddy-l4.
|
||||
func PrivateRangesCIDR() []string {
|
||||
return internal.PrivateRangesCIDR()
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ type ListenerWrapper struct {
|
|||
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
|
||||
FallbackPolicy Policy `json:"fallback_policy,omitempty"`
|
||||
|
||||
policy goproxy.PolicyFunc
|
||||
policy goproxy.ConnPolicyFunc
|
||||
}
|
||||
|
||||
// Provision sets up the listener wrapper.
|
||||
|
@ -69,13 +69,14 @@ func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
|
|||
}
|
||||
pp.deny = append(pp.deny, ipnet)
|
||||
}
|
||||
pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
|
||||
|
||||
pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) {
|
||||
// trust unix sockets
|
||||
if network := upstream.Network(); caddy.IsUnixNetwork(network) {
|
||||
if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) {
|
||||
return goproxy.USE, nil
|
||||
}
|
||||
ret := pp.FallbackPolicy
|
||||
host, _, err := net.SplitHostPort(upstream.String())
|
||||
host, _, err := net.SplitHostPort(options.Upstream.String())
|
||||
if err != nil {
|
||||
return goproxy.REJECT, err
|
||||
}
|
||||
|
@ -106,6 +107,6 @@ func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
|
|||
Listener: l,
|
||||
ReadHeaderTimeout: time.Duration(pp.Timeout),
|
||||
}
|
||||
pl.Policy = pp.policy
|
||||
pl.ConnPolicy = pp.policy
|
||||
return pl
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
|
@ -68,19 +69,20 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
|||
// lb_retry_match <request-matcher>
|
||||
//
|
||||
// # active health checking
|
||||
// health_uri <uri>
|
||||
// health_port <port>
|
||||
// health_interval <interval>
|
||||
// health_passes <num>
|
||||
// health_fails <num>
|
||||
// health_timeout <duration>
|
||||
// health_status <status>
|
||||
// health_body <regexp>
|
||||
// health_uri <uri>
|
||||
// health_port <port>
|
||||
// health_interval <interval>
|
||||
// health_passes <num>
|
||||
// health_fails <num>
|
||||
// health_timeout <duration>
|
||||
// health_status <status>
|
||||
// health_body <regexp>
|
||||
// health_method <value>
|
||||
// health_request_body <value>
|
||||
// health_follow_redirects
|
||||
// health_headers {
|
||||
// <field> [<values...>]
|
||||
// }
|
||||
// health_method <value>
|
||||
//
|
||||
// # passive health checking
|
||||
// fail_duration <duration>
|
||||
|
@ -424,6 +426,18 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
}
|
||||
h.HealthChecks.Active.Method = d.Val()
|
||||
|
||||
case "health_request_body":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if h.HealthChecks == nil {
|
||||
h.HealthChecks = new(HealthChecks)
|
||||
}
|
||||
if h.HealthChecks.Active == nil {
|
||||
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||
}
|
||||
h.HealthChecks.Active.Body = d.Val()
|
||||
|
||||
case "health_interval":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
|
@ -688,7 +702,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
case "trusted_proxies":
|
||||
for d.NextArg() {
|
||||
if d.Val() == "private_ranges" {
|
||||
h.TrustedProxies = append(h.TrustedProxies, caddyhttp.PrivateRangesCIDR()...)
|
||||
h.TrustedProxies = append(h.TrustedProxies, internal.PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
h.TrustedProxies = append(h.TrustedProxies, d.Val())
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"regexp"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -93,6 +94,9 @@ type ActiveHealthChecks struct {
|
|||
// The HTTP method to use for health checks (default "GET").
|
||||
Method string `json:"method,omitempty"`
|
||||
|
||||
// The body to send with the health check request.
|
||||
Body string `json:"body,omitempty"`
|
||||
|
||||
// Whether to follow HTTP redirects in response to active health checks (default off).
|
||||
FollowRedirects bool `json:"follow_redirects,omitempty"`
|
||||
|
||||
|
@ -396,6 +400,16 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
|
|||
u.Path = h.HealthChecks.Active.Path
|
||||
}
|
||||
|
||||
// replacer used for both body and headers. Only globals (env vars, system info, etc.) are available
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
// if body is provided, create a reader for it, otherwise nil
|
||||
var requestBody io.Reader
|
||||
if h.HealthChecks.Active.Body != "" {
|
||||
// set body, using replacer
|
||||
requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, ""))
|
||||
}
|
||||
|
||||
// attach dialing information to this request, as well as context values that
|
||||
// may be expected by handlers of this request
|
||||
ctx := h.ctx.Context
|
||||
|
@ -403,15 +417,14 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
|
|||
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
|
||||
dialInfoVarKey: dialInfo,
|
||||
})
|
||||
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), nil)
|
||||
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making request: %v", err)
|
||||
}
|
||||
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// set headers, using a replacer with only globals (env vars, system info, etc.)
|
||||
repl := caddy.NewReplacer()
|
||||
// set headers, using replacer
|
||||
repl.Set("http.reverse_proxy.active.target_upstream", networkAddr)
|
||||
for key, vals := range h.HealthChecks.Active.Headers {
|
||||
key = repl.ReplaceAll(key, "")
|
||||
|
|
|
@ -446,6 +446,9 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
||||
// HTTP without TLS, use the alternate H2C-capable transport instead
|
||||
if req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||
// There is no dedicated DisableKeepAlives field in *http2.Transport.
|
||||
// This is an alternative way to disable keep-alive.
|
||||
req.Close = h.Transport.DisableKeepAlives
|
||||
return h.h2cTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
|
|
|
@ -979,7 +979,7 @@ func (h *Handler) finalizeResponse(
|
|||
// we'll just log the error and abort the stream here and panic just as
|
||||
// the standard lib's proxy to propagate the stream error.
|
||||
// see issue https://github.com/caddyserver/caddy/issues/5951
|
||||
logger.Error("aborting with incomplete response", zap.Error(err))
|
||||
logger.Warn("aborting with incomplete response", zap.Error(err))
|
||||
// no extra logging from stdlib
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
|
|
|
@ -602,6 +602,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
|
|||
QUICConfig: &quic.Config{
|
||||
Versions: []quic.Version{quic.Version1, quic.Version2},
|
||||
},
|
||||
IdleTimeout: time.Duration(s.IdleTimeout),
|
||||
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
||||
return context.WithValue(ctx, quicConnCtxKey, c)
|
||||
},
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
// CustomCertSelectionPolicy represents a policy for selecting the certificate
|
||||
|
@ -122,6 +124,79 @@ nextChoice:
|
|||
return certmagic.DefaultCertificateSelector(hello, viable)
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// cert_selection {
|
||||
// all_tags <values...>
|
||||
// any_tag <values...>
|
||||
// public_key_algorithm <dsa|ecdsa|rsa>
|
||||
// serial_number <big_integers...>
|
||||
// subject_organization <values...>
|
||||
// }
|
||||
func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
_, wrapper := d.Next(), d.Val() // consume wrapper name
|
||||
|
||||
// No same-line options are supported
|
||||
if d.CountRemainingArgs() > 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
var hasPublicKeyAlgorithm bool
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
optionName := d.Val()
|
||||
switch optionName {
|
||||
case "all_tags":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
p.AllTags = append(p.AllTags, d.RemainingArgs()...)
|
||||
case "any_tag":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
|
||||
case "public_key_algorithm":
|
||||
if hasPublicKeyAlgorithm {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
if d.CountRemainingArgs() != 1 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
d.NextArg()
|
||||
if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
|
||||
return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
|
||||
}
|
||||
hasPublicKeyAlgorithm = true
|
||||
case "serial_number":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
for d.NextArg() {
|
||||
val, bi := d.Val(), bigInt{}
|
||||
_, ok := bi.SetString(val, 10)
|
||||
if !ok {
|
||||
return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
|
||||
}
|
||||
p.SerialNumber = append(p.SerialNumber, bi)
|
||||
}
|
||||
case "subject_organization":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
|
||||
default:
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
// No nested blocks are supported
|
||||
if d.NextBlock(nesting + 1) {
|
||||
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bigInt is a big.Int type that interops with JSON encodings as a string.
|
||||
type bigInt struct{ big.Int }
|
||||
|
||||
|
@ -144,3 +219,6 @@ func (bi *bigInt) UnmarshalJSON(p []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interface guard
|
||||
var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil)
|
||||
|
|
|
@ -363,6 +363,136 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
|
|||
p.InsecureSecretsLog == ""
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// connection_policy {
|
||||
// alpn <values...>
|
||||
// cert_selection {
|
||||
// ...
|
||||
// }
|
||||
// ciphers <cipher_suites...>
|
||||
// client_auth {
|
||||
// ...
|
||||
// }
|
||||
// curves <curves...>
|
||||
// default_sni <server_name>
|
||||
// match {
|
||||
// ...
|
||||
// }
|
||||
// protocols <min> [<max>]
|
||||
// # EXPERIMENTAL:
|
||||
// drop
|
||||
// fallback_sni <server_name>
|
||||
// insecure_secrets_log <log_file>
|
||||
// }
|
||||
func (cp *ConnectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
_, wrapper := d.Next(), d.Val()
|
||||
|
||||
// No same-line options are supported
|
||||
if d.CountRemainingArgs() > 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
var hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop,
|
||||
hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
optionName := d.Val()
|
||||
switch optionName {
|
||||
case "alpn":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
cp.ALPN = append(cp.ALPN, d.RemainingArgs()...)
|
||||
case "cert_selection":
|
||||
if hasCertSelection {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
p := &CustomCertSelectionPolicy{}
|
||||
if err := p.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
|
||||
return err
|
||||
}
|
||||
cp.CertSelection, hasCertSelection = p, true
|
||||
case "client_auth":
|
||||
if hasClientAuth {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
ca := &ClientAuthentication{}
|
||||
if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
|
||||
return err
|
||||
}
|
||||
cp.ClientAuthentication, hasClientAuth = ca, true
|
||||
case "ciphers":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...)
|
||||
case "curves":
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
cp.Curves = append(cp.Curves, d.RemainingArgs()...)
|
||||
case "default_sni":
|
||||
if hasDefaultSNI {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
if d.CountRemainingArgs() != 1 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
_, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true
|
||||
case "drop": // EXPERIMENTAL
|
||||
if hasDrop {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
cp.Drop, hasDrop = true, true
|
||||
case "fallback_sni": // EXPERIMENTAL
|
||||
if hasFallbackSNI {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
if d.CountRemainingArgs() != 1 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
_, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true
|
||||
case "insecure_secrets_log": // EXPERIMENTAL
|
||||
if hasInsecureSecretsLog {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
if d.CountRemainingArgs() != 1 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
_, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true
|
||||
case "match":
|
||||
if hasMatch {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp.MatchersRaw, hasMatch = matcherSet, true
|
||||
case "protocols":
|
||||
if hasProtocols {
|
||||
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||
}
|
||||
if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
_, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true
|
||||
if d.NextArg() {
|
||||
cp.ProtocolMax = d.Val()
|
||||
}
|
||||
default:
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
// No nested blocks are supported
|
||||
if d.NextBlock(nesting + 1) {
|
||||
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientAuthentication configures TLS client auth.
|
||||
type ClientAuthentication struct {
|
||||
// Certificate authority module which provides the certificate pool of trusted certificates
|
||||
|
@ -819,4 +949,46 @@ func (d destructableWriter) Destruct() error { return d.Close() }
|
|||
|
||||
var secretsLogPool = caddy.NewUsagePool()
|
||||
|
||||
var _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
|
||||
_ caddyfile.Unmarshaler = (*ConnectionPolicy)(nil)
|
||||
)
|
||||
|
||||
// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
|
||||
// matcher set, and returns its raw module map value.
|
||||
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
|
||||
matcherMap := make(map[string]ConnectionMatcher)
|
||||
|
||||
tokensByMatcherName := make(map[string][]caddyfile.Token)
|
||||
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
|
||||
matcherName := d.Val()
|
||||
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
|
||||
}
|
||||
|
||||
for matcherName, tokens := range tokensByMatcherName {
|
||||
dd := caddyfile.NewDispenser(tokens)
|
||||
dd.Next() // consume wrapper name
|
||||
|
||||
unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cm, ok := unm.(ConnectionMatcher)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName)
|
||||
}
|
||||
matcherMap[matcherName] = cm
|
||||
}
|
||||
|
||||
matcherSet := make(caddy.ModuleMap)
|
||||
for name, matcher := range matcherMap {
|
||||
jsonBytes, err := json.Marshal(matcher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
|
||||
}
|
||||
matcherSet[name] = jsonBytes
|
||||
}
|
||||
|
||||
return matcherSet, nil
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -48,14 +50,47 @@ func (MatchServerName) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Match matches hello based on SNI.
|
||||
func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool {
|
||||
repl := caddy.NewReplacer()
|
||||
// caddytls.TestServerNameMatcher calls this function without any context
|
||||
if ctx := hello.Context(); ctx != nil {
|
||||
// In some situations the existing context may have no replacer
|
||||
if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil {
|
||||
repl = replAny.(*caddy.Replacer)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range m {
|
||||
if certmagic.MatchWildcard(hello.ServerName, name) {
|
||||
rs := repl.ReplaceAll(name, "")
|
||||
if certmagic.MatchWildcard(hello.ServerName, rs) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the MatchServerName from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// sni <domains...>
|
||||
func (m *MatchServerName) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
wrapper := d.Val()
|
||||
|
||||
// At least one same-line option must be provided
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
*m = append(*m, d.RemainingArgs()...)
|
||||
|
||||
// No blocks are supported
|
||||
if d.NextBlock(d.Nesting()) {
|
||||
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MatchRemoteIP matches based on the remote IP of the
|
||||
// connection. Specific IPs or CIDR ranges can be specified.
|
||||
//
|
||||
|
@ -83,16 +118,19 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Provision parses m's IP ranges, either from IP or CIDR expressions.
|
||||
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
|
||||
repl := caddy.NewReplacer()
|
||||
m.logger = ctx.Logger()
|
||||
for _, str := range m.Ranges {
|
||||
cidrs, err := m.parseIPRange(str)
|
||||
rs := repl.ReplaceAll(str, "")
|
||||
cidrs, err := m.parseIPRange(rs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.cidrs = append(m.cidrs, cidrs...)
|
||||
}
|
||||
for _, str := range m.NotRanges {
|
||||
cidrs, err := m.parseIPRange(str)
|
||||
rs := repl.ReplaceAll(str, "")
|
||||
cidrs, err := m.parseIPRange(rs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -145,6 +183,46 @@ func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// remote_ip <ranges...>
|
||||
//
|
||||
// Note: IPs and CIDRs prefixed with ! symbol are treated as not_ranges
|
||||
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
wrapper := d.Val()
|
||||
|
||||
// At least one same-line option must be provided
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
for d.NextArg() {
|
||||
val := d.Val()
|
||||
var exclamation bool
|
||||
if len(val) > 1 && val[0] == '!' {
|
||||
exclamation, val = true, val[1:]
|
||||
}
|
||||
ranges := []string{val}
|
||||
if val == "private_ranges" {
|
||||
ranges = internal.PrivateRangesCIDR()
|
||||
}
|
||||
if exclamation {
|
||||
m.NotRanges = append(m.NotRanges, ranges...)
|
||||
} else {
|
||||
m.Ranges = append(m.Ranges, ranges...)
|
||||
}
|
||||
}
|
||||
|
||||
// No blocks are supported
|
||||
if d.NextBlock(d.Nesting()) {
|
||||
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MatchLocalIP matches based on the IP address of the interface
|
||||
// receiving the connection. Specific IPs or CIDR ranges can be specified.
|
||||
type MatchLocalIP struct {
|
||||
|
@ -165,9 +243,11 @@ func (MatchLocalIP) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Provision parses m's IP ranges, either from IP or CIDR expressions.
|
||||
func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
|
||||
repl := caddy.NewReplacer()
|
||||
m.logger = ctx.Logger()
|
||||
for _, str := range m.Ranges {
|
||||
cidrs, err := m.parseIPRange(str)
|
||||
rs := repl.ReplaceAll(str, "")
|
||||
cidrs, err := m.parseIPRange(rs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -219,6 +299,36 @@ func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the MatchLocalIP from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// local_ip <ranges...>
|
||||
func (m *MatchLocalIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
wrapper := d.Val()
|
||||
|
||||
// At least one same-line option must be provided
|
||||
if d.CountRemainingArgs() == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
for d.NextArg() {
|
||||
val := d.Val()
|
||||
if val == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, val)
|
||||
}
|
||||
|
||||
// No blocks are supported
|
||||
if d.NextBlock(d.Nesting()) {
|
||||
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ ConnectionMatcher = (*MatchServerName)(nil)
|
||||
|
@ -226,4 +336,8 @@ var (
|
|||
|
||||
_ caddy.Provisioner = (*MatchLocalIP)(nil)
|
||||
_ ConnectionMatcher = (*MatchLocalIP)(nil)
|
||||
|
||||
_ caddyfile.Unmarshaler = (*MatchLocalIP)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchServerName)(nil)
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package caddy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -354,6 +355,8 @@ func (f fileReplacementProvider) replace(key string) (any, bool) {
|
|||
zap.Error(err))
|
||||
return nil, true
|
||||
}
|
||||
body = bytes.TrimSuffix(body, []byte("\n"))
|
||||
body = bytes.TrimSuffix(body, []byte("\r"))
|
||||
return string(body), true
|
||||
}
|
||||
|
||||
|
|
|
@ -431,6 +431,14 @@ func TestReplacerNew(t *testing.T) {
|
|||
variable: "file.caddytest/integration/testdata/foo.txt",
|
||||
value: "foo",
|
||||
},
|
||||
{
|
||||
variable: "file.caddytest/integration/testdata/foo_with_trailing_newline.txt",
|
||||
value: "foo",
|
||||
},
|
||||
{
|
||||
variable: "file.caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt",
|
||||
value: "foo" + getEOL(),
|
||||
},
|
||||
} {
|
||||
if val, ok := repl.providers[1].replace(tc.variable); ok {
|
||||
if val != tc.value {
|
||||
|
@ -442,6 +450,13 @@ func TestReplacerNew(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func getEOL() string {
|
||||
if os.PathSeparator == '\\' {
|
||||
return "\r\n" // Windows EOL
|
||||
}
|
||||
return "\n" // Unix and modern macOS EOL
|
||||
}
|
||||
|
||||
func TestReplacerNewWithoutFile(t *testing.T) {
|
||||
repl := NewReplacer().WithoutFile()
|
||||
|
||||
|
|
Loading…
Reference in a new issue