mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cbbd1b62e |
+3
-3
@@ -7,7 +7,7 @@ The Caddy project would like to make sure that it stays on top of all practicall
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | ✔️ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 1.x | :x: |
|
||||
| < 1.x | :x: |
|
||||
|
||||
@@ -24,7 +24,7 @@ We do not accept reports if the steps imply or require a compromised system or t
|
||||
|
||||
Client-side exploits are out of scope. In other words, it is not a bug in Caddy if the web browser does something unsafe, even if the downloaded content was served by Caddy. (Those kinds of exploits can generally be mitigated by proper configuration of HTTP headers.) As a general rule, the content served by Caddy is not considered in scope because content is configurable by the site owner or the associated web application.
|
||||
|
||||
Security bugs in code dependencies (including Go's standard library) are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
|
||||
Security bugs in code dependencies are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
@@ -42,7 +42,7 @@ We'll need enough information to verify the bug and make a patch. To speed thing
|
||||
- Specific minimal steps to reproduce the issue from scratch
|
||||
- A working patch
|
||||
|
||||
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl -v` instead of web browsers.
|
||||
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl` instead of web browsers.
|
||||
|
||||
We consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems. Do not redact domain names from your reports. The actual content of your domain name affects Caddy's behavior, so we need the exact domain name(s) to reproduce with, or your report will be ignored.
|
||||
|
||||
|
||||
+10
-16
@@ -18,22 +18,17 @@ jobs:
|
||||
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
go:
|
||||
- '1.20'
|
||||
- '1.21'
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
go: [ '1.19', '1.20' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.20'
|
||||
GO_SEMVER: '~1.20.6'
|
||||
- go: '1.19'
|
||||
GO_SEMVER: '~1.19.6'
|
||||
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.20'
|
||||
GO_SEMVER: '~1.20.1'
|
||||
|
||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||
@@ -54,7 +49,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
@@ -73,7 +68,6 @@ jobs:
|
||||
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
@@ -136,7 +130,7 @@ jobs:
|
||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: Run Tests
|
||||
run: |
|
||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||
@@ -162,9 +156,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: check
|
||||
|
||||
@@ -15,32 +15,20 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos:
|
||||
- 'android'
|
||||
- 'linux'
|
||||
- 'solaris'
|
||||
- 'illumos'
|
||||
- 'dragonfly'
|
||||
- 'freebsd'
|
||||
- 'openbsd'
|
||||
- 'plan9'
|
||||
- 'windows'
|
||||
- 'darwin'
|
||||
- 'netbsd'
|
||||
go:
|
||||
- '1.21'
|
||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||
go: [ '1.20' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.20'
|
||||
GO_SEMVER: '~1.20.1'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
|
||||
@@ -17,45 +17,25 @@ jobs:
|
||||
# From https://github.com/golangci/golangci-lint-action
|
||||
golangci:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
name: lint
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '~1.21.0'
|
||||
go-version: '~1.19.6'
|
||||
check-latest: true
|
||||
|
||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
||||
skip-pkg-cache: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.54
|
||||
|
||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
||||
skip-pkg-cache: true
|
||||
|
||||
version: v1.50
|
||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||
args: --timeout 10m
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
govulncheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
go-version-input: '~1.21.0'
|
||||
check-latest: true
|
||||
|
||||
@@ -10,16 +10,14 @@ jobs:
|
||||
name: Release
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
go:
|
||||
- '1.21'
|
||||
os: [ ubuntu-latest ]
|
||||
go: [ '1.20' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.20'
|
||||
GO_SEMVER: '~1.20.1'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||
@@ -32,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -43,7 +41,7 @@ jobs:
|
||||
check-latest: true
|
||||
|
||||
# Force fetch upstream tags -- because 65 minutes
|
||||
# tl;dr: actions/checkout@v4 runs this line:
|
||||
# tl;dr: actions/checkout@v3 runs this line:
|
||||
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
||||
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
||||
# git fetch --prune --unshallow
|
||||
@@ -106,10 +104,10 @@ jobs:
|
||||
run: syft version
|
||||
# GoReleaser will take care of publishing those artifacts into the release
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --timeout 60m
|
||||
args: release --rm-dist --timeout 60m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.vars.outputs.version_tag }}
|
||||
|
||||
@@ -10,8 +10,7 @@ jobs:
|
||||
name: Release Published
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
os: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
|
||||
@@ -12,7 +12,6 @@ Caddyfile.*
|
||||
cmd/caddy/caddy
|
||||
cmd/caddy/caddy.exe
|
||||
cmd/caddy/tmp/*.exe
|
||||
cmd/caddy/.env
|
||||
|
||||
# mac specific
|
||||
.DS_Store
|
||||
|
||||
+7
-20
@@ -2,27 +2,14 @@ linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
||||
ignoretests: true
|
||||
gci:
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
- default # Default section: contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
||||
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
||||
# Skip generated files.
|
||||
# Default: true
|
||||
skip-generated: true
|
||||
# Enable custom order of sections.
|
||||
# If `true`, make the section order the same as the order of `sections`.
|
||||
# Default: false
|
||||
custom-order: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- errcheck
|
||||
- gci
|
||||
- gofumpt
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
@@ -90,23 +77,23 @@ output:
|
||||
issues:
|
||||
exclude-rules:
|
||||
# we aren't calling unknown URL
|
||||
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
||||
- text: "G107" # G107: Url provided to HTTP request as taint input
|
||||
linters:
|
||||
- gosec
|
||||
# as a web server that's expected to handle any template, this is totally in the hands of the user.
|
||||
- text: 'G203' # G203: Use of unescaped data in HTML templates
|
||||
- text: "G203" # G203: Use of unescaped data in HTML templates
|
||||
linters:
|
||||
- gosec
|
||||
# we're shelling out to known commands, not relying on user-defined input.
|
||||
- text: 'G204' # G204: Audit use of command execution
|
||||
- text: "G204" # G204: Audit use of command execution
|
||||
linters:
|
||||
- gosec
|
||||
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
||||
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
||||
text: 'G404' # G404: Insecure random number source (rand)
|
||||
text: "G404" # G404: Insecure random number source (rand)
|
||||
linters:
|
||||
- gosec
|
||||
- path: modules/caddyhttp/reverseproxy/streaming.go
|
||||
text: 'G404' # G404: Insecure random number source (rand)
|
||||
text: "G404" # G404: Insecure random number source (rand)
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
+10
-8
@@ -43,7 +43,6 @@ builds:
|
||||
- arm64
|
||||
- s390x
|
||||
- ppc64le
|
||||
- riscv64
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
@@ -55,20 +54,14 @@ builds:
|
||||
goarch: ppc64le
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
- goos: freebsd
|
||||
goarch: ppc64le
|
||||
- goos: freebsd
|
||||
goarch: s390x
|
||||
- goos: freebsd
|
||||
goarch: riscv64
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: "5"
|
||||
@@ -113,10 +106,11 @@ archives:
|
||||
{{- with .Mips }}_{{ . }}{{ end }}
|
||||
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
|
||||
|
||||
# package the 'caddy-build' directory into a tarball,
|
||||
# packge the 'caddy-build' directory into a tarball,
|
||||
# allowing users to build the exact same set of files as ours.
|
||||
- id: source
|
||||
meta: true
|
||||
rlcp: true
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_buildable-artifact"
|
||||
files:
|
||||
- src: LICENSE
|
||||
@@ -133,6 +127,14 @@ source:
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_src'
|
||||
format: 'tar.gz'
|
||||
|
||||
# This will make the destination paths be relative to the longest common
|
||||
# path prefix between all the files matched and the source glob.
|
||||
# Enabling this essentially mimic the behavior of nfpm's contents section.
|
||||
# It will be the default by June 2023.
|
||||
#
|
||||
# Default: false
|
||||
rlcp: true
|
||||
|
||||
# Additional files/template/globs you want to add to the source archive.
|
||||
#
|
||||
# Default: empty.
|
||||
|
||||
@@ -87,7 +87,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
||||
|
||||
Requirements:
|
||||
|
||||
- [Go 1.20 or newer](https://golang.org/dl/)
|
||||
- [Go 1.19 or newer](https://golang.org/dl/)
|
||||
|
||||
### For development
|
||||
|
||||
|
||||
@@ -318,32 +318,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
||||
// messages. If the requested URI does not include an Internet host
|
||||
// name for the service being requested, then the Host header field MUST
|
||||
// be given with an empty value."
|
||||
//
|
||||
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
|
||||
// Understandable, but frustrating. See:
|
||||
// https://github.com/golang/go/issues/60374
|
||||
// See also the discussion here:
|
||||
// https://github.com/golang/go/issues/61431
|
||||
//
|
||||
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
|
||||
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
|
||||
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
||||
// security checks, the infosec community assures me that it is secure to do
|
||||
// so, because:
|
||||
// 1) Browsers do not allow access to unix sockets
|
||||
// 2) DNS is irrelevant to unix sockets
|
||||
//
|
||||
// I am not quite ready to trust either of those external factors, so instead
|
||||
// of disabling Host/Origin checks, we now allow specific Host values when
|
||||
// accessing the admin endpoint over unix sockets. I definitely don't trust
|
||||
// DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
|
||||
// and IP shouldn't even be used, but if it is for some reason, I think we can
|
||||
// at least be reasonably assured that 127.0.0.1 and ::1 route to the local
|
||||
// machine, meaning that a hypothetical browser origin would have to be on the
|
||||
// local machine as well.
|
||||
uniqueOrigins[""] = struct{}{}
|
||||
uniqueOrigins["127.0.0.1"] = struct{}{}
|
||||
uniqueOrigins["::1"] = struct{}{}
|
||||
} else {
|
||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||
@@ -1041,9 +1016,9 @@ func handleConfigID(w http.ResponseWriter, r *http.Request) error {
|
||||
id := parts[2]
|
||||
|
||||
// map the ID to the expanded path
|
||||
rawCfgMu.RLock()
|
||||
currentCtxMu.RLock()
|
||||
expanded, ok := rawCfgIndex[id]
|
||||
rawCfgMu.RUnlock()
|
||||
defer currentCtxMu.RUnlock()
|
||||
if !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
@@ -1196,27 +1171,15 @@ traverseLoop:
|
||||
}
|
||||
case http.MethodPut:
|
||||
if _, ok := v[part]; ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusConflict,
|
||||
Err: fmt.Errorf("[%s] key already exists: %s", path, part),
|
||||
}
|
||||
return fmt.Errorf("[%s] key already exists: %s", path, part)
|
||||
}
|
||||
v[part] = val
|
||||
case http.MethodPatch:
|
||||
if _, ok := v[part]; !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
|
||||
}
|
||||
return fmt.Errorf("[%s] key does not exist: %s", path, part)
|
||||
}
|
||||
v[part] = val
|
||||
case http.MethodDelete:
|
||||
if _, ok := v[part]; !ok {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
|
||||
}
|
||||
}
|
||||
delete(v, part)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized method %s", method)
|
||||
@@ -1358,7 +1321,7 @@ var (
|
||||
// will get deleted before the process gracefully exits.
|
||||
func PIDFile(filename string) error {
|
||||
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
||||
err := os.WriteFile(filename, pid, 0o600)
|
||||
err := os.WriteFile(filename, pid, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -75,12 +75,6 @@ func TestUnsyncedConfigAccess(t *testing.T) {
|
||||
path: "/bar/qq",
|
||||
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
path: "/bar/qq",
|
||||
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
path: "/list",
|
||||
|
||||
@@ -34,11 +34,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/notify"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/notify"
|
||||
)
|
||||
|
||||
// Config is the top (or beginning) of the Caddy configuration structure.
|
||||
@@ -157,8 +156,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||
return fmt.Errorf("method not allowed")
|
||||
}
|
||||
|
||||
rawCfgMu.Lock()
|
||||
defer rawCfgMu.Unlock()
|
||||
currentCtxMu.Lock()
|
||||
defer currentCtxMu.Unlock()
|
||||
|
||||
if ifMatchHeader != "" {
|
||||
// expect the first and last character to be quotes
|
||||
@@ -258,8 +257,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||
// readConfig traverses the current config to path
|
||||
// and writes its JSON encoding to out.
|
||||
func readConfig(path string, out io.Writer) error {
|
||||
rawCfgMu.RLock()
|
||||
defer rawCfgMu.RUnlock()
|
||||
currentCtxMu.RLock()
|
||||
defer currentCtxMu.RUnlock()
|
||||
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
||||
}
|
||||
|
||||
@@ -306,7 +305,7 @@ func indexConfigObjects(ptr any, configPath string, index map[string]string) err
|
||||
// it as the new config, replacing any other current config.
|
||||
// It does NOT update the raw config state, as this is a
|
||||
// lower-level function; most callers will want to use Load
|
||||
// instead. A write lock on rawCfgMu is required! If
|
||||
// instead. A write lock on currentCtxMu is required! If
|
||||
// allowPersist is false, it will not be persisted to disk,
|
||||
// even if it is configured to.
|
||||
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
@@ -341,10 +340,8 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
}
|
||||
|
||||
// swap old context (including its config) with the new one
|
||||
currentCtxMu.Lock()
|
||||
oldCtx := currentCtx
|
||||
currentCtx = ctx
|
||||
currentCtxMu.Unlock()
|
||||
|
||||
// Stop, Cleanup each old app
|
||||
unsyncedStop(oldCtx)
|
||||
@@ -357,13 +354,13 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||
newCfg.Admin.Config.Persist == nil ||
|
||||
*newCfg.Admin.Config.Persist) {
|
||||
dir := filepath.Dir(ConfigAutosavePath)
|
||||
err := os.MkdirAll(dir, 0o700)
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
Log().Error("unable to create folder for config autosave",
|
||||
zap.String("dir", dir),
|
||||
zap.Error(err))
|
||||
} else {
|
||||
err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0o600)
|
||||
err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
|
||||
if err == nil {
|
||||
Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
|
||||
} else {
|
||||
@@ -630,35 +627,22 @@ type ConfigLoader interface {
|
||||
// stop the others. Stop should only be called
|
||||
// if not replacing with a new config.
|
||||
func Stop() error {
|
||||
currentCtxMu.RLock()
|
||||
ctx := currentCtx
|
||||
currentCtxMu.RUnlock()
|
||||
|
||||
rawCfgMu.Lock()
|
||||
unsyncedStop(ctx)
|
||||
|
||||
currentCtxMu.Lock()
|
||||
defer currentCtxMu.Unlock()
|
||||
unsyncedStop(currentCtx)
|
||||
currentCtx = Context{}
|
||||
currentCtxMu.Unlock()
|
||||
|
||||
rawCfgJSON = nil
|
||||
rawCfgIndex = nil
|
||||
rawCfg[rawConfigKey] = nil
|
||||
rawCfgMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unsyncedStop stops ctx from running, but has
|
||||
// no locking around ctx. It is a no-op if ctx has a
|
||||
// nil cfg. If any app returns an error when stopping,
|
||||
// unsyncedStop stops cfg from running, but has
|
||||
// no locking around cfg. It is a no-op if cfg is
|
||||
// nil. If any app returns an error when stopping,
|
||||
// it is logged and the function continues stopping
|
||||
// the next app. This function assumes all apps in
|
||||
// ctx were successfully started first.
|
||||
//
|
||||
// A lock on rawCfgMu is required, even though this
|
||||
// function does not access rawCfg, that lock
|
||||
// synchronizes the stop/start of apps.
|
||||
// cfg were successfully started first.
|
||||
func unsyncedStop(ctx Context) {
|
||||
if ctx.cfg == nil {
|
||||
return
|
||||
@@ -832,7 +816,7 @@ func InstanceID() (uuid.UUID, error) {
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0o600)
|
||||
err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0600)
|
||||
return uuid, err
|
||||
} else if err != nil {
|
||||
return [16]byte{}, err
|
||||
@@ -985,12 +969,14 @@ type CtxKey string
|
||||
|
||||
// This group of variables pertains to the current configuration.
|
||||
var (
|
||||
// currentCtxMu protects everything in this var block.
|
||||
currentCtxMu sync.RWMutex
|
||||
|
||||
// currentCtx is the root context for the currently-running
|
||||
// configuration, which can be accessed through this value.
|
||||
// If the Config contained in this value is not nil, then
|
||||
// a config is currently active/running.
|
||||
currentCtx Context
|
||||
currentCtxMu sync.RWMutex
|
||||
currentCtx Context
|
||||
|
||||
// rawCfg is the current, generic-decoded configuration;
|
||||
// we initialize it as a map with one field ("config")
|
||||
@@ -1008,10 +994,6 @@ var (
|
||||
// rawCfgIndex is the map of user-assigned ID to expanded
|
||||
// path, for converting /id/ paths to /config/ paths.
|
||||
rawCfgIndex map[string]string
|
||||
|
||||
// rawCfgMu protects all the rawCfg fields and also
|
||||
// essentially synchronizes config changes/reloads.
|
||||
rawCfgMu sync.RWMutex
|
||||
)
|
||||
|
||||
// errSameConfig is returned if the new config is the same
|
||||
|
||||
@@ -106,7 +106,7 @@ func (d *Dispenser) nextOnSameLine() bool {
|
||||
}
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
if !isNextOnNewLine(curr, next) {
|
||||
if curr.File == next.File && curr.Line+curr.NumLineBreaks() == next.Line {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func (d *Dispenser) NextLine() bool {
|
||||
}
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
if isNextOnNewLine(curr, next) {
|
||||
if curr.File != next.File || curr.Line+curr.NumLineBreaks() < next.Line {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
@@ -391,22 +391,22 @@ func (d *Dispenser) Reset() {
|
||||
// an argument.
|
||||
func (d *Dispenser) ArgErr() error {
|
||||
if d.Val() == "{" {
|
||||
return d.Err("unexpected token '{', expecting argument")
|
||||
return d.Err("Unexpected token '{', expecting argument")
|
||||
}
|
||||
return d.Errf("wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||
return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||
}
|
||||
|
||||
// SyntaxErr creates a generic syntax error which explains what was
|
||||
// found and what was expected.
|
||||
func (d *Dispenser) SyntaxErr(expected string) error {
|
||||
msg := fmt.Sprintf("syntax error: unexpected token '%s', expecting '%s', at %s:%d import chain: ['%s']", d.Val(), expected, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
|
||||
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s', import chain: ['%s']", d.File(), d.Line(), d.Val(), expected, strings.Join(d.Token().imports, "','"))
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
// EOFErr returns an error indicating that the dispenser reached
|
||||
// the end of the input when searching for the next token.
|
||||
func (d *Dispenser) EOFErr() error {
|
||||
return d.Errf("unexpected EOF")
|
||||
return d.Errf("Unexpected EOF")
|
||||
}
|
||||
|
||||
// Err generates a custom parse-time error with a message of msg.
|
||||
@@ -421,10 +421,7 @@ func (d *Dispenser) Errf(format string, args ...any) error {
|
||||
|
||||
// WrapErr takes an existing error and adds the Caddyfile file and line number.
|
||||
func (d *Dispenser) WrapErr(err error) error {
|
||||
if len(d.Token().imports) > 0 {
|
||||
return fmt.Errorf("%w, at %s:%d import chain ['%s']", err, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
|
||||
}
|
||||
return fmt.Errorf("%w, at %s:%d", err, d.File(), d.Line())
|
||||
return fmt.Errorf("%s:%d - Error during parsing: %w, import chain: ['%s']", d.File(), d.Line(), err, strings.Join(d.Token().imports, "','"))
|
||||
}
|
||||
|
||||
// Delete deletes the current token and returns the updated slice
|
||||
@@ -467,7 +464,17 @@ func (d *Dispenser) isNewLine() bool {
|
||||
|
||||
prev := d.tokens[d.cursor-1]
|
||||
curr := d.tokens[d.cursor]
|
||||
return isNextOnNewLine(prev, curr)
|
||||
|
||||
// If the previous token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if prev.File != curr.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the previous token (incl line breaks) ends
|
||||
// on a line earlier than the current token,
|
||||
// then the current token is on a new line
|
||||
return prev.Line+prev.NumLineBreaks() < curr.Line
|
||||
}
|
||||
|
||||
// isNextOnNewLine determines whether the current token is on a different
|
||||
@@ -483,5 +490,15 @@ func (d *Dispenser) isNextOnNewLine() bool {
|
||||
|
||||
curr := d.tokens[d.cursor]
|
||||
next := d.tokens[d.cursor+1]
|
||||
return isNextOnNewLine(curr, next)
|
||||
|
||||
// If the next token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if curr.File != next.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the current token (incl line breaks) ends
|
||||
// on a line earlier than the next token,
|
||||
// then the next token is on a new line
|
||||
return curr.Line+curr.NumLineBreaks() < next.Line
|
||||
}
|
||||
|
||||
@@ -19,9 +19,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// parseVariadic determines if the token is a variadic placeholder,
|
||||
@@ -52,13 +51,6 @@ func parseVariadic(token Token, argCount int) (bool, int, int) {
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
// A valid token may contain several placeholders, and
|
||||
// they may be separated by ":". It's not variadic.
|
||||
// https://github.com/caddyserver/caddy/issues/5716
|
||||
if strings.Contains(start, "}") || strings.Contains(end, "{") {
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
var (
|
||||
startIndex = 0
|
||||
endIndex = argCount
|
||||
@@ -101,11 +93,6 @@ func makeArgsReplacer(args []string) *caddy.Replacer {
|
||||
// TODO: Remove the deprecated {args.*} placeholder
|
||||
// support at some point in the future
|
||||
if matches := argsRegexpIndexDeprecated.FindStringSubmatch(key); len(matches) > 0 {
|
||||
// What's matched may be a substring of the key
|
||||
if matches[0] != key {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
@@ -124,11 +111,6 @@ func makeArgsReplacer(args []string) *caddy.Replacer {
|
||||
|
||||
// Handle args[*] form
|
||||
if matches := argsRegexpIndex.FindStringSubmatch(key); len(matches) > 0 {
|
||||
// What's matched may be a substring of the key
|
||||
if matches[0] != key {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if strings.Contains(matches[1], ":") {
|
||||
caddy.Log().Named("caddyfile").Warn(
|
||||
"Variadic placeholder {args[" + matches[1] + "]} must be a token on its own")
|
||||
|
||||
@@ -34,7 +34,6 @@ func (i *importGraph) addNode(name string) {
|
||||
}
|
||||
i.nodes[name] = true
|
||||
}
|
||||
|
||||
func (i *importGraph) addNodes(names []string) {
|
||||
for _, name := range names {
|
||||
i.addNode(name)
|
||||
@@ -44,7 +43,6 @@ func (i *importGraph) addNodes(names []string) {
|
||||
func (i *importGraph) removeNode(name string) {
|
||||
delete(i.nodes, name)
|
||||
}
|
||||
|
||||
func (i *importGraph) removeNodes(names []string) {
|
||||
for _, name := range names {
|
||||
i.removeNode(name)
|
||||
@@ -75,7 +73,6 @@ func (i *importGraph) addEdge(from, to string) error {
|
||||
i.edges[from] = append(i.edges[from], to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *importGraph) addEdges(from string, tos []string) error {
|
||||
for _, to := range tos {
|
||||
err := i.addEdge(from, to)
|
||||
|
||||
@@ -137,32 +137,18 @@ func (l *lexer) next() (bool, error) {
|
||||
}
|
||||
|
||||
// detect whether we have the start of a heredoc
|
||||
if !(quoted || btQuoted) && !(inHeredoc || heredocEscaped) &&
|
||||
len(val) > 1 && string(val[:2]) == "<<" {
|
||||
// a space means it's just a regular token and not a heredoc
|
||||
if ch == ' ' {
|
||||
return makeToken(0), nil
|
||||
if !inHeredoc && !heredocEscaped && len(val) > 1 && string(val[:2]) == "<<" {
|
||||
if ch == '<' {
|
||||
return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
|
||||
}
|
||||
|
||||
// skip CR, we only care about LF
|
||||
if ch == '\r' {
|
||||
continue
|
||||
}
|
||||
|
||||
// after hitting a newline, we know that the heredoc marker
|
||||
// is the characters after the two << and the newline.
|
||||
// we reset the val because the heredoc is syntax we don't
|
||||
// want to keep.
|
||||
if ch == '\n' {
|
||||
if len(val) == 2 {
|
||||
return false, fmt.Errorf("missing opening heredoc marker on line #%d; must contain only alpha-numeric characters, dashes and underscores; got empty string", l.line)
|
||||
}
|
||||
|
||||
// check if there's too many <
|
||||
if string(val[:3]) == "<<<" {
|
||||
return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
|
||||
}
|
||||
|
||||
heredocMarker = string(val[2:])
|
||||
if !heredocMarkerRegexp.Match([]byte(heredocMarker)) {
|
||||
return false, fmt.Errorf("heredoc marker on line #%d must contain only alpha-numeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
|
||||
@@ -352,28 +338,3 @@ func (t Token) NumLineBreaks() int {
|
||||
}
|
||||
|
||||
var heredocMarkerRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
|
||||
|
||||
// isNextOnNewLine tests whether t2 is on a different line from t1
|
||||
func isNextOnNewLine(t1, t2 Token) bool {
|
||||
// If the second token is from a different file,
|
||||
// we can assume it's from a different line
|
||||
if t1.File != t2.File {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the second token is from a different import chain,
|
||||
// we can assume it's from a different line
|
||||
if len(t1.imports) != len(t2.imports) {
|
||||
return true
|
||||
}
|
||||
for i, im := range t1.imports {
|
||||
if im != t2.imports[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If the first token (incl line breaks) ends
|
||||
// on a line earlier than the next token,
|
||||
// then the second token is on a new line
|
||||
return t1.Line+t1.NumLineBreaks() < t2.Line
|
||||
}
|
||||
|
||||
@@ -322,59 +322,15 @@ EOF same-line-arg
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`escaped-heredoc \<< >>`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `escaped-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <EOF
|
||||
input: []byte(`heredoc <EOF
|
||||
content
|
||||
EOF same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `heredoc`},
|
||||
{Line: 1, Text: `<EOF`},
|
||||
{Line: 2, Text: `content`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <<<EOF content`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<<EOF`},
|
||||
{Line: 1, Text: `content`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc "<<" ">>"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc << >>`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<`},
|
||||
{Line: 1, Text: `>>`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`not-a-heredoc <<HERE SAME LINE
|
||||
content
|
||||
HERE same-line-arg
|
||||
`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `not-a-heredoc`},
|
||||
{Line: 1, Text: `<<HERE`},
|
||||
{Line: 1, Text: `SAME`},
|
||||
{Line: 1, Text: `LINE`},
|
||||
{Line: 2, Text: `content`},
|
||||
{Line: 3, Text: `HERE`},
|
||||
{Line: 3, Text: `EOF`},
|
||||
{Line: 3, Text: `same-line-arg`},
|
||||
},
|
||||
},
|
||||
@@ -410,9 +366,12 @@ EOF same-line-arg
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("not-a-heredoc <<\n"),
|
||||
input: []byte(`heredoc <<HERE SAME LINE
|
||||
content
|
||||
HERE same-line-arg
|
||||
`),
|
||||
expectErr: true,
|
||||
errorMessage: "missing opening heredoc marker on line #1; must contain only alpha-numeric characters, dashes and underscores; got empty string",
|
||||
errorMessage: "heredoc marker on line #1 must contain only alpha-numeric characters, dashes and underscores; got 'HERE SAME LINE'",
|
||||
},
|
||||
{
|
||||
input: []byte(`heredoc <<<EOF
|
||||
|
||||
@@ -22,9 +22,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Parse parses the input just enough to group tokens, in
|
||||
@@ -465,7 +464,7 @@ func (p *parser) doImport(nesting int) error {
|
||||
// format, won't check for nesting correctness or any other error, that's what parser does.
|
||||
if !maybeSnippet && nesting == 0 {
|
||||
// first of the line
|
||||
if i == 0 || isNextOnNewLine(tokensCopy[i-1], token) {
|
||||
if i == 0 || importedTokens[i-1].File != token.File || importedTokens[i-1].Line+importedTokens[i-1].NumLineBreaks() < token.Line {
|
||||
index = 0
|
||||
} else {
|
||||
index++
|
||||
@@ -566,6 +565,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
||||
// are loaded into the current server block for later use
|
||||
// by directive setup functions.
|
||||
func (p *parser) directive() error {
|
||||
|
||||
// a segment is a list of tokens associated with this directive
|
||||
var segment Segment
|
||||
|
||||
|
||||
@@ -91,10 +91,6 @@ func TestParseVariadic(t *testing.T) {
|
||||
input: "{args[0:10]}",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
input: "{args[0]}:{args[1]}:{args[2]}",
|
||||
result: false,
|
||||
},
|
||||
} {
|
||||
token := Token{
|
||||
File: "test",
|
||||
@@ -722,36 +718,6 @@ func TestEnvironmentReplacement(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportReplacementInJSONWithBrace(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
args []string
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
args: []string{"123"},
|
||||
input: "{args[0]}",
|
||||
expect: "123",
|
||||
},
|
||||
{
|
||||
args: []string{"123"},
|
||||
input: `{"key":"{args[0]}"}`,
|
||||
expect: `{"key":"123"}`,
|
||||
},
|
||||
{
|
||||
args: []string{"123", "123"},
|
||||
input: `{"key":[{args[0]},{args[1]}]}`,
|
||||
expect: `{"key":[123,123]}`,
|
||||
},
|
||||
} {
|
||||
repl := makeArgsReplacer(test.args)
|
||||
actual := repl.ReplaceKnown(test.input, "")
|
||||
if actual != test.expect {
|
||||
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnippets(t *testing.T) {
|
||||
p := testParser(`
|
||||
(common) {
|
||||
|
||||
@@ -135,4 +135,11 @@ func (am adapterModule) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// UnwrapAdapter return the original Adapter, this method must be exported
|
||||
// to be type-assertable 🤷
|
||||
// hack, https://github.com/caddyserver/caddy/issues/5621
|
||||
func (am adapterModule) UnwrapAdapter() any {
|
||||
return am.Adapter
|
||||
}
|
||||
|
||||
var configAdapters = make(map[string]Adapter)
|
||||
|
||||
@@ -24,11 +24,10 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
// mapAddressToServerBlocks returns a map of listener address to list of server
|
||||
@@ -78,8 +77,7 @@ import (
|
||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||
// (Doing this is essentially a map-reduce technique.)
|
||||
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
|
||||
options map[string]any,
|
||||
) (map[string][]serverBlock, error) {
|
||||
options map[string]any) (map[string][]serverBlock, error) {
|
||||
sbmap := make(map[string][]serverBlock)
|
||||
|
||||
for i, sblock := range originalServerBlocks {
|
||||
@@ -189,25 +187,13 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
|
||||
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
|
||||
// site addresses to Caddy listener addresses for each server block.
|
||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
||||
options map[string]any,
|
||||
) ([]string, error) {
|
||||
options map[string]any) ([]string, error) {
|
||||
addr, err := ParseAddress(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing key: %v", err)
|
||||
}
|
||||
addr = addr.Normalize()
|
||||
|
||||
switch addr.Scheme {
|
||||
case "wss":
|
||||
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
||||
case "ws":
|
||||
return nil, fmt.Errorf("the scheme ws:// is only supported in browsers; use http:// instead")
|
||||
case "https", "http", "":
|
||||
// Do nothing or handle the valid schemes
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported URL scheme %s://", addr.Scheme)
|
||||
}
|
||||
|
||||
// figure out the HTTP and HTTPS ports; either
|
||||
// use defaults, or override with user config
|
||||
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
|
||||
@@ -26,15 +26,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -181,17 +180,17 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
case "protocols":
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return nil, h.Errf("protocols requires one or two arguments")
|
||||
return nil, h.SyntaxErr("one or two protocols")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
|
||||
return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0])
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
||||
}
|
||||
cp.ProtocolMin = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
|
||||
return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1])
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
||||
}
|
||||
cp.ProtocolMax = args[1]
|
||||
}
|
||||
@@ -199,7 +198,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
case "ciphers":
|
||||
for h.NextArg() {
|
||||
if !caddytls.CipherSuiteNameSupported(h.Val()) {
|
||||
return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||
}
|
||||
cp.CipherSuites = append(cp.CipherSuites, h.Val())
|
||||
}
|
||||
@@ -789,8 +788,7 @@ func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
|
||||
// parseLog parses the log directive. Syntax:
|
||||
//
|
||||
// log <logger_name> {
|
||||
// hostnames <hostnames...>
|
||||
// log {
|
||||
// output <writer_module> ...
|
||||
// format <encoder_module> ...
|
||||
// level <level>
|
||||
@@ -811,13 +809,11 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
var configValues []ConfigValue
|
||||
for h.Next() {
|
||||
// Logic below expects that a name is always present when a
|
||||
// global option is being parsed; or an optional override
|
||||
// is supported for access logs.
|
||||
var logName string
|
||||
|
||||
// global option is being parsed.
|
||||
var globalLogName string
|
||||
if parseAsGlobalOption {
|
||||
if h.NextArg() {
|
||||
logName = h.Val()
|
||||
globalLogName = h.Val()
|
||||
|
||||
// Only a single argument is supported.
|
||||
if h.NextArg() {
|
||||
@@ -828,47 +824,26 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
// reference the default logger. See the
|
||||
// setupNewDefault function in the logging
|
||||
// package for where this is configured.
|
||||
logName = caddy.DefaultLoggerName
|
||||
globalLogName = caddy.DefaultLoggerName
|
||||
}
|
||||
|
||||
// Verify this name is unused.
|
||||
_, used := globalLogNames[logName]
|
||||
_, used := globalLogNames[globalLogName]
|
||||
if used {
|
||||
return nil, h.Err("duplicate global log option for: " + logName)
|
||||
return nil, h.Err("duplicate global log option for: " + globalLogName)
|
||||
}
|
||||
globalLogNames[logName] = struct{}{}
|
||||
globalLogNames[globalLogName] = struct{}{}
|
||||
} else {
|
||||
// An optional override of the logger name can be provided;
|
||||
// otherwise a default will be used, like "log0", "log1", etc.
|
||||
// No arguments are supported for the server block log directive
|
||||
if h.NextArg() {
|
||||
logName = h.Val()
|
||||
|
||||
// Only a single argument is supported.
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
cl := new(caddy.CustomLog)
|
||||
|
||||
// allow overriding the current site block's hostnames for this logger;
|
||||
// this is useful for setting up loggers per subdomain in a site block
|
||||
// with a wildcard domain
|
||||
customHostnames := []string{}
|
||||
|
||||
for h.NextBlock(0) {
|
||||
switch h.Val() {
|
||||
case "hostnames":
|
||||
if parseAsGlobalOption {
|
||||
return nil, h.Err("hostnames is not allowed in the log global options")
|
||||
}
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
customHostnames = append(customHostnames, args...)
|
||||
|
||||
case "output":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
@@ -927,16 +902,18 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
}
|
||||
|
||||
case "include":
|
||||
// This configuration is only allowed in the global options
|
||||
if !parseAsGlobalOption {
|
||||
return nil, h.Err("include is not allowed in the log directive")
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for h.NextArg() {
|
||||
cl.Include = append(cl.Include, h.Val())
|
||||
}
|
||||
|
||||
case "exclude":
|
||||
// This configuration is only allowed in the global options
|
||||
if !parseAsGlobalOption {
|
||||
return nil, h.Err("exclude is not allowed in the log directive")
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for h.NextArg() {
|
||||
cl.Exclude = append(cl.Exclude, h.Val())
|
||||
@@ -948,34 +925,24 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
}
|
||||
|
||||
var val namedCustomLog
|
||||
val.hostnames = customHostnames
|
||||
|
||||
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
||||
|
||||
// Skip handling of empty logging configs
|
||||
|
||||
if parseAsGlobalOption {
|
||||
// Use indicated name for global log options
|
||||
val.name = logName
|
||||
} else {
|
||||
if logName != "" {
|
||||
val.name = logName
|
||||
} else if !isEmptyConfig {
|
||||
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
|
||||
if parseAsGlobalOption {
|
||||
// Use indicated name for global log options
|
||||
val.name = globalLogName
|
||||
val.log = cl
|
||||
} else {
|
||||
// Construct a log name for server log streams
|
||||
logCounter, ok := h.State["logCounter"].(int)
|
||||
if !ok {
|
||||
logCounter = 0
|
||||
}
|
||||
val.name = fmt.Sprintf("log%d", logCounter)
|
||||
cl.Include = []string{"http.log.access." + val.name}
|
||||
val.log = cl
|
||||
logCounter++
|
||||
h.State["logCounter"] = logCounter
|
||||
}
|
||||
if val.name != "" {
|
||||
cl.Include = []string{"http.log.access." + val.name}
|
||||
}
|
||||
}
|
||||
if !isEmptyConfig {
|
||||
val.log = cl
|
||||
}
|
||||
configValues = append(configValues, ConfigValue{
|
||||
Class: "custom_log",
|
||||
|
||||
@@ -52,13 +52,12 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
log name-override {
|
||||
log invalid {
|
||||
output file foo.log
|
||||
}
|
||||
}
|
||||
`,
|
||||
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
|
||||
expectError: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -278,76 +277,3 @@ func TestImportErrorLine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedImport(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
errorFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[0]} 202
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar"
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[:]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar" 202
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `(t1) {
|
||||
respond {args[0]} {args[1]}
|
||||
}
|
||||
|
||||
(t2) {
|
||||
import t1 {args[:]}
|
||||
}
|
||||
|
||||
:8080 {
|
||||
handle {
|
||||
import t2 "foobar" 202
|
||||
}
|
||||
}`,
|
||||
errorFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
} {
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
||||
if !tc.errorFunc(err) {
|
||||
t.Errorf("Test %d error expectation failed, got %s", i, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,8 +217,7 @@ func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
|
||||
|
||||
// NewRoute returns config values relevant to creating a new HTTP route.
|
||||
func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
|
||||
handler caddyhttp.MiddlewareHandler,
|
||||
) []ConfigValue {
|
||||
handler caddyhttp.MiddlewareHandler) []ConfigValue {
|
||||
mod, err := caddy.GetModule(caddy.GetModuleID(handler))
|
||||
if err != nil {
|
||||
*h.warnings = append(*h.warnings, caddyconfig.Warning{
|
||||
|
||||
@@ -17,21 +17,19 @@ package httpcaddyfile
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -50,7 +48,8 @@ type App struct {
|
||||
}
|
||||
|
||||
// ServerType can set up a config from an HTTP Caddyfile.
|
||||
type ServerType struct{}
|
||||
type ServerType struct {
|
||||
}
|
||||
|
||||
// Setup makes a config from the tokens.
|
||||
func (st ServerType) Setup(
|
||||
@@ -82,18 +81,46 @@ func (st ServerType) Setup(
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
// this will replace both static and user-defined placeholder shorthands
|
||||
// with actual identifiers used by Caddy
|
||||
replacer := NewShorthandReplacer()
|
||||
|
||||
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer)
|
||||
originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
// replace shorthand placeholders (which are convenient
|
||||
// when writing a Caddyfile) with their actual placeholder
|
||||
// identifiers or variable names
|
||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
||||
|
||||
// these are placeholders that allow a user-defined final
|
||||
// parameters, but we still want to provide a shorthand
|
||||
// for those, so we use a regexp to replace
|
||||
regexpReplacements := []struct {
|
||||
search *regexp.Regexp
|
||||
replace string
|
||||
}{
|
||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
|
||||
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
|
||||
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||
}
|
||||
|
||||
for _, sb := range originalServerBlocks {
|
||||
for i := range sb.block.Segments {
|
||||
replacer.ApplyToSegment(&sb.block.Segments[i])
|
||||
for _, segment := range sb.block.Segments {
|
||||
for i := 0; i < len(segment); i++ {
|
||||
// simple string replacements
|
||||
segment[i].Text = replacer.Replace(segment[i].Text)
|
||||
// complex regexp replacements
|
||||
for _, r := range regexpReplacements {
|
||||
segment[i].Text = r.search.ReplaceAllString(segment[i].Text, r.replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sb.block.Keys) == 0 {
|
||||
@@ -214,7 +241,7 @@ func (st ServerType) Setup(
|
||||
if ncl.name == caddy.DefaultLoggerName {
|
||||
hasDefaultLog = true
|
||||
}
|
||||
if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" {
|
||||
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
||||
ncl.log.Level = zap.DebugLevel.CapitalString()
|
||||
}
|
||||
customLogs = append(customLogs, ncl)
|
||||
@@ -297,21 +324,7 @@ func (st ServerType) Setup(
|
||||
Logs: make(map[string]*caddy.CustomLog),
|
||||
}
|
||||
}
|
||||
|
||||
// Add the default log first if defined, so that it doesn't
|
||||
// accidentally get re-created below due to the Exclude logic
|
||||
for _, ncl := range customLogs {
|
||||
if ncl.name == caddy.DefaultLoggerName && ncl.log != nil {
|
||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add the rest of the custom logs
|
||||
for _, ncl := range customLogs {
|
||||
if ncl.log == nil || ncl.name == caddy.DefaultLoggerName {
|
||||
continue
|
||||
}
|
||||
if ncl.name != "" {
|
||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||
}
|
||||
@@ -325,16 +338,8 @@ func (st ServerType) Setup(
|
||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
||||
}
|
||||
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
||||
|
||||
// avoid duplicates by sorting + compacting
|
||||
sort.Strings(defaultLog.Exclude)
|
||||
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
|
||||
}
|
||||
}
|
||||
// we may have not actually added anything, so remove if empty
|
||||
if len(cfg.Logging.Logs) == 0 {
|
||||
cfg.Logging = nil
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, warnings, nil
|
||||
@@ -424,7 +429,6 @@ func (ServerType) extractNamedRoutes(
|
||||
serverBlocks []serverBlock,
|
||||
options map[string]any,
|
||||
warnings *[]caddyconfig.Warning,
|
||||
replacer ShorthandReplacer,
|
||||
) ([]serverBlock, error) {
|
||||
namedRoutes := map[string]*caddyhttp.Route{}
|
||||
|
||||
@@ -450,14 +454,11 @@ func (ServerType) extractNamedRoutes(
|
||||
continue
|
||||
}
|
||||
|
||||
// zip up all the segments since ParseSegmentAsSubroute
|
||||
// was designed to take a directive+
|
||||
wholeSegment := caddyfile.Segment{}
|
||||
for i := range sb.block.Segments {
|
||||
// replace user-defined placeholder shorthands in extracted named routes
|
||||
replacer.ApplyToSegment(&sb.block.Segments[i])
|
||||
|
||||
// zip up all the segments since ParseSegmentAsSubroute
|
||||
// was designed to take a directive+
|
||||
wholeSegment = append(wholeSegment, sb.block.Segments[i]...)
|
||||
for _, segment := range sb.block.Segments {
|
||||
wholeSegment = append(wholeSegment, segment...)
|
||||
}
|
||||
|
||||
h := Helper{
|
||||
@@ -686,7 +687,6 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
|
||||
if len(hosts) > 0 {
|
||||
slices.Sort(hosts) // for deterministic JSON output
|
||||
cp.MatchersRaw = caddy.ModuleMap{
|
||||
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
|
||||
}
|
||||
@@ -718,20 +718,10 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// specifying prefix "https://"
|
||||
// Second part of the condition is to allow creating TLS conn policy even though `auto_https` has been disabled
|
||||
// ensuring compatibility with behavior described in below link
|
||||
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
||||
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
||||
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
||||
(addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
|
||||
|
||||
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
||||
// can add a TLS conn policy if necessary
|
||||
if addr.Scheme == "https" ||
|
||||
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
|
||||
(addr.Scheme != "http" && addr.Host != "" && addr.Port != httpPort) {
|
||||
addressQualifiesForTLS = true
|
||||
}
|
||||
// predict whether auto-HTTPS will add the conn policy for us; if so, we
|
||||
@@ -780,30 +770,17 @@ func (st *ServerType) serversFromPairings(
|
||||
sblockLogHosts := sblock.hostsFromKeys(true)
|
||||
for _, cval := range sblock.pile["custom_log"] {
|
||||
ncl := cval.Value.(namedCustomLog)
|
||||
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
|
||||
if sblock.hasHostCatchAllKey() {
|
||||
// all requests for hosts not able to be listed should use
|
||||
// this log because it's a catch-all-hosts server block
|
||||
srv.Logs.DefaultLoggerName = ncl.name
|
||||
} else if len(ncl.hostnames) > 0 {
|
||||
// if the logger overrides the hostnames, map that to the logger name
|
||||
for _, h := range ncl.hostnames {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
}
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
}
|
||||
} else {
|
||||
// otherwise, map each host to the logger name
|
||||
// map each host to the user's desired logger name
|
||||
for _, h := range sblockLogHosts {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
}
|
||||
// strip the port from the host, if any
|
||||
host, _, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
host = h
|
||||
}
|
||||
srv.Logs.LoggerNames[host] = ncl.name
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1051,8 +1028,8 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
||||
subroute *caddyhttp.Subroute,
|
||||
matcherSetsEnc []caddy.ModuleMap,
|
||||
p sbAddrAssociation,
|
||||
warnings *[]caddyconfig.Warning,
|
||||
) caddyhttp.RouteList {
|
||||
warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
|
||||
|
||||
// nothing to do if... there's nothing to do
|
||||
if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil {
|
||||
return routeList
|
||||
@@ -1441,6 +1418,37 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul
|
||||
return msEncoded, nil
|
||||
}
|
||||
|
||||
// placeholderShorthands returns a slice of old-new string pairs,
|
||||
// where the left of the pair is a placeholder shorthand that may
|
||||
// be used in the Caddyfile, and the right is the replacement.
|
||||
func placeholderShorthands() []string {
|
||||
return []string{
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
"{client_ip}", "{http.vars.client_ip}",
|
||||
}
|
||||
}
|
||||
|
||||
// WasReplacedPlaceholderShorthand checks if a token string was
|
||||
// likely a replaced shorthand of the known Caddyfile placeholder
|
||||
// replacement outputs. Useful to prevent some user-defined map
|
||||
@@ -1556,9 +1564,8 @@ func (c counter) nextGroup() string {
|
||||
}
|
||||
|
||||
type namedCustomLog struct {
|
||||
name string
|
||||
hostnames []string
|
||||
log *caddy.CustomLog
|
||||
name string
|
||||
log *caddy.CustomLog
|
||||
}
|
||||
|
||||
// sbAddrAssociation is a mapping from a list of
|
||||
@@ -1569,10 +1576,8 @@ type sbAddrAssociation struct {
|
||||
serverBlocks []serverBlock
|
||||
}
|
||||
|
||||
const (
|
||||
matcherPrefix = "@"
|
||||
namedRouteKey = "named_route"
|
||||
)
|
||||
const matcherPrefix = "@"
|
||||
const namedRouteKey = "named_route"
|
||||
|
||||
// Interface guard
|
||||
var _ caddyfile.ServerType = (*ServerType)(nil)
|
||||
|
||||
@@ -17,13 +17,12 @@ package httpcaddyfile
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -174,6 +174,7 @@ func (st ServerType) buildPKIApp(
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
||||
|
||||
skipInstallTrust := false
|
||||
if _, ok := options["skip_install_trust"]; ok {
|
||||
skipInstallTrust = true
|
||||
|
||||
@@ -18,12 +18,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
// serverOptions collects server config overrides parsed from Caddyfile global options
|
||||
@@ -42,7 +41,6 @@ type serverOptions struct {
|
||||
IdleTimeout caddy.Duration
|
||||
KeepAliveInterval caddy.Duration
|
||||
MaxHeaderBytes int
|
||||
EnableFullDuplex bool
|
||||
Protocols []string
|
||||
StrictSNIHost *bool
|
||||
TrustedProxiesRaw json.RawMessage
|
||||
@@ -159,12 +157,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
serverOpts.MaxHeaderBytes = int(size)
|
||||
|
||||
case "enable_full_duplex":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
serverOpts.EnableFullDuplex = true
|
||||
|
||||
case "log_credentials":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
@@ -335,7 +327,6 @@ func applyServerOptions(
|
||||
server.IdleTimeout = opts.IdleTimeout
|
||||
server.KeepAliveInterval = opts.KeepAliveInterval
|
||||
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
||||
server.EnableFullDuplex = opts.EnableFullDuplex
|
||||
server.Protocols = opts.Protocols
|
||||
server.StrictSNIHost = opts.StrictSNIHost
|
||||
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
type ComplexShorthandReplacer struct {
|
||||
search *regexp.Regexp
|
||||
replace string
|
||||
}
|
||||
|
||||
type ShorthandReplacer struct {
|
||||
complex []ComplexShorthandReplacer
|
||||
simple *strings.Replacer
|
||||
}
|
||||
|
||||
func NewShorthandReplacer() ShorthandReplacer {
|
||||
// replace shorthand placeholders (which are convenient
|
||||
// when writing a Caddyfile) with their actual placeholder
|
||||
// identifiers or variable names
|
||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
||||
|
||||
// these are placeholders that allow a user-defined final
|
||||
// parameters, but we still want to provide a shorthand
|
||||
// for those, so we use a regexp to replace
|
||||
regexpReplacements := []ComplexShorthandReplacer{
|
||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
|
||||
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
|
||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
|
||||
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
|
||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||
}
|
||||
|
||||
return ShorthandReplacer{
|
||||
complex: regexpReplacements,
|
||||
simple: replacer,
|
||||
}
|
||||
}
|
||||
|
||||
// placeholderShorthands returns a slice of old-new string pairs,
|
||||
// where the left of the pair is a placeholder shorthand that may
|
||||
// be used in the Caddyfile, and the right is the replacement.
|
||||
func placeholderShorthands() []string {
|
||||
return []string{
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
"{client_ip}", "{http.vars.client_ip}",
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyToSegment replaces shorthand placeholder to its full placeholder, understandable by Caddy.
|
||||
func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) {
|
||||
if segment != nil {
|
||||
for i := 0; i < len(*segment); i++ {
|
||||
// simple string replacements
|
||||
(*segment)[i].Text = s.simple.Replace((*segment)[i].Text)
|
||||
// complex regexp replacements
|
||||
for _, r := range s.complex {
|
||||
(*segment)[i].Text = r.search.ReplaceAllString((*segment)[i].Text, r.replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func (st ServerType) buildTLSApp(
|
||||
@@ -37,6 +36,7 @@ func (st ServerType) buildTLSApp(
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
||||
|
||||
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
|
||||
var certLoaders []caddytls.CertificateLoader
|
||||
|
||||
@@ -582,7 +582,6 @@ outer:
|
||||
// eaten up by the one with subjects; and if both have subjects, we
|
||||
// need to combine their lists
|
||||
if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
|
||||
reflect.DeepEqual(aps[i].ManagersRaw, aps[j].ManagersRaw) &&
|
||||
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
|
||||
aps[i].MustStaple == aps[j].MustStaple &&
|
||||
aps[i].KeyType == aps[j].KeyType &&
|
||||
|
||||
@@ -30,14 +30,8 @@ func init() {
|
||||
caddy.RegisterModule(HTTPLoader{})
|
||||
}
|
||||
|
||||
// HTTPLoader can load Caddy configs over HTTP(S).
|
||||
//
|
||||
// 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
|
||||
// returned in the HTTP response from the server. The Content-Type header is
|
||||
// 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
|
||||
// the Content-Type header by setting the `adapter` property in this config.
|
||||
// HTTPLoader can load Caddy configs over HTTP(S). It can adapt the config
|
||||
// based on the Content-Type header of the HTTP response.
|
||||
type HTTPLoader struct {
|
||||
// The method for the request. Default: GET
|
||||
Method string `json:"method,omitempty"`
|
||||
@@ -51,11 +45,6 @@ type HTTPLoader struct {
|
||||
// Maximum time allowed for a complete connection and request.
|
||||
Timeout caddy.Duration `json:"timeout,omitempty"`
|
||||
|
||||
// The name of the config adapter to use, if any. Only needed
|
||||
// if the HTTP response is not a JSON config and if the server's
|
||||
// Content-Type header is missing or incorrect.
|
||||
Adapter string `json:"adapter,omitempty"`
|
||||
|
||||
TLS *struct {
|
||||
// Present this instance's managed remote identity credentials to the server.
|
||||
UseServerIdentity bool `json:"use_server_identity,omitempty"`
|
||||
@@ -119,12 +108,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// adapt the config based on either manually-configured adapter or server's response header
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if hl.Adapter != "" {
|
||||
ct = "text/" + hl.Adapter
|
||||
}
|
||||
result, warnings, err := adaptByContentType(ct, body)
|
||||
result, warnings, err := adaptByContentType(resp.Header.Get("Content-Type"), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+18
-3
@@ -22,10 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aryann/difflib"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
// plug in Caddy modules here
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
)
|
||||
@@ -64,6 +63,7 @@ type Tester struct {
|
||||
|
||||
// NewTester will create a new testing client with an attached cookie jar
|
||||
func NewTester(t *testing.T) *Tester {
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cookiejar: %s", err)
|
||||
@@ -94,6 +94,7 @@ func timeElapsed(start time.Time, name string) {
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
|
||||
if err := tc.initServer(rawConfig, configType); err != nil {
|
||||
tc.t.Logf("failed to load config: %s", err)
|
||||
tc.t.Fail()
|
||||
@@ -107,6 +108,7 @@ func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||
|
||||
if testing.Short() {
|
||||
tc.t.SkipNow()
|
||||
return nil
|
||||
@@ -230,6 +232,7 @@ const initConfig = `{
|
||||
// validateTestPrerequisites ensures the certificates are available in the
|
||||
// designated path and Caddy sub-process is running.
|
||||
func validateTestPrerequisites(t *testing.T) error {
|
||||
|
||||
// check certificates are found
|
||||
for _, certName := range Default.Certifcates {
|
||||
if _, err := os.Stat(getIntegrationDir() + certName); os.IsNotExist(err) {
|
||||
@@ -281,6 +284,7 @@ func isCaddyAdminRunning() error {
|
||||
}
|
||||
|
||||
func getIntegrationDir() string {
|
||||
|
||||
_, filename, _, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
panic("unable to determine the current file path")
|
||||
@@ -300,6 +304,7 @@ func prependCaddyFilePath(rawConfig string) string {
|
||||
|
||||
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
|
||||
func CreateTestingTransport() *http.Transport {
|
||||
|
||||
dialer := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 5 * time.Second,
|
||||
@@ -327,6 +332,7 @@ func CreateTestingTransport() *http.Transport {
|
||||
|
||||
// AssertLoadError will load a config and expect an error
|
||||
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
||||
|
||||
tc := NewTester(t)
|
||||
|
||||
err := tc.initServer(rawConfig, configType)
|
||||
@@ -337,6 +343,7 @@ func AssertLoadError(t *testing.T, rawConfig string, configType string, expected
|
||||
|
||||
// AssertRedirect makes a request and asserts the redirection happens
|
||||
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
|
||||
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
@@ -374,6 +381,7 @@ func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, e
|
||||
|
||||
// CompareAdapt adapts a config and then compares it against an expected result
|
||||
func CompareAdapt(t *testing.T, filename, rawConfig string, adapterName string, expectedResponse string) bool {
|
||||
|
||||
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
t.Logf("unrecognized config adapter '%s'", adapterName)
|
||||
@@ -461,13 +469,14 @@ func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
|
||||
|
||||
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
||||
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
||||
|
||||
resp, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("failed to call server %s", err)
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode)
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp
|
||||
@@ -475,6 +484,7 @@ func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int)
|
||||
|
||||
// AssertResponse request a URI and assert the status code and the body contains a string
|
||||
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -496,6 +506,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
||||
|
||||
// AssertGetResponse GET a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("GET", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
@@ -506,6 +517,7 @@ func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, e
|
||||
|
||||
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
@@ -516,6 +528,7 @@ func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int
|
||||
|
||||
// AssertPostResponseBody POST to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("POST", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
@@ -529,6 +542,7 @@ func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []str
|
||||
|
||||
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
@@ -542,6 +556,7 @@ func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []stri
|
||||
|
||||
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestACMEServerDirectory(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
local_certs
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
pki {
|
||||
ca local {
|
||||
name "Caddy Local Authority"
|
||||
}
|
||||
}
|
||||
}
|
||||
acme.localhost:9443 {
|
||||
acme_server
|
||||
}
|
||||
`, "caddyfile")
|
||||
tester.AssertGetResponse(
|
||||
"https://acme.localhost:9443/acme/local/directory",
|
||||
200,
|
||||
`{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"}
|
||||
`)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
:8443 {
|
||||
tls internal {
|
||||
on_demand
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8443"
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuers": [
|
||||
{
|
||||
"module": "internal"
|
||||
}
|
||||
],
|
||||
"on_demand": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ encode gzip zstd {
|
||||
header Content-Type application/xhtml+xml*
|
||||
header Content-Type application/atom+xml*
|
||||
header Content-Type application/rss+xml*
|
||||
header Content-Type application/wasm*
|
||||
header Content-Type image/svg+xml*
|
||||
}
|
||||
}
|
||||
@@ -48,7 +47,6 @@ encode {
|
||||
"application/xhtml+xml*",
|
||||
"application/atom+xml*",
|
||||
"application/rss+xml*",
|
||||
"application/wasm*",
|
||||
"image/svg+xml*"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -69,11 +69,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
},
|
||||
"disable_ocsp_stapling": true
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
},
|
||||
"ocsp_interval": 172800000000000,
|
||||
"renew_interval": 86400000000000,
|
||||
|
||||
@@ -71,11 +71,11 @@
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"ask": "https://example.com",
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
}
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
idle 30s
|
||||
}
|
||||
max_header_size 100MB
|
||||
enable_full_duplex
|
||||
log_credentials
|
||||
protocols h1 h2 h2c h3
|
||||
strict_sni_host
|
||||
@@ -46,7 +45,6 @@ foo.com {
|
||||
"write_timeout": 30000000000,
|
||||
"idle_timeout": 30000000000,
|
||||
"max_header_bytes": 100000000,
|
||||
"enable_full_duplex": true,
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
|
||||
@@ -99,7 +99,7 @@ http://localhost:2020 {
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": ""
|
||||
"localhost:2020": ""
|
||||
},
|
||||
"skip_unmapped_hosts": true
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
*.example.com {
|
||||
log {
|
||||
hostnames foo.example.com bar.example.com
|
||||
output file /foo-bar.txt
|
||||
}
|
||||
log {
|
||||
hostnames baz.example.com
|
||||
output file /baz.txt
|
||||
}
|
||||
}
|
||||
|
||||
example.com:8443 {
|
||||
log {
|
||||
output file /port.txt
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.log0",
|
||||
"http.log.access.log1",
|
||||
"http.log.access.log2"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"filename": "/foo-bar.txt",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log1": {
|
||||
"writer": {
|
||||
"filename": "/baz.txt",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log1"
|
||||
]
|
||||
},
|
||||
"log2": {
|
||||
"writer": {
|
||||
"filename": "/port.txt",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log2"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"bar.example.com": "log0",
|
||||
"baz.example.com": "log1",
|
||||
"foo.example.com": "log0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
":8443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"example.com": "log2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
log access-console {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.log
|
||||
format console
|
||||
}
|
||||
|
||||
log access-json {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.json
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
http://localhost:8881 {
|
||||
log foo
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"access-console": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"access-json": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
debug
|
||||
|
||||
log access-console {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.log
|
||||
format console
|
||||
}
|
||||
|
||||
log access-json {
|
||||
include http.log.access.foo
|
||||
output file access-localhost.json
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
http://localhost:8881 {
|
||||
log foo
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"access-console": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"level": "DEBUG",
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"access-json": {
|
||||
"writer": {
|
||||
"filename": "access-localhost.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"level": "DEBUG",
|
||||
"include": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"level": "DEBUG",
|
||||
"exclude": [
|
||||
"http.log.access.foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp first_label Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy {re.first_label.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "{http.regexp.first_label.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "first_label",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp port Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy app:6{re.port.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "app:6{http.regexp.port.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "port",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
*.sandbox.localhost {
|
||||
@sandboxPort {
|
||||
header_regexp port Host ^([0-9]{3})\.sandbox\.
|
||||
}
|
||||
handle @sandboxPort {
|
||||
reverse_proxy app:{re.port.1}
|
||||
}
|
||||
handle {
|
||||
redir {scheme}://application.localhost
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.sandbox.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "app:{http.regexp.port.1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"header_regexp": {
|
||||
"Host": {
|
||||
"name": "port",
|
||||
"pattern": "^([0-9]{3})\\.sandbox\\."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.scheme}://application.localhost"
|
||||
]
|
||||
},
|
||||
"status_code": 302
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
https://example.com {
|
||||
reverse_proxy https://localhost:54321 {
|
||||
request_buffers unlimited
|
||||
response_buffers unlimited
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"request_buffers": -1,
|
||||
"response_buffers": -1,
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
X-Header-Key 95ca39e3cbe7
|
||||
X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO
|
||||
X-Empty-Value
|
||||
Same-Key 1
|
||||
Same-Key 2
|
||||
X-System-Hostname {system.hostname}
|
||||
}
|
||||
health_uri /health
|
||||
}
|
||||
@@ -32,10 +29,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
"Host": [
|
||||
"example.com"
|
||||
],
|
||||
"Same-Key": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"X-Empty-Value": [
|
||||
""
|
||||
],
|
||||
@@ -45,9 +38,6 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
"X-Header-Keys": [
|
||||
"VbG4NZwWnipo",
|
||||
"335Q9/MhqcNU3s2TO"
|
||||
],
|
||||
"X-System-Hostname": [
|
||||
"{system.hostname}"
|
||||
]
|
||||
},
|
||||
"uri": "/health"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# example from https://caddy.community/t/21415
|
||||
a.com {
|
||||
tls {
|
||||
get_certificate http http://foo.com/get
|
||||
}
|
||||
}
|
||||
|
||||
b.com {
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"b.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"subjects": [
|
||||
"a.com"
|
||||
],
|
||||
"get_certificate": [
|
||||
{
|
||||
"url": "http://foo.com/get",
|
||||
"via": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"subjects": [
|
||||
"b.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,352 +135,3 @@ func TestReplIndex(t *testing.T) {
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "")
|
||||
}
|
||||
|
||||
func TestInvalidPrefix(t *testing.T) {
|
||||
type testCase struct {
|
||||
config, expectedError string
|
||||
}
|
||||
|
||||
failureCases := []testCase{
|
||||
{
|
||||
config: `wss://localhost`,
|
||||
expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`,
|
||||
},
|
||||
{
|
||||
config: `ws://localhost`,
|
||||
expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`,
|
||||
},
|
||||
{
|
||||
config: `someInvalidPrefix://localhost`,
|
||||
expectedError: "unsupported URL scheme someinvalidprefix://",
|
||||
},
|
||||
{
|
||||
config: `h2c://localhost`,
|
||||
expectedError: `unsupported URL scheme h2c://`,
|
||||
},
|
||||
{
|
||||
config: `localhost, wss://localhost`,
|
||||
expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`,
|
||||
},
|
||||
{
|
||||
config: `localhost {
|
||||
reverse_proxy ws://localhost"
|
||||
}`,
|
||||
expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`,
|
||||
},
|
||||
{
|
||||
config: `localhost {
|
||||
reverse_proxy someInvalidPrefix://localhost"
|
||||
}`,
|
||||
expectedError: `unsupported URL scheme someinvalidprefix://`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, failureCase := range failureCases {
|
||||
caddytest.AssertLoadError(t, failureCase.config, "caddyfile", failureCase.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidPrefix(t *testing.T) {
|
||||
type testCase struct {
|
||||
rawConfig, expectedResponse string
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
"localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
"https://localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
"http://localhost",
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy http://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy https://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy h2c://localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
`localhost {
|
||||
reverse_proxy localhost:3000
|
||||
}`,
|
||||
`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:3000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, successCase := range successCases {
|
||||
caddytest.AssertAdapt(t, successCase.rawConfig, "caddyfile", successCase.expectedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.Tester {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen: %s", err)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", handlerFunc)
|
||||
srv := &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
go srv.Serve(l)
|
||||
t.Cleanup(func() {
|
||||
_ = srv.Close()
|
||||
_ = l.Close()
|
||||
})
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(fmt.Sprintf(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
local_certs
|
||||
servers :9443 {
|
||||
listener_wrappers {
|
||||
http_redirect
|
||||
tls
|
||||
}
|
||||
}
|
||||
}
|
||||
localhost {
|
||||
reverse_proxy %s
|
||||
}
|
||||
`, l.Addr().String()), "caddyfile")
|
||||
return tester
|
||||
}
|
||||
|
||||
func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
|
||||
const uploadSize = (1024 * 1024) + 1 // 1 MB + 1 byte
|
||||
// 1 more than an MB
|
||||
body := make([]byte, uploadSize)
|
||||
rand.New(rand.NewSource(0)).Read(body)
|
||||
|
||||
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(request.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read body: %s", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf.Bytes(), body) {
|
||||
t.Fatalf("body not the same")
|
||||
}
|
||||
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
resp, err := tester.Client.Post("https://localhost:9443", "application/octet-stream", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to post: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("unexpected status: %d != %d", resp.StatusCode, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLargeHttpRequest(t *testing.T) {
|
||||
tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) {
|
||||
t.Fatal("not supposed to handle a request")
|
||||
})
|
||||
|
||||
// We never read the body in any way, set an extra long header instead.
|
||||
req, _ := http.NewRequest("POST", "http://localhost:9443", nil)
|
||||
req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024))
|
||||
_, err := tester.Client.Do(req)
|
||||
if err == nil {
|
||||
t.Fatal("not supposed to succeed")
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,9 @@ func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(200)
|
||||
http.NewResponseController(w).Flush()
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
buf := make([]byte, 4*1024)
|
||||
|
||||
|
||||
+1
-29
@@ -1,11 +1,7 @@
|
||||
package caddycmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -99,22 +95,15 @@ https://caddyserver.com/docs/running
|
||||
// kind of annoying to have all the help text printed out if
|
||||
// caddy has an error provisioning its modules, for instance...
|
||||
SilenceUsage: true,
|
||||
Version: onlyVersionText(),
|
||||
}
|
||||
|
||||
const fullDocsFooter = `Full documentation is available at:
|
||||
https://caddyserver.com/docs/command-line`
|
||||
|
||||
func init() {
|
||||
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
||||
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
||||
}
|
||||
|
||||
func onlyVersionText() string {
|
||||
_, f := caddy.Version()
|
||||
return f
|
||||
}
|
||||
|
||||
func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: caddyCmd.Name,
|
||||
@@ -134,24 +123,7 @@ func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
|
||||
// in a cobra command's RunE field.
|
||||
func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error {
|
||||
return func(cmd *cobra.Command, _ []string) error {
|
||||
status, err := f(Flags{cmd.Flags()})
|
||||
if status > 1 {
|
||||
cmd.SilenceErrors = true
|
||||
return &exitError{ExitCode: status, Err: err}
|
||||
}
|
||||
_, err := f(Flags{cmd.Flags()})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// exitError carries the exit code from CommandFunc to Main()
|
||||
type exitError struct {
|
||||
ExitCode int
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *exitError) Error() string {
|
||||
if e.Err == nil {
|
||||
return fmt.Sprintf("exiting with status %d", e.ExitCode)
|
||||
}
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
+110
-184
@@ -22,7 +22,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -33,27 +32,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/aryann/difflib"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func cmdStart(fl Flags) (int, error) {
|
||||
configFlag := fl.String("config")
|
||||
configAdapterFlag := fl.String("adapter")
|
||||
pidfileFlag := fl.String("pidfile")
|
||||
watchFlag := fl.Bool("watch")
|
||||
|
||||
var err error
|
||||
var envfileFlag []string
|
||||
envfileFlag, err = fl.GetStringSlice("envfile")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("reading envfile flag: %v", err)
|
||||
}
|
||||
startCmdConfigFlag := fl.String("config")
|
||||
startCmdConfigAdapterFlag := fl.String("adapter")
|
||||
startCmdPidfileFlag := fl.String("pidfile")
|
||||
startCmdWatchFlag := fl.Bool("watch")
|
||||
startCmdEnvfileFlag := fl.String("envfile")
|
||||
|
||||
// open a listener to which the child process will connect when
|
||||
// it is ready to confirm that it has successfully started
|
||||
@@ -74,23 +64,22 @@ 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())
|
||||
if configFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--config", configFlag)
|
||||
if startCmdConfigFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag)
|
||||
}
|
||||
|
||||
for _, envfile := range envfileFlag {
|
||||
cmd.Args = append(cmd.Args, "--envfile", envfile)
|
||||
if startCmdEnvfileFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--envfile", startCmdEnvfileFlag)
|
||||
}
|
||||
if configAdapterFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--adapter", configAdapterFlag)
|
||||
if startCmdConfigAdapterFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag)
|
||||
}
|
||||
if watchFlag {
|
||||
if startCmdWatchFlag {
|
||||
cmd.Args = append(cmd.Args, "--watch")
|
||||
}
|
||||
if pidfileFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--pidfile", pidfileFlag)
|
||||
if startCmdPidfileFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag)
|
||||
}
|
||||
stdinPipe, err := cmd.StdinPipe()
|
||||
stdinpipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("creating stdin pipe: %v", err)
|
||||
@@ -102,8 +91,7 @@ func cmdStart(fl Flags) (int, error) {
|
||||
expect := make([]byte, 32)
|
||||
_, err = rand.Read(expect)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("generating random confirmation bytes: %v", err)
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err)
|
||||
}
|
||||
|
||||
// begin writing the confirmation bytes to the child's
|
||||
@@ -111,15 +99,14 @@ func cmdStart(fl Flags) (int, error) {
|
||||
// started yet, and writing synchronously would result
|
||||
// in a deadlock
|
||||
go func() {
|
||||
_, _ = stdinPipe.Write(expect)
|
||||
stdinPipe.Close()
|
||||
_, _ = stdinpipe.Write(expect)
|
||||
stdinpipe.Close()
|
||||
}()
|
||||
|
||||
// start the process
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("starting caddy process: %v", err)
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err)
|
||||
}
|
||||
|
||||
// there are two ways we know we're done: either
|
||||
@@ -167,37 +154,41 @@ func cmdStart(fl Flags) (int, error) {
|
||||
func cmdRun(fl Flags) (int, error) {
|
||||
caddy.TrapSignals()
|
||||
|
||||
configFlag := fl.String("config")
|
||||
configAdapterFlag := fl.String("adapter")
|
||||
resumeFlag := fl.Bool("resume")
|
||||
printEnvFlag := fl.Bool("environ")
|
||||
watchFlag := fl.Bool("watch")
|
||||
pidfileFlag := fl.String("pidfile")
|
||||
pingbackFlag := fl.String("pingback")
|
||||
runCmdConfigFlag := fl.String("config")
|
||||
runCmdConfigAdapterFlag := fl.String("adapter")
|
||||
runCmdResumeFlag := fl.Bool("resume")
|
||||
runCmdLoadEnvfileFlag := fl.String("envfile")
|
||||
runCmdPrintEnvFlag := fl.Bool("environ")
|
||||
runCmdWatchFlag := fl.Bool("watch")
|
||||
runCmdPidfileFlag := fl.String("pidfile")
|
||||
runCmdPingbackFlag := fl.String("pingback")
|
||||
|
||||
// load all additional envs as soon as possible
|
||||
err := handleEnvFileFlag(fl)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
if runCmdLoadEnvfileFlag != "" {
|
||||
if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("loading additional environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if we are supposed to print the environment, do that first
|
||||
if printEnvFlag {
|
||||
if runCmdPrintEnvFlag {
|
||||
printEnvironment()
|
||||
}
|
||||
|
||||
// load the config, depending on flags
|
||||
var config []byte
|
||||
if resumeFlag {
|
||||
var err error
|
||||
if runCmdResumeFlag {
|
||||
config, err = os.ReadFile(caddy.ConfigAutosavePath)
|
||||
if os.IsNotExist(err) {
|
||||
// not a bad error; just can't resume if autosave file doesn't exist
|
||||
caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||
resumeFlag = false
|
||||
runCmdResumeFlag = false
|
||||
} else if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
} else {
|
||||
if configFlag == "" {
|
||||
if runCmdConfigFlag == "" {
|
||||
caddy.Log().Info("resuming from last configuration",
|
||||
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||
} else {
|
||||
@@ -210,19 +201,19 @@ func cmdRun(fl Flags) (int, error) {
|
||||
}
|
||||
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
|
||||
var configFile string
|
||||
if !resumeFlag {
|
||||
config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
|
||||
if !runCmdResumeFlag {
|
||||
config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
}
|
||||
|
||||
// create pidfile now, in case loading config takes a while (issue #5477)
|
||||
if pidfileFlag != "" {
|
||||
err := caddy.PIDFile(pidfileFlag)
|
||||
if runCmdPidfileFlag != "" {
|
||||
err := caddy.PIDFile(runCmdPidfileFlag)
|
||||
if err != nil {
|
||||
caddy.Log().Error("unable to write PID file",
|
||||
zap.String("pidfile", pidfileFlag),
|
||||
zap.String("pidfile", runCmdPidfileFlag),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -236,13 +227,13 @@ func cmdRun(fl Flags) (int, error) {
|
||||
|
||||
// if we are to report to another process the successful start
|
||||
// of the server, do so now by echoing back contents of stdin
|
||||
if pingbackFlag != "" {
|
||||
if runCmdPingbackFlag != "" {
|
||||
confirmationBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("reading confirmation bytes from stdin: %v", err)
|
||||
}
|
||||
conn, err := net.Dial("tcp", pingbackFlag)
|
||||
conn, err := net.Dial("tcp", runCmdPingbackFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("dialing confirmation address: %v", err)
|
||||
@@ -251,14 +242,14 @@ func cmdRun(fl Flags) (int, error) {
|
||||
_, err = conn.Write(confirmationBytes)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("writing confirmation bytes to %s: %v", pingbackFlag, err)
|
||||
fmt.Errorf("writing confirmation bytes to %s: %v", runCmdPingbackFlag, err)
|
||||
}
|
||||
}
|
||||
|
||||
// if enabled, reload config file automatically on changes
|
||||
// (this better only be used in dev!)
|
||||
if watchFlag {
|
||||
go watchConfigFile(configFile, configAdapterFlag)
|
||||
if runCmdWatchFlag {
|
||||
go watchConfigFile(configFile, runCmdConfigAdapterFlag)
|
||||
}
|
||||
|
||||
// warn if the environment does not provide enough information about the disk
|
||||
@@ -284,11 +275,11 @@ func cmdRun(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
func cmdStop(fl Flags) (int, error) {
|
||||
addressFlag := fl.String("address")
|
||||
addrFlag := fl.String("address")
|
||||
configFlag := fl.String("config")
|
||||
configAdapterFlag := fl.String("adapter")
|
||||
|
||||
adminAddr, err := DetermineAdminAPIAddress(addressFlag, nil, configFlag, configAdapterFlag)
|
||||
adminAddr, err := DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
|
||||
}
|
||||
@@ -306,7 +297,7 @@ func cmdStop(fl Flags) (int, error) {
|
||||
func cmdReload(fl Flags) (int, error) {
|
||||
configFlag := fl.String("config")
|
||||
configAdapterFlag := fl.String("adapter")
|
||||
addressFlag := fl.String("address")
|
||||
addrFlag := fl.String("address")
|
||||
forceFlag := fl.Bool("force")
|
||||
|
||||
// get the config in caddy's native format
|
||||
@@ -318,7 +309,7 @@ func cmdReload(fl Flags) (int, error) {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
|
||||
}
|
||||
|
||||
adminAddr, err := DetermineAdminAPIAddress(addressFlag, config, configFlag, configAdapterFlag)
|
||||
adminAddr, err := DetermineAdminAPIAddress(addrFlag, config, configFlag, configAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
|
||||
}
|
||||
@@ -420,60 +411,60 @@ func cmdListModules(fl Flags) (int, error) {
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
func cmdEnviron(fl Flags) (int, error) {
|
||||
// load all additional envs as soon as possible
|
||||
err := handleEnvFileFlag(fl)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
func cmdEnviron(_ Flags) (int, error) {
|
||||
printEnvironment()
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
func cmdAdaptConfig(fl Flags) (int, error) {
|
||||
inputFlag := fl.String("config")
|
||||
adapterFlag := fl.String("adapter")
|
||||
prettyFlag := fl.Bool("pretty")
|
||||
validateFlag := fl.Bool("validate")
|
||||
adaptCmdInputFlag := fl.String("config")
|
||||
adaptCmdAdapterFlag := fl.String("adapter")
|
||||
adaptCmdPrettyFlag := fl.Bool("pretty")
|
||||
adaptCmdValidateFlag := fl.Bool("validate")
|
||||
|
||||
var err error
|
||||
inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
// if no input file was specified, try a default
|
||||
// Caddyfile if the Caddyfile adapter is plugged in
|
||||
if adaptCmdInputFlag == "" && caddyconfig.GetAdapter("caddyfile") != nil {
|
||||
_, err := os.Stat("Caddyfile")
|
||||
if err == nil {
|
||||
// default Caddyfile exists
|
||||
adaptCmdInputFlag = "Caddyfile"
|
||||
caddy.Log().Info("using adjacent Caddyfile")
|
||||
} else if !os.IsNotExist(err) {
|
||||
// default Caddyfile exists, but error accessing it
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing default Caddyfile: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// load all additional envs as soon as possible
|
||||
err = handleEnvFileFlag(fl)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
if adaptCmdInputFlag == "" {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
|
||||
}
|
||||
|
||||
if adapterFlag == "" {
|
||||
if adaptCmdAdapterFlag == "" {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("adapter name is required (use --adapt flag or leave unspecified for default)")
|
||||
}
|
||||
|
||||
cfgAdapter := caddyconfig.GetAdapter(adapterFlag)
|
||||
cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag)
|
||||
if cfgAdapter == nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("unrecognized config adapter: %s", adapterFlag)
|
||||
fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag)
|
||||
}
|
||||
|
||||
input, err := os.ReadFile(inputFlag)
|
||||
input, err := os.ReadFile(adaptCmdInputFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("reading input file: %v", err)
|
||||
}
|
||||
|
||||
opts := map[string]any{"filename": inputFlag}
|
||||
opts := map[string]any{"filename": adaptCmdInputFlag}
|
||||
|
||||
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
if prettyFlag {
|
||||
if adaptCmdPrettyFlag {
|
||||
var prettyBuf bytes.Buffer
|
||||
err = json.Indent(&prettyBuf, adaptedConfig, "", "\t")
|
||||
if err != nil {
|
||||
@@ -491,13 +482,13 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
||||
if warn.Directive != "" {
|
||||
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
||||
}
|
||||
caddy.Log().Named(adapterFlag).Warn(msg,
|
||||
caddy.Log().Named(adaptCmdAdapterFlag).Warn(msg,
|
||||
zap.String("file", warn.File),
|
||||
zap.Int("line", warn.Line))
|
||||
}
|
||||
|
||||
// validate output if requested
|
||||
if validateFlag {
|
||||
if adaptCmdValidateFlag {
|
||||
var cfg *caddy.Config
|
||||
err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg)
|
||||
if err != nil {
|
||||
@@ -513,26 +504,19 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
func cmdValidateConfig(fl Flags) (int, error) {
|
||||
configFlag := fl.String("config")
|
||||
adapterFlag := fl.String("adapter")
|
||||
validateCmdConfigFlag := fl.String("config")
|
||||
validateCmdAdapterFlag := fl.String("adapter")
|
||||
runCmdLoadEnvfileFlag := fl.String("envfile")
|
||||
|
||||
// load all additional envs as soon as possible
|
||||
err := handleEnvFileFlag(fl)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
if runCmdLoadEnvfileFlag != "" {
|
||||
if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("loading additional environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// use default config and ensure a config file is specified
|
||||
configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
if configFlag == "" {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
|
||||
}
|
||||
|
||||
input, _, err := LoadConfig(configFlag, adapterFlag)
|
||||
input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
@@ -555,13 +539,13 @@ func cmdValidateConfig(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
func cmdFmt(fl Flags) (int, error) {
|
||||
configFile := fl.Arg(0)
|
||||
if configFile == "" {
|
||||
configFile = "Caddyfile"
|
||||
formatCmdConfigFile := fl.Arg(0)
|
||||
if formatCmdConfigFile == "" {
|
||||
formatCmdConfigFile = "Caddyfile"
|
||||
}
|
||||
|
||||
// as a special case, read from stdin if the file name is "-"
|
||||
if configFile == "-" {
|
||||
if formatCmdConfigFile == "-" {
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
@@ -571,7 +555,7 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
input, err := os.ReadFile(configFile)
|
||||
input, err := os.ReadFile(formatCmdConfigFile)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("reading input file: %v", err)
|
||||
@@ -580,7 +564,7 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
output := caddyfile.Format(input)
|
||||
|
||||
if fl.Bool("overwrite") {
|
||||
if err := os.WriteFile(configFile, output, 0o600); err != nil {
|
||||
if err := os.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err)
|
||||
}
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
@@ -604,7 +588,7 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
fmt.Print(string(output))
|
||||
}
|
||||
|
||||
if warning, diff := caddyfile.FormattingDifference(configFile, input); diff {
|
||||
if warning, diff := caddyfile.FormattingDifference(formatCmdConfigFile, input); diff {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf(`%s:%d: Caddyfile input is not formatted; Tip: use '--overwrite' to update your Caddyfile in-place instead of previewing it. Consult '--help' for more options`,
|
||||
warning.File,
|
||||
warning.Line,
|
||||
@@ -614,25 +598,6 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
// handleEnvFileFlag loads the environment variables from the given --envfile
|
||||
// flag if specified. This should be called as early in the command function.
|
||||
func handleEnvFileFlag(fl Flags) error {
|
||||
var err error
|
||||
var envfileFlag []string
|
||||
envfileFlag, err = fl.GetStringSlice("envfile")
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading envfile flag: %v", err)
|
||||
}
|
||||
|
||||
for _, envfile := range envfileFlag {
|
||||
if err := loadEnvFromFile(envfile); err != nil {
|
||||
return fmt.Errorf("loading additional environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminAPIRequest makes an API request according to the CLI flags given,
|
||||
// with the given HTTP method and request URI. If body is non-nil, it will
|
||||
// be assumed to be Content-Type application/json. The caller should close
|
||||
@@ -645,17 +610,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
||||
}
|
||||
origin := "http://" + parsedAddr.JoinHostPort(0)
|
||||
if parsedAddr.IsUnixNetwork() {
|
||||
origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy
|
||||
|
||||
// the unix address at this point might still contain the optional
|
||||
// unix socket permissions, which are part of the address/host.
|
||||
// those need to be removed first, as they aren't part of the
|
||||
// resulting unix file path
|
||||
addr, _, err := internal.SplitUnixSocketPermissionsBits(parsedAddr.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedAddr.Host = addr
|
||||
origin = "http://unixsocket" // hack so that http.NewRequest() is happy
|
||||
}
|
||||
|
||||
// form the request
|
||||
@@ -664,24 +619,20 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
||||
return nil, fmt.Errorf("making request: %v", err)
|
||||
}
|
||||
if parsedAddr.IsUnixNetwork() {
|
||||
// We used to conform to RFC 2616 Section 14.26 which requires
|
||||
// an empty host header when there is no host, as is the case
|
||||
// with unix sockets. However, Go required a Host value so we
|
||||
// used a hack of a space character as the host (it would see
|
||||
// the Host was non-empty, then trim the space later). As of
|
||||
// Go 1.20.6 (July 2023), this hack no longer works. See:
|
||||
// https://github.com/golang/go/issues/60374
|
||||
// See also the discussion here:
|
||||
// https://github.com/golang/go/issues/61431
|
||||
// When listening on a unix socket, the admin endpoint doesn't
|
||||
// accept any Host header because there is no host:port for
|
||||
// a unix socket's address. The server's host check is fairly
|
||||
// strict for security reasons, so we don't allow just any
|
||||
// Host header. For unix sockets, the Host header must be
|
||||
// empty. Unfortunately, Go makes it impossible to make HTTP
|
||||
// requests with an empty Host header... except with this one
|
||||
// weird trick. (Hopefully they don't fix it. It's already
|
||||
// hard enough to use HTTP over unix sockets.)
|
||||
//
|
||||
// After that, we now require a Host value of either 127.0.0.1
|
||||
// or ::1 if one is set. Above I choose to use 127.0.0.1. Even
|
||||
// though the value should be completely irrelevant (it could be
|
||||
// "srldkjfsd"), if for some reason the Host *is* used, at least
|
||||
// we can have some reasonable assurance it will stay on the local
|
||||
// machine and that browsers, if they ever allow access to unix
|
||||
// sockets, can still enforce CORS, ensuring it is still coming
|
||||
// from the local machine.
|
||||
// An equivalent curl command would be something like:
|
||||
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI
|
||||
req.URL.Host = " "
|
||||
req.Host = ""
|
||||
} else {
|
||||
req.Header.Set("Origin", origin)
|
||||
}
|
||||
@@ -770,31 +721,6 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA
|
||||
return caddy.DefaultAdminListen, nil
|
||||
}
|
||||
|
||||
// configFileWithRespectToDefault returns the filename to use for loading the config, based
|
||||
// on whether a config file is already specified and a supported default config file exists.
|
||||
func configFileWithRespectToDefault(logger *zap.Logger, configFile string) (string, error) {
|
||||
const defaultCaddyfile = "Caddyfile"
|
||||
|
||||
// if no input file was specified, try a default Caddyfile if the Caddyfile adapter is plugged in
|
||||
if configFile == "" && caddyconfig.GetAdapter("caddyfile") != nil {
|
||||
_, err := os.Stat(defaultCaddyfile)
|
||||
if err == nil {
|
||||
// default Caddyfile exists
|
||||
if logger != nil {
|
||||
logger.Info("using adjacent Caddyfile")
|
||||
}
|
||||
return defaultCaddyfile, nil
|
||||
}
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
// problem checking
|
||||
return configFile, fmt.Errorf("checking if default Caddyfile exists: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// default config file does not exist or is irrelevant
|
||||
return configFile, nil
|
||||
}
|
||||
|
||||
type moduleInfo struct {
|
||||
caddyModuleID string
|
||||
goModule *debug.Module
|
||||
|
||||
+30
-42
@@ -21,10 +21,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
// Command represents a subcommand. Name, Func,
|
||||
@@ -94,8 +93,8 @@ func init() {
|
||||
Starts the Caddy process, optionally bootstrapped with an initial config file.
|
||||
This command unblocks after the server starts running or fails to run.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables
|
||||
in the KEY=VALUE format will be loaded into the Caddy process.
|
||||
If --envfile is specified, an environment file with environment variables in
|
||||
the KEY=VALUE format will be loaded into the Caddy process.
|
||||
|
||||
On Windows, the spawned child process will remain attached to the terminal, so
|
||||
closing the window will forcefully stop Caddy; to avoid forgetting this, try
|
||||
@@ -104,7 +103,7 @@ using 'caddy run' instead to keep it in the foreground.
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("config", "c", "", "Configuration file")
|
||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply")
|
||||
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
|
||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
||||
cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically")
|
||||
cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID")
|
||||
cmd.RunE = WrapCommandFuncForCobra(cmdStart)
|
||||
@@ -133,8 +132,8 @@ As a special case, if the current working directory has a file called
|
||||
that file will be loaded and used to configure Caddy, even without any command
|
||||
line flags.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables
|
||||
in the KEY=VALUE format will be loaded into the Caddy process.
|
||||
If --envfile is specified, an environment file with environment variables in
|
||||
the KEY=VALUE format will be loaded into the Caddy process.
|
||||
|
||||
If --environ is specified, the environment as seen by the Caddy process will
|
||||
be printed before starting. This is the same as the environ command but does
|
||||
@@ -150,7 +149,7 @@ option in a local development environment.
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("config", "c", "", "Configuration file")
|
||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply")
|
||||
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
|
||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
||||
cmd.Flags().BoolP("environ", "e", false, "Print environment")
|
||||
cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)")
|
||||
cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically")
|
||||
@@ -240,7 +239,6 @@ documentation: https://go.dev/doc/modules/version-numbers
|
||||
|
||||
RegisterCommand(Command{
|
||||
Name: "environ",
|
||||
Usage: "[--envfile <path>]",
|
||||
Short: "Prints the environment",
|
||||
Long: `
|
||||
Prints the environment as seen by this Caddy process.
|
||||
@@ -250,9 +248,6 @@ configuration uses environment variables (e.g. "{env.VARIABLE}") then
|
||||
this command can be useful for verifying that the variables will have
|
||||
the values you expect in your config.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables
|
||||
in the KEY=VALUE format will be loaded into the Caddy process.
|
||||
|
||||
Note that environments may be different depending on how you run Caddy.
|
||||
Environments for Caddy instances started by service managers such as
|
||||
systemd are often different than the environment inherited from your
|
||||
@@ -263,15 +258,12 @@ by adding the "--environ" flag.
|
||||
|
||||
Environments may contain sensitive data.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
|
||||
cmd.RunE = WrapCommandFuncForCobra(cmdEnviron)
|
||||
},
|
||||
Func: cmdEnviron,
|
||||
})
|
||||
|
||||
RegisterCommand(Command{
|
||||
Name: "adapt",
|
||||
Usage: "--config <path> [--adapter <name>] [--pretty] [--validate] [--envfile <path>]",
|
||||
Usage: "--config <path> [--adapter <name>] [--pretty] [--validate]",
|
||||
Short: "Adapts a configuration to Caddy's native JSON",
|
||||
Long: `
|
||||
Adapts a configuration to Caddy's native JSON format and writes the
|
||||
@@ -283,16 +275,12 @@ for human readability.
|
||||
If --validate is used, the adapted config will be checked for validity.
|
||||
If the config is invalid, an error will be printed to stderr and a non-
|
||||
zero exit status will be returned.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables
|
||||
in the KEY=VALUE format will be loaded into the Caddy process.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)")
|
||||
cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter")
|
||||
cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability")
|
||||
cmd.Flags().BoolP("validate", "", false, "Validate the output")
|
||||
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
|
||||
cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig)
|
||||
},
|
||||
})
|
||||
@@ -306,13 +294,13 @@ Loads and provisions the provided config, but does not start running it.
|
||||
This reveals any errors with the configuration through the loading and
|
||||
provisioning stages.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables
|
||||
in the KEY=VALUE format will be loaded into the Caddy process.
|
||||
If --envfile is specified, an environment file with environment variables in
|
||||
the KEY=VALUE format will be loaded into the Caddy process.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("config", "c", "", "Input configuration file")
|
||||
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter")
|
||||
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
|
||||
cmd.Flags().StringP("envfile", "", "", "Environment file to load")
|
||||
cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig)
|
||||
},
|
||||
})
|
||||
@@ -413,7 +401,7 @@ latest versions. EXPERIMENTAL: May be changed or removed.
|
||||
Short: "Adds Caddy packages (EXPERIMENTAL)",
|
||||
Long: `
|
||||
Downloads an updated Caddy binary with the specified packages (module/plugin)
|
||||
added. Retains existing packages. Returns an error if the any of packages are
|
||||
added. Retains existing packages. Returns an error if the any of packages are
|
||||
already included. EXPERIMENTAL: May be changed or removed.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
@@ -428,8 +416,8 @@ already included. EXPERIMENTAL: May be changed or removed.
|
||||
Usage: "<packages...>",
|
||||
Short: "Removes Caddy packages (EXPERIMENTAL)",
|
||||
Long: `
|
||||
Downloads an updated Caddy binaries without the specified packages (module/plugin).
|
||||
Returns an error if any of the packages are not included.
|
||||
Downloads an updated Caddy binaries without the specified packages (module/plugin).
|
||||
Returns an error if any of the packages are not included.
|
||||
EXPERIMENTAL: May be changed or removed.
|
||||
`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
@@ -456,7 +444,7 @@ argument of --directory. If the directory does not exist, it will be created.
|
||||
if dir == "" {
|
||||
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return caddy.ExitCodeFailedQuit, err
|
||||
}
|
||||
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
|
||||
@@ -475,40 +463,40 @@ argument of --directory. If the directory does not exist, it will be created.
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate completion script",
|
||||
Long: fmt.Sprintf(`To load completions:
|
||||
|
||||
|
||||
Bash:
|
||||
|
||||
|
||||
$ source <(%[1]s completion bash)
|
||||
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
# Linux:
|
||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
||||
# macOS:
|
||||
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
||||
|
||||
|
||||
Zsh:
|
||||
|
||||
|
||||
# If shell completion is not already enabled in your environment,
|
||||
# you will need to enable it. You can execute the following once:
|
||||
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
||||
|
||||
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
|
||||
|
||||
fish:
|
||||
|
||||
|
||||
$ %[1]s completion fish | source
|
||||
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
||||
|
||||
|
||||
PowerShell:
|
||||
|
||||
|
||||
PS> %[1]s completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
|
||||
# To load completions for every new session, run:
|
||||
PS> %[1]s completion powershell > %[1]s.ps1
|
||||
# and source this file from your PowerShell profile.
|
||||
|
||||
+7
-16
@@ -17,7 +17,6 @@ package caddycmd
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -31,12 +30,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/spf13/pflag"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -64,10 +62,6 @@ func Main() {
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
var exitError *exitError
|
||||
if errors.As(err, &exitError) {
|
||||
os.Exit(exitError.ExitCode)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -123,8 +117,9 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
||||
zap.String("config_adapter", adapterName))
|
||||
}
|
||||
} else if adapterName == "" {
|
||||
// if the Caddyfile adapter is plugged in, we can try using an
|
||||
// adjacent Caddyfile by default
|
||||
// as a special case when no config file or adapter
|
||||
// is specified, see if the Caddyfile adapter is
|
||||
// plugged in, and if so, try using a default Caddyfile
|
||||
cfgAdapter = caddyconfig.GetAdapter("caddyfile")
|
||||
if cfgAdapter != nil {
|
||||
config, err = os.ReadFile("Caddyfile")
|
||||
@@ -305,12 +300,8 @@ func loadEnvFromFile(envFile string) error {
|
||||
}
|
||||
|
||||
for k, v := range envMap {
|
||||
// do not overwrite existing environment variables
|
||||
_, exists := os.LookupEnv(k)
|
||||
if !exists {
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return fmt.Errorf("setting environment variables: %v", err)
|
||||
}
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return fmt.Errorf("setting environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -22,15 +22,13 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func cmdUpgrade(fl Flags) (int, error) {
|
||||
@@ -104,15 +102,6 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("retrieving current executable permission bits: %v", err)
|
||||
}
|
||||
if thisExecStat.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
symSource := thisExecPath
|
||||
// we are a symlink; resolve it
|
||||
thisExecPath, err = filepath.EvalSymlinks(thisExecPath)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("resolving current executable symlink: %v", err)
|
||||
}
|
||||
l.Info("this executable is a symlink", zap.String("source", symSource), zap.String("target", thisExecPath))
|
||||
}
|
||||
l.Info("this executable will be replaced", zap.String("path", thisExecPath))
|
||||
|
||||
// build the request URL to download this custom build
|
||||
@@ -185,6 +174,13 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
func getModPkgPath(iface any) string {
|
||||
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
|
||||
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
|
||||
}
|
||||
return reflect.TypeOf(iface).PkgPath()
|
||||
}
|
||||
|
||||
func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
@@ -206,10 +202,15 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
|
||||
// not sure why), and since New() should return a pointer
|
||||
// value, we need to dereference it first
|
||||
iface := any(modInfo.New())
|
||||
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
|
||||
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
|
||||
modPkgPath := getModPkgPath(iface)
|
||||
|
||||
// Unwrap config adapters to get the underlying adapter modules, as config adapter modules are hacks anyway. https://github.com/caddyserver/caddy/issues/5621
|
||||
// this method will only be called if it's from the built-in module to prevent abuse
|
||||
if strings.HasPrefix(modPkgPath, caddy.ImportPath) {
|
||||
if unwrapper, ok := iface.(interface{ UnwrapAdapter() any }); ok {
|
||||
modPkgPath = getModPkgPath(unwrapper.UnwrapAdapter())
|
||||
}
|
||||
}
|
||||
modPkgPath := reflect.TypeOf(iface).PkgPath()
|
||||
|
||||
// now we find the Go module that the Caddy module's package
|
||||
// belongs to; we assume the Caddy module package path will
|
||||
|
||||
+2
-3
@@ -23,9 +23,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
type storVal struct {
|
||||
@@ -201,7 +200,7 @@ func cmdExportStorage(fl Flags) (int, error) {
|
||||
|
||||
hdr := &tar.Header{
|
||||
Name: k,
|
||||
Mode: 0o600,
|
||||
Mode: 0600,
|
||||
Size: int64(len(v)),
|
||||
}
|
||||
|
||||
|
||||
+11
-19
@@ -410,11 +410,6 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
|
||||
// called during the Provision/Validate phase to reference a
|
||||
// module's own host app (since the parent app module is still
|
||||
// in the process of being provisioned, it is not yet ready).
|
||||
//
|
||||
// We return any type instead of the App type because it is NOT
|
||||
// intended for the caller of this method to be the one to start
|
||||
// or stop App modules. The caller is expected to assert to the
|
||||
// concrete type.
|
||||
func (ctx Context) App(name string) (any, error) {
|
||||
if app, ok := ctx.cfg.apps[name]; ok {
|
||||
return app, nil
|
||||
@@ -433,21 +428,18 @@ func (ctx Context) App(name string) (any, error) {
|
||||
|
||||
// AppIfConfigured returns an app by its name if it has been
|
||||
// configured. Can be called instead of App() to avoid
|
||||
// instantiating an empty app when that's not desirable. If
|
||||
// the app has not been loaded, nil is returned.
|
||||
//
|
||||
// We return any type instead of the App type because it is not
|
||||
// intended for the caller of this method to be the one to start
|
||||
// or stop App modules. The caller is expected to assert to the
|
||||
// concrete type.
|
||||
func (ctx Context) AppIfConfigured(name string) any {
|
||||
if ctx.cfg == nil {
|
||||
// this can happen if the currently-active context
|
||||
// is being accessed, but no config has successfully
|
||||
// been loaded yet
|
||||
return nil
|
||||
// instantiating an empty app when that's not desirable.
|
||||
func (ctx Context) AppIfConfigured(name string) (any, error) {
|
||||
app, ok := ctx.cfg.apps[name]
|
||||
if !ok || app == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return ctx.cfg.apps[name]
|
||||
|
||||
appModule, err := ctx.App(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appModule, nil
|
||||
}
|
||||
|
||||
// Storage returns the configured Caddy storage implementation.
|
||||
|
||||
@@ -1,73 +1,71 @@
|
||||
module github.com/caddyserver/caddy/v2
|
||||
|
||||
go 1.20
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/alecthomas/chroma/v2 v2.7.0
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/caddyserver/certmagic v0.18.2
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/google/cel-go v0.15.1
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/klauspost/compress v1.17.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/klauspost/compress v1.16.6
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/mastercactapus/proxyprotocol v0.0.4
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/quic-go/quic-go v0.40.0
|
||||
github.com/smallstep/certificates v0.25.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/quic-go/quic-go v0.36.0
|
||||
github.com/smallstep/certificates v0.24.2
|
||||
github.com/smallstep/nosql v0.6.0
|
||||
github.com/smallstep/truststore v0.12.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046
|
||||
github.com/yuin/goldmark v1.5.6
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/tailscale/tscert v0.0.0-20230509043813-4e9cb4f2b4ad
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
|
||||
go.opentelemetry.io/otel/sdk v1.21.0
|
||||
go.uber.org/zap v1.25.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sync v0.4.0
|
||||
golang.org/x/term v0.13.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
|
||||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/term v0.9.0
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/iam v1.1.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/golang/glog v1.1.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.6 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/golang/glog v1.1.0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
||||
github.com/google/go-tpm v0.3.3 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -85,13 +83,13 @@ require (
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/go-kit/kit v0.10.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // 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.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -110,7 +108,7 @@ require (
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
@@ -118,34 +116,36 @@ require (
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/slackhq/nebula v1.6.1 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/urfave/cli v1.22.14 // indirect
|
||||
github.com/urfave/cli v1.22.13 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
go.step.sm/cli-utils v0.8.0 // indirect
|
||||
go.step.sm/crypto v0.35.1
|
||||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go.step.sm/cli-utils v0.7.6 // indirect
|
||||
go.step.sm/crypto v0.30.0
|
||||
go.step.sm/linkedca v0.19.1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/sys v0.14.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/sys v0.9.0
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,56 +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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SplitUnixSocketPermissionsBits takes a unix socket address in the
|
||||
// unusual "path|bits" format (e.g. /run/caddy.sock|0222) and tries
|
||||
// to split it into socket path (host) and permissions bits (port).
|
||||
// Colons (":") can't be used as separator, as socket paths on Windows
|
||||
// may include a drive letter (e.g. `unix/c:\absolute\path.sock`).
|
||||
// Permission bits will default to 0200 if none are specified.
|
||||
// Throws an error, if the first carrying bit does not
|
||||
// include write perms (e.g. `0422` or `022`).
|
||||
// Symbolic permission representation (e.g. `u=w,g=w,o=w`)
|
||||
// is not supported and will throw an error for now!
|
||||
func SplitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) {
|
||||
addrSplit := strings.SplitN(addr, "|", 2)
|
||||
|
||||
if len(addrSplit) == 2 {
|
||||
// parse octal permission bit string as uint32
|
||||
fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err)
|
||||
}
|
||||
fileMode = fs.FileMode(fileModeUInt64)
|
||||
|
||||
// FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`)
|
||||
if string(fileMode.String()[2]) != "w" {
|
||||
return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr)
|
||||
}
|
||||
|
||||
return addrSplit[0], fileMode, nil
|
||||
}
|
||||
|
||||
// default to 0200 (symbolic: `u=w,g=,o=`)
|
||||
// if no permission bits are specified
|
||||
return addr, 0o200, nil
|
||||
}
|
||||
@@ -30,34 +30,18 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||
switch network {
|
||||
case "udp", "udp4", "udp6", "unixgram":
|
||||
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
pc, err := config.ListenPacket(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
|
||||
})
|
||||
func listenTCPOrUnix(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (net.Listener, error) {
|
||||
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
ln, err := config.Listen(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
||||
|
||||
default:
|
||||
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
ln, err := config.Listen(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sharedListener{Listener: ln, key: lnKey}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
||||
return &sharedListener{Listener: ln, key: lnKey}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
||||
}
|
||||
|
||||
// fakeCloseListener is a private wrapper over a listener that
|
||||
@@ -114,7 +98,7 @@ func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
|
||||
// so that it's clear in the code that side-effects are shared with other
|
||||
// users of this listener, not just our own reference to it; we also don't
|
||||
// do anything with the error because all we could do is log it, but we
|
||||
// explicitly assign it to nothing so we don't forget it's there if needed
|
||||
// expliclty assign it to nothing so we don't forget it's there if needed
|
||||
_ = fcl.sharedListener.clearDeadline()
|
||||
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
@@ -188,75 +172,3 @@ func (sl *sharedListener) setDeadline() error {
|
||||
func (sl *sharedListener) Destruct() error {
|
||||
return sl.Listener.Close()
|
||||
}
|
||||
|
||||
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
|
||||
// or more specifically, *net.UDPConn
|
||||
type fakeClosePacketConn struct {
|
||||
closed int32 // accessed atomically; belongs to this struct only
|
||||
*sharedPacketConn // embedded, so we also become a net.PacketConn; its key is used in Close
|
||||
}
|
||||
|
||||
func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
// if the listener is already "closed", return error
|
||||
if atomic.LoadInt32(&fcpc.closed) == 1 {
|
||||
return 0, nil, &net.OpError{
|
||||
Op: "readfrom",
|
||||
Net: fcpc.LocalAddr().Network(),
|
||||
Addr: fcpc.LocalAddr(),
|
||||
Err: errFakeClosed,
|
||||
}
|
||||
}
|
||||
|
||||
// call underlying readfrom
|
||||
n, addr, err = fcpc.sharedPacketConn.ReadFrom(p)
|
||||
if err != nil {
|
||||
// this server was stopped, so clear the deadline and let
|
||||
// any new server continue reading; but we will exit
|
||||
if atomic.LoadInt32(&fcpc.closed) == 1 {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
if err = fcpc.SetReadDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
|
||||
func (fcpc *fakeClosePacketConn) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
||||
_ = fcpc.SetReadDeadline(time.Now()) // unblock ReadFrom() calls to kick old servers out of their loops
|
||||
_, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fcpc *fakeClosePacketConn) Unwrap() net.PacketConn {
|
||||
return fcpc.sharedPacketConn.PacketConn
|
||||
}
|
||||
|
||||
// sharedPacketConn is like sharedListener, but for net.PacketConns.
|
||||
type sharedPacketConn struct {
|
||||
net.PacketConn
|
||||
key string
|
||||
}
|
||||
|
||||
// Destruct closes the underlying socket.
|
||||
func (spc *sharedPacketConn) Destruct() error {
|
||||
return spc.PacketConn.Close()
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying socket
|
||||
func (spc *sharedPacketConn) Unwrap() net.PacketConn {
|
||||
return spc.PacketConn
|
||||
}
|
||||
|
||||
// Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
|
||||
var (
|
||||
_ (interface {
|
||||
Unwrap() net.PacketConn
|
||||
}) = (*fakeClosePacketConn)(nil)
|
||||
)
|
||||
|
||||
+3
-72
@@ -22,10 +22,8 @@ package caddy
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
@@ -89,7 +87,7 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||
func listenTCPOrUnix(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (net.Listener, error) {
|
||||
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
||||
oldControl := config.Control
|
||||
config.Control = func(network, address string, c syscall.RawConn) error {
|
||||
@@ -105,14 +103,7 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
||||
// we still put it in the listenerPool so we can count how many
|
||||
// configs are using this socket; necessary to ensure we can know
|
||||
// whether to enforce shutdown delays, for example (see #5393).
|
||||
var ln io.Closer
|
||||
var err error
|
||||
switch network {
|
||||
case "udp", "udp4", "udp6", "unixgram":
|
||||
ln, err = config.ListenPacket(ctx, network, address)
|
||||
default:
|
||||
ln, err = config.Listen(ctx, network, address)
|
||||
}
|
||||
ln, err := config.Listen(ctx, network, address)
|
||||
if err == nil {
|
||||
listenerPool.LoadOrStore(lnKey, nil)
|
||||
}
|
||||
@@ -126,23 +117,9 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
||||
unixSockets[lnKey] = ln.(*unixListener)
|
||||
}
|
||||
|
||||
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
|
||||
if unix, ok := ln.(*net.UnixConn); ok {
|
||||
ln = &unixConn{unix, address, lnKey, &one}
|
||||
unixSockets[lnKey] = ln.(*unixConn)
|
||||
}
|
||||
|
||||
// lightly wrap the listener so that when it is closed,
|
||||
// we can decrement the usage pool counter
|
||||
switch specificLn := ln.(type) {
|
||||
case net.Listener:
|
||||
return deleteListener{specificLn, lnKey}, err
|
||||
case net.PacketConn:
|
||||
return deletePacketConn{specificLn, lnKey}, err
|
||||
}
|
||||
|
||||
// other types, I guess we just return them directly
|
||||
return ln, err
|
||||
return deleteListener{ln, lnKey}, err
|
||||
}
|
||||
|
||||
// reusePort sets SO_REUSEPORT. Ineffective for unix sockets.
|
||||
@@ -181,36 +158,6 @@ func (uln *unixListener) Close() error {
|
||||
return uln.UnixListener.Close()
|
||||
}
|
||||
|
||||
type unixConn struct {
|
||||
*net.UnixConn
|
||||
filename string
|
||||
mapKey string
|
||||
count *int32 // accessed atomically
|
||||
}
|
||||
|
||||
func (uc *unixConn) Close() error {
|
||||
newCount := atomic.AddInt32(uc.count, -1)
|
||||
if newCount == 0 {
|
||||
defer func() {
|
||||
unixSocketsMu.Lock()
|
||||
delete(unixSockets, uc.mapKey)
|
||||
unixSocketsMu.Unlock()
|
||||
_ = syscall.Unlink(uc.filename)
|
||||
}()
|
||||
}
|
||||
return uc.UnixConn.Close()
|
||||
}
|
||||
|
||||
func (uc *unixConn) Unwrap() net.PacketConn {
|
||||
return uc.UnixConn
|
||||
}
|
||||
|
||||
// unixSockets keeps track of the currently-active unix sockets
|
||||
// so we can transfer their FDs gracefully during reloads.
|
||||
var unixSockets = make(map[string]interface {
|
||||
File() (*os.File, error)
|
||||
})
|
||||
|
||||
// deleteListener is a type that simply deletes itself
|
||||
// from the listenerPool when it closes. It is used
|
||||
// solely for the purpose of reference counting (i.e.
|
||||
@@ -224,19 +171,3 @@ func (dl deleteListener) Close() error {
|
||||
_, _ = listenerPool.Delete(dl.lnKey)
|
||||
return dl.Listener.Close()
|
||||
}
|
||||
|
||||
// deletePacketConn is like deleteListener, but
|
||||
// for net.PacketConns.
|
||||
type deletePacketConn struct {
|
||||
net.PacketConn
|
||||
lnKey string
|
||||
}
|
||||
|
||||
func (dl deletePacketConn) Close() error {
|
||||
_, _ = listenerPool.Delete(dl.lnKey)
|
||||
return dl.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (dl deletePacketConn) Unwrap() net.PacketConn {
|
||||
return dl.PacketConn
|
||||
}
|
||||
|
||||
+212
-133
@@ -28,13 +28,12 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
// NetworkAddress represents one or more network addresses.
|
||||
@@ -148,19 +147,17 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
|
||||
}
|
||||
|
||||
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
||||
var (
|
||||
ln any
|
||||
err error
|
||||
address string
|
||||
unixFileMode fs.FileMode
|
||||
isAbtractUnixSocket bool
|
||||
)
|
||||
var ln any
|
||||
var err error
|
||||
var address string
|
||||
var unixFileMode fs.FileMode
|
||||
var isAbtractUnixSocket bool
|
||||
|
||||
// split unix socket addr early so lnKey
|
||||
// is independent of permissions bits
|
||||
if na.IsUnixNetwork() {
|
||||
var err error
|
||||
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
||||
address, unixFileMode, err = splitUnixSocketPermissionsBits(na.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,16 +173,33 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
||||
}
|
||||
|
||||
}
|
||||
return socket, err
|
||||
}
|
||||
|
||||
lnKey := listenerKey(na.Network, address)
|
||||
|
||||
switch na.Network {
|
||||
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
|
||||
ln, err = listenTCPOrUnix(ctx, lnKey, na.Network, address, config)
|
||||
case "unixgram":
|
||||
ln, err = config.ListenPacket(ctx, na.Network, address)
|
||||
case "udp", "udp4", "udp6":
|
||||
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
pc, err := config.ListenPacket(ctx, na.Network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln = &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}
|
||||
}
|
||||
if strings.HasPrefix(na.Network, "ip") {
|
||||
ln, err = config.ListenPacket(ctx, na.Network, address)
|
||||
} else {
|
||||
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -194,6 +208,13 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
||||
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
||||
}
|
||||
|
||||
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
|
||||
if unix, ok := ln.(*net.UnixConn); ok {
|
||||
one := int32(1)
|
||||
ln = &unixConn{unix, address, lnKey, &one}
|
||||
unixSockets[lnKey] = unix
|
||||
}
|
||||
|
||||
if IsUnixNetwork(na.Network) {
|
||||
if !isAbtractUnixSocket {
|
||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
||||
@@ -297,6 +318,40 @@ func IsUnixNetwork(netw string) bool {
|
||||
return strings.HasPrefix(netw, "unix")
|
||||
}
|
||||
|
||||
// Takes a unix socket address in the unusual "path|bits" format
|
||||
// (e.g. /run/caddy.sock|0222) and tries to split it into
|
||||
// socket path (host) and permissions bits (port). Colons (":")
|
||||
// can't be used as separator, as socket paths on Windows may
|
||||
// include a drive letter (e.g. `unix/c:\absolute\path.sock`).
|
||||
// Permission bits will default to 0200 if none are specified.
|
||||
// Throws an error, if the first carrying bit does not
|
||||
// include write perms (e.g. `0422` or `022`).
|
||||
// Symbolic permission representation (e.g. `u=w,g=w,o=w`)
|
||||
// is not supported and will throw an error for now!
|
||||
func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) {
|
||||
addrSplit := strings.SplitN(addr, "|", 2)
|
||||
|
||||
if len(addrSplit) == 2 {
|
||||
// parse octal permission bit string as uint32
|
||||
fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err)
|
||||
}
|
||||
fileMode = fs.FileMode(fileModeUInt64)
|
||||
|
||||
// FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`)
|
||||
if string(fileMode.String()[2]) != "w" {
|
||||
return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr)
|
||||
}
|
||||
|
||||
return addrSplit[0], fileMode, nil
|
||||
}
|
||||
|
||||
// default to 0200 (symbolic: `u=w,g=,o=`)
|
||||
// if no permission bits are specified
|
||||
return addr, 0200, nil
|
||||
}
|
||||
|
||||
// ParseNetworkAddress parses addr into its individual
|
||||
// components. The input string is expected to be of
|
||||
// the form "network/host:port-range" where any part is
|
||||
@@ -321,7 +376,7 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui
|
||||
network = defaultNetwork
|
||||
}
|
||||
if IsUnixNetwork(network) {
|
||||
_, _, err := internal.SplitUnixSocketPermissionsBits(host)
|
||||
_, _, err := splitUnixSocketPermissionsBits(host)
|
||||
return NetworkAddress{
|
||||
Network: network,
|
||||
Host: host,
|
||||
@@ -447,93 +502,53 @@ func ListenPacket(network, addr string) (net.PacketConn, error) {
|
||||
// unixgram will be used; otherwise, udp will be used).
|
||||
//
|
||||
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
||||
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
|
||||
lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
|
||||
|
||||
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
lnAny, err := na.Listen(ctx, portOffset, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln := lnAny.(net.PacketConn)
|
||||
|
||||
h3ln := ln
|
||||
for {
|
||||
// retrieve the underlying socket, so quic-go can optimize.
|
||||
if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
|
||||
h3ln = unwrapper.Unwrap()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sqs := newSharedQUICState(tlsConf, activeRequests)
|
||||
// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
|
||||
//nolint:gosec
|
||||
quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient}
|
||||
earlyLn, err := quic.ListenEarly(h3ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
|
||||
Allow0RTT: true,
|
||||
RequireAddressValidation: func(clientAddr net.Addr) bool {
|
||||
// TODO: make tunable?
|
||||
return sqs.getActiveRequests() > 1000
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// using the original net.PacketConn to close them properly
|
||||
return &sharedQuicListener{EarlyListener: earlyLn, packetConn: ln, sqs: sqs, key: lnKey}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sql := sharedEarlyListener.(*sharedQuicListener)
|
||||
// add current tls.Config to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation,
|
||||
// and the request counter will reflect current http server
|
||||
ctx, cancel := sql.sqs.addState(tlsConf, activeRequests)
|
||||
|
||||
return &fakeCloseQuicListener{
|
||||
sharedQuicListener: sql,
|
||||
context: ctx,
|
||||
contextCancel: cancel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DEPRECATED: Use NetworkAddress.ListenQUIC instead. This function will likely be changed or removed in the future.
|
||||
//
|
||||
// TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API.
|
||||
func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
|
||||
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
|
||||
|
||||
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||
sqs := newSharedQUICState(tlsConf, activeRequests)
|
||||
sqtc := newSharedQUICTLSConfig(tlsConf)
|
||||
// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
|
||||
//nolint:gosec
|
||||
quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient}
|
||||
quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient}
|
||||
earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
|
||||
Allow0RTT: true,
|
||||
RequireAddressValidation: func(clientAddr net.Addr) bool {
|
||||
// TODO: make tunable?
|
||||
return sqs.getActiveRequests() > 1000
|
||||
var highLoad bool
|
||||
if activeRequests != nil {
|
||||
highLoad = atomic.LoadInt64(activeRequests) > 1000 // TODO: make tunable?
|
||||
}
|
||||
return highLoad
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sharedQuicListener{EarlyListener: earlyLn, sqs: sqs, key: lnKey}, nil
|
||||
return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sql := sharedEarlyListener.(*sharedQuicListener)
|
||||
// add current tls.Config and request counter to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation,
|
||||
// and the request counter will reflect current http server
|
||||
ctx, cancel := sql.sqs.addState(tlsConf, activeRequests)
|
||||
// add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
|
||||
ctx, cancel := sql.sqtc.addTLSConfig(tlsConf)
|
||||
|
||||
// TODO: to serve QUIC over a unix socket, currently we need to hold onto
|
||||
// the underlying net.PacketConn (which we wrap as unixConn to keep count
|
||||
// of closes) because closing the quic.EarlyListener doesn't actually close
|
||||
// the underlying PacketConn, but we need to for unix sockets since we dup
|
||||
// the file descriptor and thus need to close the original; track issue:
|
||||
// https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608
|
||||
var unix *unixConn
|
||||
if uc, ok := ln.(*unixConn); ok {
|
||||
unix = uc
|
||||
}
|
||||
|
||||
return &fakeCloseQuicListener{
|
||||
sharedQuicListener: sql,
|
||||
uc: unix,
|
||||
context: ctx,
|
||||
contextCancel: cancel,
|
||||
}, nil
|
||||
@@ -551,50 +566,38 @@ type contextAndCancelFunc struct {
|
||||
context.CancelFunc
|
||||
}
|
||||
|
||||
// sharedQUICState manages GetConfigForClient and current number of active requests
|
||||
// sharedQUICTLSConfig manages GetConfigForClient
|
||||
// see issue: https://github.com/caddyserver/caddy/pull/4849
|
||||
type sharedQUICState struct {
|
||||
rmu sync.RWMutex
|
||||
tlsConfs map[*tls.Config]contextAndCancelFunc
|
||||
requestCounters map[*tls.Config]*int64
|
||||
activeTlsConf *tls.Config
|
||||
activeRequestsCounter *int64
|
||||
type sharedQUICTLSConfig struct {
|
||||
rmu sync.RWMutex
|
||||
tlsConfs map[*tls.Config]contextAndCancelFunc
|
||||
activeTlsConf *tls.Config
|
||||
}
|
||||
|
||||
// newSharedQUICState creates a new sharedQUICState
|
||||
func newSharedQUICState(tlsConfig *tls.Config, activeRequests *int64) *sharedQUICState {
|
||||
sqtc := &sharedQUICState{
|
||||
tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
|
||||
requestCounters: make(map[*tls.Config]*int64),
|
||||
activeTlsConf: tlsConfig,
|
||||
activeRequestsCounter: activeRequests,
|
||||
// newSharedQUICTLSConfig creates a new sharedQUICTLSConfig
|
||||
func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig {
|
||||
sqtc := &sharedQUICTLSConfig{
|
||||
tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
|
||||
activeTlsConf: tlsConfig,
|
||||
}
|
||||
sqtc.addState(tlsConfig, activeRequests)
|
||||
sqtc.addTLSConfig(tlsConfig)
|
||||
return sqtc
|
||||
}
|
||||
|
||||
// getConfigForClient is used as tls.Config's GetConfigForClient field
|
||||
func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
sqs.rmu.RLock()
|
||||
defer sqs.rmu.RUnlock()
|
||||
return sqs.activeTlsConf.GetConfigForClient(ch)
|
||||
func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
sqtc.rmu.RLock()
|
||||
defer sqtc.rmu.RUnlock()
|
||||
return sqtc.activeTlsConf.GetConfigForClient(ch)
|
||||
}
|
||||
|
||||
// getActiveRequests returns the number of active requests
|
||||
func (sqs *sharedQUICState) getActiveRequests() int64 {
|
||||
// Prevent a race when a context is cancelled and active request counter is being changed
|
||||
sqs.rmu.RLock()
|
||||
defer sqs.rmu.RUnlock()
|
||||
return atomic.LoadInt64(sqs.activeRequestsCounter)
|
||||
}
|
||||
// addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc
|
||||
// so that when cancelled, the active tls.Config will change
|
||||
func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
|
||||
sqtc.rmu.Lock()
|
||||
defer sqtc.rmu.Unlock()
|
||||
|
||||
// addState adds tls.Config and activeRequests to the map if not present and returns the corresponding context and its cancelFunc
|
||||
// so that when cancelled, the active tls.Config and request counter will change
|
||||
func (sqs *sharedQUICState) addState(tlsConfig *tls.Config, activeRequests *int64) (context.Context, context.CancelFunc) {
|
||||
sqs.rmu.Lock()
|
||||
defer sqs.rmu.Unlock()
|
||||
|
||||
if cacc, ok := sqs.tlsConfs[tlsConfig]; ok {
|
||||
if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok {
|
||||
return cacc.Context, cacc.CancelFunc
|
||||
}
|
||||
|
||||
@@ -602,26 +605,23 @@ func (sqs *sharedQUICState) addState(tlsConfig *tls.Config, activeRequests *int6
|
||||
wrappedCancel := func() {
|
||||
cancel()
|
||||
|
||||
sqs.rmu.Lock()
|
||||
defer sqs.rmu.Unlock()
|
||||
sqtc.rmu.Lock()
|
||||
defer sqtc.rmu.Unlock()
|
||||
|
||||
delete(sqs.tlsConfs, tlsConfig)
|
||||
delete(sqs.requestCounters, tlsConfig)
|
||||
if sqs.activeTlsConf == tlsConfig {
|
||||
// select another tls.Config and request counter, if there is none,
|
||||
delete(sqtc.tlsConfs, tlsConfig)
|
||||
if sqtc.activeTlsConf == tlsConfig {
|
||||
// select another tls.Config, if there is none,
|
||||
// related sharedQuicListener will be destroyed anyway
|
||||
for tc, counter := range sqs.requestCounters {
|
||||
sqs.activeTlsConf = tc
|
||||
sqs.activeRequestsCounter = counter
|
||||
for tc := range sqtc.tlsConfs {
|
||||
sqtc.activeTlsConf = tc
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sqs.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
|
||||
sqs.requestCounters[tlsConfig] = activeRequests
|
||||
sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
|
||||
// there should be at most 2 tls.Configs
|
||||
if len(sqs.tlsConfs) > 2 {
|
||||
Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqs.tlsConfs)))
|
||||
if len(sqtc.tlsConfs) > 2 {
|
||||
Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs)))
|
||||
}
|
||||
return ctx, wrappedCancel
|
||||
}
|
||||
@@ -629,17 +629,24 @@ func (sqs *sharedQUICState) addState(tlsConfig *tls.Config, activeRequests *int6
|
||||
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
||||
type sharedQuicListener struct {
|
||||
*quic.EarlyListener
|
||||
packetConn net.PacketConn // we have to hold these because quic-go won't close listeners it didn't create
|
||||
sqs *sharedQUICState
|
||||
key string
|
||||
sqtc *sharedQUICTLSConfig
|
||||
key string
|
||||
}
|
||||
|
||||
// Destruct closes the underlying QUIC listener and its associated net.PacketConn.
|
||||
// Destruct closes the underlying QUIC listener.
|
||||
func (sql *sharedQuicListener) Destruct() error {
|
||||
// close EarlyListener first to stop any operations being done to the net.PacketConn
|
||||
_ = sql.EarlyListener.Close()
|
||||
// then close the net.PacketConn
|
||||
return sql.packetConn.Close()
|
||||
return sql.EarlyListener.Close()
|
||||
}
|
||||
|
||||
// sharedPacketConn is like sharedListener, but for net.PacketConns.
|
||||
type sharedPacketConn struct {
|
||||
net.PacketConn
|
||||
key string
|
||||
}
|
||||
|
||||
// Destruct closes the underlying socket.
|
||||
func (spc *sharedPacketConn) Destruct() error {
|
||||
return spc.PacketConn.Close()
|
||||
}
|
||||
|
||||
// fakeClosedErr returns an error value that is not temporary
|
||||
@@ -661,9 +668,41 @@ func fakeClosedErr(l interface{ Addr() net.Addr }) error {
|
||||
// socket is actually left open.
|
||||
var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
|
||||
|
||||
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns.
|
||||
type fakeClosePacketConn struct {
|
||||
closed int32 // accessed atomically; belongs to this struct only
|
||||
*sharedPacketConn // embedded, so we also become a net.PacketConn
|
||||
}
|
||||
|
||||
func (fcpc *fakeClosePacketConn) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
||||
_, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
||||
func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error {
|
||||
if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
|
||||
return conn.SetReadBuffer(bytes)
|
||||
}
|
||||
return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn)
|
||||
}
|
||||
|
||||
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
||||
func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||
if conn, ok := fcpc.PacketConn.(interface {
|
||||
SyscallConn() (syscall.RawConn, error)
|
||||
}); ok {
|
||||
return conn.SyscallConn()
|
||||
}
|
||||
return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn)
|
||||
}
|
||||
|
||||
type fakeCloseQuicListener struct {
|
||||
closed int32 // accessed atomically; belongs to this struct only
|
||||
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
||||
closed int32 // accessed atomically; belongs to this struct only
|
||||
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
||||
uc *unixConn // underlying unix socket, if UDS
|
||||
context context.Context
|
||||
contextCancel context.CancelFunc
|
||||
}
|
||||
@@ -690,6 +729,11 @@ func (fcql *fakeCloseQuicListener) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
|
||||
fcql.contextCancel()
|
||||
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
|
||||
if fcql.uc != nil {
|
||||
// unix sockets need to be closed ourselves because we dup() the file
|
||||
// descriptor when we reuse them, so this avoids a resource leak
|
||||
fcql.uc.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -715,7 +759,34 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
|
||||
networkTypes[network] = getListener
|
||||
}
|
||||
|
||||
var unixSocketsMu sync.Mutex
|
||||
type unixConn struct {
|
||||
*net.UnixConn
|
||||
filename string
|
||||
mapKey string
|
||||
count *int32 // accessed atomically
|
||||
}
|
||||
|
||||
func (uc *unixConn) Close() error {
|
||||
newCount := atomic.AddInt32(uc.count, -1)
|
||||
if newCount == 0 {
|
||||
defer func() {
|
||||
unixSocketsMu.Lock()
|
||||
delete(unixSockets, uc.mapKey)
|
||||
unixSocketsMu.Unlock()
|
||||
_ = syscall.Unlink(uc.filename)
|
||||
}()
|
||||
}
|
||||
return uc.UnixConn.Close()
|
||||
}
|
||||
|
||||
// unixSockets keeps track of the currently-active unix sockets
|
||||
// so we can transfer their FDs gracefully during reloads.
|
||||
var (
|
||||
unixSockets = make(map[string]interface {
|
||||
File() (*os.File, error)
|
||||
})
|
||||
unixSocketsMu sync.Mutex
|
||||
)
|
||||
|
||||
// getListenerFromPlugin returns a listener on the given network and address
|
||||
// if a plugin has registered the network name. It may return (nil, nil) if
|
||||
@@ -759,3 +830,11 @@ type ListenerWrapper interface {
|
||||
var listenerPool = NewUsagePool()
|
||||
|
||||
const maxPortSpan = 65535
|
||||
|
||||
// Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
|
||||
var (
|
||||
_ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil)
|
||||
_ (interface {
|
||||
SyscallConn() (syscall.RawConn, error)
|
||||
}) = (*fakeClosePacketConn)(nil)
|
||||
)
|
||||
|
||||
+1
-3
@@ -17,8 +17,6 @@ package caddy
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
func TestSplitNetworkAddress(t *testing.T) {
|
||||
@@ -636,7 +634,7 @@ func TestSplitUnixSocketPermissionsBits(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
} {
|
||||
actualPath, actualFileMode, err := internal.SplitUnixSocketPermissionsBits(tc.input)
|
||||
actualPath, actualFileMode, err := splitUnixSocketPermissionsBits(tc.input)
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("Test %d: Expected error but got: %v", i, err)
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,11 +3,10 @@ package caddy
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
)
|
||||
|
||||
// define and register the metrics used in this package.
|
||||
|
||||
@@ -22,10 +22,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
+20
-14
@@ -20,19 +20,16 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyevents"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -73,7 +70,7 @@ func init() {
|
||||
// `{http.request.remote.host}` | The host (IP) part of the remote client's address
|
||||
// `{http.request.remote.port}` | The port part of the remote client's address
|
||||
// `{http.request.remote}` | The address of the remote client
|
||||
// `{http.request.scheme}` | The request scheme, typically `http` or `https`
|
||||
// `{http.request.scheme}` | The request scheme
|
||||
// `{http.request.tls.version}` | The TLS version name
|
||||
// `{http.request.tls.cipher_suite}` | The TLS cipher suite
|
||||
// `{http.request.tls.resumed}` | The TLS connection resumed a previous connection
|
||||
@@ -328,15 +325,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
|
||||
// Validate ensures the app's configuration is valid.
|
||||
func (app *App) Validate() error {
|
||||
isGo120 := strings.Contains(runtime.Version(), "go1.20")
|
||||
|
||||
// each server must use distinct listener addresses
|
||||
lnAddrs := make(map[string]string)
|
||||
for srvName, srv := range app.Servers {
|
||||
if isGo120 && srv.EnableFullDuplex {
|
||||
app.logger.Warn("enable_full_duplex is not supported in Go 1.20, use a build made with Go 1.21 or later", zap.String("server", srvName))
|
||||
}
|
||||
|
||||
for _, addr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
||||
if err != nil {
|
||||
@@ -378,7 +369,11 @@ func (app *App) Start() error {
|
||||
return context.WithValue(ctx, ConnCtxKey, c)
|
||||
},
|
||||
}
|
||||
h2server := new(http2.Server)
|
||||
h2server := &http2.Server{
|
||||
NewWriteScheduler: func() http2.WriteScheduler {
|
||||
return http2.NewPriorityWriteScheduler(nil)
|
||||
},
|
||||
}
|
||||
|
||||
// disable HTTP/2, which we enabled by default during provisioning
|
||||
if !srv.protocol("h2") {
|
||||
@@ -613,6 +608,17 @@ func (app *App) Stop() error {
|
||||
zap.Error(err),
|
||||
zap.Strings("addresses", server.Listen))
|
||||
}
|
||||
|
||||
// TODO: we have to manually close our listeners because quic-go won't
|
||||
// close listeners it didn't create along with the server itself...
|
||||
// see https://github.com/quic-go/quic-go/issues/3560
|
||||
for _, el := range server.h3listeners {
|
||||
if err := el.Close(); err != nil {
|
||||
app.logger.Error("HTTP/3 listener close",
|
||||
zap.Error(err),
|
||||
zap.String("address", el.LocalAddr().String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
stopH2Listener := func(server *Server) {
|
||||
defer finishedShutdown.Done()
|
||||
|
||||
@@ -20,11 +20,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AutoHTTPSConfig is used to disable automatic HTTPS
|
||||
@@ -197,7 +196,8 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
// if a certificate for this name is already loaded,
|
||||
// don't obtain another one for it, unless we are
|
||||
// supposed to ignore loaded certificates
|
||||
if !srv.AutoHTTPS.IgnoreLoadedCerts && app.tlsApp.HasCertificateForSubject(d) {
|
||||
if !srv.AutoHTTPS.IgnoreLoadedCerts &&
|
||||
len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
|
||||
logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
|
||||
zap.String("domain", d),
|
||||
zap.String("server_name", srvName),
|
||||
|
||||
@@ -23,14 +23,16 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(HTTPBasicAuth{})
|
||||
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// HTTPBasicAuth facilitates HTTP basic authentication.
|
||||
|
||||
@@ -18,10 +18,9 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -22,12 +22,10 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -18,10 +18,9 @@ import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -307,7 +307,5 @@ const (
|
||||
const separator = string(filepath.Separator)
|
||||
|
||||
// Interface guard
|
||||
var (
|
||||
_ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil)
|
||||
_ caddyfile.Unmarshaler = (*tlsPlaceholderWrapper)(nil)
|
||||
)
|
||||
var _ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil)
|
||||
var _ caddyfile.Unmarshaler = (*tlsPlaceholderWrapper)(nil)
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/operators"
|
||||
@@ -37,9 +39,6 @@ import (
|
||||
"github.com/google/cel-go/parser"
|
||||
"go.uber.org/zap"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -235,11 +234,9 @@ func (cr celHTTPRequest) Parent() interpreter.Activation {
|
||||
func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
||||
return cr.Request, nil
|
||||
}
|
||||
|
||||
func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
|
||||
if o, ok := other.Value().(celHTTPRequest); ok {
|
||||
return types.Bool(o.Request == cr.Request)
|
||||
@@ -258,14 +255,12 @@ type celPkixName struct{ *pkix.Name }
|
||||
func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
||||
return pn.Name, nil
|
||||
}
|
||||
|
||||
func (pn celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
if typeVal.TypeName() == "string" {
|
||||
return types.String(pn.Name.String())
|
||||
}
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (pn celPkixName) Equal(other ref.Val) ref.Val {
|
||||
if o, ok := other.Value().(string); ok {
|
||||
return types.Bool(pn.Name.String() == o)
|
||||
|
||||
@@ -1,26 +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 !go1.21
|
||||
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func enableFullDuplex(w http.ResponseWriter) error {
|
||||
// Do nothing, Go 1.20 and earlier do not support full duplex
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +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 go1.21
|
||||
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func enableFullDuplex(w http.ResponseWriter) error {
|
||||
//nolint:bodyclose
|
||||
return http.NewResponseController(w).EnableFullDuplex()
|
||||
}
|
||||
@@ -93,7 +93,6 @@ func (enc *Encode) Provision(ctx caddy.Context) error {
|
||||
"application/xhtml+xml*",
|
||||
"application/atom+xml*",
|
||||
"application/rss+xml*",
|
||||
"application/wasm*",
|
||||
"image/svg+xml*",
|
||||
},
|
||||
},
|
||||
@@ -168,10 +167,10 @@ func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter
|
||||
// initResponseWriter initializes the responseWriter instance
|
||||
// allocated in openResponseWriter, enabling mid-stack inlining.
|
||||
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
|
||||
if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok {
|
||||
rw.ResponseWriter = rww
|
||||
if httpInterfaces, ok := wrappedRW.(caddyhttp.HTTPInterfaces); ok {
|
||||
rw.HTTPInterfaces = httpInterfaces
|
||||
} else {
|
||||
rw.ResponseWriter = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
|
||||
rw.HTTPInterfaces = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
|
||||
}
|
||||
rw.encodingName = encodingName
|
||||
rw.config = enc
|
||||
@@ -183,7 +182,7 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
|
||||
// using the encoding represented by encodingName and
|
||||
// configured by config.
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
caddyhttp.HTTPInterfaces
|
||||
encodingName string
|
||||
w Encoder
|
||||
config *Encode
|
||||
@@ -212,8 +211,7 @@ func (rw *responseWriter) Flush() {
|
||||
// to rw.Write (see bug in #4314)
|
||||
return
|
||||
}
|
||||
//nolint:bodyclose
|
||||
http.NewResponseController(rw.ResponseWriter).Flush()
|
||||
rw.HTTPInterfaces.Flush()
|
||||
}
|
||||
|
||||
// Hijack implements http.Hijacker. It will flush status code if set. We don't track actual hijacked
|
||||
@@ -221,12 +219,11 @@ func (rw *responseWriter) Flush() {
|
||||
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if !rw.wroteHeader {
|
||||
if rw.statusCode != 0 {
|
||||
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
||||
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
}
|
||||
//nolint:bodyclose
|
||||
return http.NewResponseController(rw.ResponseWriter).Hijack()
|
||||
return rw.HTTPInterfaces.Hijack()
|
||||
}
|
||||
|
||||
// Write writes to the response. If the response qualifies,
|
||||
@@ -263,7 +260,7 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||
// by the standard library
|
||||
if !rw.wroteHeader {
|
||||
if rw.statusCode != 0 {
|
||||
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
||||
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
}
|
||||
@@ -271,7 +268,7 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||
if rw.w != nil {
|
||||
return rw.w.Write(p)
|
||||
} else {
|
||||
return rw.ResponseWriter.Write(p)
|
||||
return rw.HTTPInterfaces.Write(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +284,7 @@ func (rw *responseWriter) Close() error {
|
||||
|
||||
// issue #5059, don't write status code if not set explicitly.
|
||||
if rw.statusCode != 0 {
|
||||
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
||||
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
}
|
||||
@@ -304,7 +301,7 @@ func (rw *responseWriter) Close() error {
|
||||
|
||||
// Unwrap returns the underlying ResponseWriter.
|
||||
func (rw *responseWriter) Unwrap() http.ResponseWriter {
|
||||
return rw.ResponseWriter
|
||||
return rw.HTTPInterfaces
|
||||
}
|
||||
|
||||
// init should be called before we write a response, if rw.buf has contents.
|
||||
@@ -313,7 +310,7 @@ func (rw *responseWriter) init() {
|
||||
rw.config.Match(rw) {
|
||||
|
||||
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
||||
rw.w.Reset(rw.ResponseWriter)
|
||||
rw.w.Reset(rw.HTTPInterfaces)
|
||||
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
|
||||
rw.Header().Set("Content-Encoding", rw.encodingName)
|
||||
rw.Header().Add("Vary", "Accept-Encoding")
|
||||
@@ -432,4 +429,5 @@ var (
|
||||
_ caddy.Provisioner = (*Encode)(nil)
|
||||
_ caddy.Validator = (*Encode)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Encode)(nil)
|
||||
_ caddyhttp.HTTPInterfaces = (*responseWriter)(nil)
|
||||
)
|
||||
|
||||
@@ -18,11 +18,10 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
package caddyzstd
|
||||
|
||||
import (
|
||||
"github.com/klauspost/compress/zstd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -15,24 +15,27 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Error is a convenient way for a Handler to populate the
|
||||
// essential fields of a HandlerError. If err is itself a
|
||||
// HandlerError, then any essential fields that are not
|
||||
// set will be populated.
|
||||
func Error(statusCode int, err error) HandlerError {
|
||||
const idLen = 9
|
||||
var he HandlerError
|
||||
if errors.As(err, &he) {
|
||||
if he, ok := err.(HandlerError); ok {
|
||||
if he.ID == "" {
|
||||
he.ID = randString(idLen, true)
|
||||
}
|
||||
|
||||
@@ -29,25 +29,18 @@ import (
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// BrowseTemplate is the default template document to use for
|
||||
// file listings. By default, its default value is an embedded
|
||||
// document. You can override this value at program start, or
|
||||
// if you are running Caddy via config, you can specify a
|
||||
// custom template_file in the browse configuration.
|
||||
//
|
||||
//go:embed browse.html
|
||||
var BrowseTemplate string
|
||||
var defaultBrowseTemplate string
|
||||
|
||||
// Browse configures directory browsing.
|
||||
type Browse struct {
|
||||
// Filename of the template to use instead of the embedded browse template.
|
||||
// Use this template file instead of the default browse template.
|
||||
TemplateFile string `json:"template_file,omitempty"`
|
||||
}
|
||||
|
||||
@@ -120,7 +113,7 @@ func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter,
|
||||
fs = http.Dir(repl.ReplaceAll(fsrv.Root, "."))
|
||||
}
|
||||
|
||||
tplCtx := &templateContext{
|
||||
var tplCtx = &templateContext{
|
||||
TemplateContext: templates.TemplateContext{
|
||||
Root: fs,
|
||||
Req: r,
|
||||
@@ -211,7 +204,7 @@ func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*template.T
|
||||
}
|
||||
} else {
|
||||
tpl = tplCtx.NewTemplate("default_listing")
|
||||
tpl, err = tpl.Parse(BrowseTemplate)
|
||||
tpl, err = tpl.Parse(defaultBrowseTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing default browse template: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,296 +1,252 @@
|
||||
{{- define "icon"}}
|
||||
{{- if .IsDir}}
|
||||
{{- if .IsSymlink}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
||||
<path fill="#000" d="M2.795 17.306c0-2.374 1.792-4.314 4.078-4.538v-1.104a.38.38 0 0 1 .651-.272l2.45 2.492a.132.132 0 0 1 0 .188l-2.45 2.492a.381.381 0 0 1-.651-.272V15.24c-1.889.297-3.436 1.39-3.817 3.26a2.809 2.809 0 0 1-.261-1.193Z" style="stroke-width:.127478"/>
|
||||
</svg>
|
||||
{{- else}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
||||
</svg>
|
||||
{{- end}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"></path>
|
||||
</svg>
|
||||
{{- else if or (eq .Name "LICENSE") (eq .Name "README")}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-license" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 21h-9a3 3 0 0 1 -3 -3v-1h10v2a2 2 0 0 0 4 0v-14a2 2 0 1 1 2 2h-2m2 -4h-11a3 3 0 0 0 -3 3v11"/>
|
||||
<path d="M9 7l4 0"/>
|
||||
<path d="M9 11l4 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M15 21h-9a3 3 0 0 1 -3 -3v-1h10v2a2 2 0 0 0 4 0v-14a2 2 0 1 1 2 2h-2m2 -4h-11a3 3 0 0 0 -3 3v11"></path>
|
||||
<path d="M9 7l4 0"></path>
|
||||
<path d="M9 11l4 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}}
|
||||
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic"}}
|
||||
{{- if eq .Tpl.Layout "grid"}}
|
||||
<img loading="lazy" src="{{html .Name}}">
|
||||
<img src="{{html .Name}}">
|
||||
{{- else}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 8h.01"/>
|
||||
<path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z"/>
|
||||
<path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5"/>
|
||||
<path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M15 8h.01"></path>
|
||||
<path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z"></path>
|
||||
<path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5"></path>
|
||||
<path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3"></path>
|
||||
</svg>
|
||||
{{- end}}
|
||||
{{- else if .HasExt ".mp4" ".mov" ".m4v" ".mpeg" ".mpg" ".avi" ".ogg" ".webm" ".mkv" ".vob" ".gifv" ".3gp"}}
|
||||
{{- else if .HasExt ".mp4" ".mov" ".mpeg" ".mpg" ".avi" ".ogg" ".webm" ".mkv" ".vob" ".gifv" ".3gp"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-movie" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
||||
<path d="M8 4l0 16"/>
|
||||
<path d="M16 4l0 16"/>
|
||||
<path d="M4 8l4 0"/>
|
||||
<path d="M4 16l4 0"/>
|
||||
<path d="M4 12l16 0"/>
|
||||
<path d="M16 8l4 0"/>
|
||||
<path d="M16 16l4 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
|
||||
<path d="M8 4l0 16"></path>
|
||||
<path d="M16 4l0 16"></path>
|
||||
<path d="M4 8l4 0"></path>
|
||||
<path d="M4 16l4 0"></path>
|
||||
<path d="M4 12l16 0"></path>
|
||||
<path d="M16 8l4 0"></path>
|
||||
<path d="M16 16l4 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".mp3" ".m4a" ".aac" ".ogg" ".flac" ".wav" ".wma" ".midi" ".cda"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-music" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"/>
|
||||
<path d="M16 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"/>
|
||||
<path d="M9 17l0 -13l10 0l0 13"/>
|
||||
<path d="M9 8l10 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
|
||||
<path d="M16 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
|
||||
<path d="M9 17l0 -13l10 0l0 13"></path>
|
||||
<path d="M9 8l10 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".pdf"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-pdf" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M5 18h1.5a1.5 1.5 0 0 0 0 -3h-1.5v6"/>
|
||||
<path d="M17 18h2"/>
|
||||
<path d="M20 15h-3v6"/>
|
||||
<path d="M11 15v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1z"/>
|
||||
</svg>
|
||||
{{- else if .HasExt ".csv" ".tsv"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-csv" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M7 16.5a1.5 1.5 0 0 0 -3 0v3a1.5 1.5 0 0 0 3 0"/>
|
||||
<path d="M10 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<path d="M16 15l2 6l2 -6"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pdf" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M10 8v8h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-2z"></path>
|
||||
<path d="M3 12h2a2 2 0 1 0 0 -4h-2v8"></path>
|
||||
<path d="M17 12h3"></path>
|
||||
<path d="M21 8h-4v8"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".txt" ".doc" ".docx" ".odt" ".fodt" ".rtf"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-text" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>
|
||||
<path d="M9 9l1 0"/>
|
||||
<path d="M9 13l6 0"/>
|
||||
<path d="M9 17l6 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
|
||||
<path d="M9 9l1 0"></path>
|
||||
<path d="M9 13l6 0"></path>
|
||||
<path d="M9 17l6 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".xls" ".xlsx" ".ods" ".fods"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-spreadsheet" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>
|
||||
<path d="M8 11h8v7h-8z"/>
|
||||
<path d="M8 15h8"/>
|
||||
<path d="M11 11v7"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
|
||||
<path d="M8 11h8v7h-8z"></path>
|
||||
<path d="M8 15h8"></path>
|
||||
<path d="M11 11v7"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".ppt" ".pptx" ".odp" ".fodp"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-presentation-analytics" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 12v-4"/>
|
||||
<path d="M15 12v-2"/>
|
||||
<path d="M12 12v-1"/>
|
||||
<path d="M3 4h18"/>
|
||||
<path d="M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-10"/>
|
||||
<path d="M12 16v4"/>
|
||||
<path d="M9 20h6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M9 12v-4"></path>
|
||||
<path d="M15 12v-2"></path>
|
||||
<path d="M12 12v-1"></path>
|
||||
<path d="M3 4h18"></path>
|
||||
<path d="M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-10"></path>
|
||||
<path d="M12 16v4"></path>
|
||||
<path d="M9 20h6"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".zip" ".gz" ".xz" ".tar" ".7z" ".rar" ".xz" ".zst"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-zip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 20.735a2 2 0 0 1 -1 -1.735v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1"/>
|
||||
<path d="M11 17a2 2 0 0 1 2 2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-2a2 2 0 0 1 2 -2z"/>
|
||||
<path d="M11 5l-1 0"/>
|
||||
<path d="M13 7l-1 0"/>
|
||||
<path d="M11 9l-1 0"/>
|
||||
<path d="M13 11l-1 0"/>
|
||||
<path d="M11 13l-1 0"/>
|
||||
<path d="M13 15l-1 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 20.735a2 2 0 0 1 -1 -1.735v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1"></path>
|
||||
<path d="M11 17a2 2 0 0 1 2 2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-2a2 2 0 0 1 2 -2z"></path>
|
||||
<path d="M11 5l-1 0"></path>
|
||||
<path d="M13 7l-1 0"></path>
|
||||
<path d="M11 9l-1 0"></path>
|
||||
<path d="M13 11l-1 0"></path>
|
||||
<path d="M11 13l-1 0"></path>
|
||||
<path d="M13 15l-1 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".deb" ".dpkg"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-debian" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 17c-2.397 -.943 -4 -3.153 -4 -5.635c0 -2.19 1.039 -3.14 1.604 -3.595c2.646 -2.133 6.396 -.27 6.396 3.23c0 2.5 -2.905 2.121 -3.5 1.5c-.595 -.621 -1 -1.5 -.5 -2.5"/>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 17c-2.397 -.943 -4 -3.153 -4 -5.635c0 -2.19 1.039 -3.14 1.604 -3.595c2.646 -2.133 6.396 -.27 6.396 3.23c0 2.5 -2.905 2.121 -3.5 1.5c-.595 -.621 -1 -1.5 -.5 -2.5"></path>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".rpm" ".exe" ".flatpak" ".appimage" ".jar" ".msi" ".apk"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-package" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/>
|
||||
<path d="M12 12l8 -4.5"/>
|
||||
<path d="M12 12l0 9"/>
|
||||
<path d="M12 12l-8 -4.5"/>
|
||||
<path d="M16 5.25l-8 4.5"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"></path>
|
||||
<path d="M12 12l8 -4.5"></path>
|
||||
<path d="M12 12l0 9"></path>
|
||||
<path d="M12 12l-8 -4.5"></path>
|
||||
<path d="M16 5.25l-8 4.5"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".ps1"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-powershell" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4.887 20h11.868c.893 0 1.664 -.665 1.847 -1.592l2.358 -12c.212 -1.081 -.442 -2.14 -1.462 -2.366a1.784 1.784 0 0 0 -.385 -.042h-11.868c-.893 0 -1.664 .665 -1.847 1.592l-2.358 12c-.212 1.081 .442 2.14 1.462 2.366c.127 .028 .256 .042 .385 .042z"/>
|
||||
<path d="M9 8l4 4l-6 4"/>
|
||||
<path d="M12 16h3"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4.887 20h11.868c.893 0 1.664 -.665 1.847 -1.592l2.358 -12c.212 -1.081 -.442 -2.14 -1.462 -2.366a1.784 1.784 0 0 0 -.385 -.042h-11.868c-.893 0 -1.664 .665 -1.847 1.592l-2.358 12c-.212 1.081 .442 2.14 1.462 2.366c.127 .028 .256 .042 .385 .042z"></path>
|
||||
<path d="M9 8l4 4l-6 4"></path>
|
||||
<path d="M12 16h3"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".py" ".pyc" ".pyo"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-python" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 9h-7a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h3"/>
|
||||
<path d="M12 15h7a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-3"/>
|
||||
<path d="M8 9v-4a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v5a2 2 0 0 1 -2 2h-4a2 2 0 0 0 -2 2v5a2 2 0 0 0 2 2h4a2 2 0 0 0 2 -2v-4"/>
|
||||
<path d="M11 6l0 .01"/>
|
||||
<path d="M13 18l0 .01"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 9h-7a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h3"></path>
|
||||
<path d="M12 15h7a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-3"></path>
|
||||
<path d="M8 9v-4a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v5a2 2 0 0 1 -2 2h-4a2 2 0 0 0 -2 2v5a2 2 0 0 0 2 2h4a2 2 0 0 0 2 -2v-4"></path>
|
||||
<path d="M11 6l0 .01"></path>
|
||||
<path d="M13 18l0 .01"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".bash" ".sh" ".com" ".bat" ".dll" ".so"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-script" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M17 20h-11a3 3 0 0 1 0 -6h11a3 3 0 0 0 0 6h1a3 3 0 0 0 3 -3v-11a2 2 0 0 0 -2 -2h-10a2 2 0 0 0 -2 2v8"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M17 20h-11a3 3 0 0 1 0 -6h11a3 3 0 0 0 0 6h1a3 3 0 0 0 3 -3v-11a2 2 0 0 0 -2 -2h-10a2 2 0 0 0 -2 2v8"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".dmg"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-finder" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 4m0 1a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1z"/>
|
||||
<path d="M7 8v1"/>
|
||||
<path d="M17 8v1"/>
|
||||
<path d="M12.5 4c-.654 1.486 -1.26 3.443 -1.5 9h2.5c-.19 2.867 .094 5.024 .5 7"/>
|
||||
<path d="M7 15.5c3.667 2 6.333 2 10 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M3 4m0 1a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M7 8v1"></path>
|
||||
<path d="M17 8v1"></path>
|
||||
<path d="M12.5 4c-.654 1.486 -1.26 3.443 -1.5 9h2.5c-.19 2.867 .094 5.024 .5 7"></path>
|
||||
<path d="M7 15.5c3.667 2 6.333 2 10 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".iso" ".img"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-disc" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/>
|
||||
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M7 12a5 5 0 0 1 5 -5"/>
|
||||
<path d="M12 17a5 5 0 0 0 5 -5"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"></path>
|
||||
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
|
||||
<path d="M7 12a5 5 0 0 1 5 -5"></path>
|
||||
<path d="M12 17a5 5 0 0 0 5 -5"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".md" ".mdown" ".markdown"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-markdown" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"/>
|
||||
<path d="M7 15v-6l2 2l2 -2v6"/>
|
||||
<path d="M14 13l2 2l2 -2m-2 2v-6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path>
|
||||
<path d="M7 15v-6l2 2l2 -2v6"></path>
|
||||
<path d="M14 13l2 2l2 -2m-2 2v-6"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".ttf" ".otf" ".woff" ".woff2" ".eof"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-typography" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>
|
||||
<path d="M11 18h2"/>
|
||||
<path d="M12 18v-7"/>
|
||||
<path d="M9 12v-1h6v1"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
|
||||
<path d="M11 18h2"></path>
|
||||
<path d="M12 18v-7"></path>
|
||||
<path d="M9 12v-1h6v1"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".go"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-golang" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15.695 14.305c1.061 1.06 2.953 .888 4.226 -.384c1.272 -1.273 1.444 -3.165 .384 -4.226c-1.061 -1.06 -2.953 -.888 -4.226 .384c-1.272 1.273 -1.444 3.165 -.384 4.226z"/>
|
||||
<path d="M12.68 9.233c-1.084 -.497 -2.545 -.191 -3.591 .846c-1.284 1.273 -1.457 3.165 -.388 4.226c1.07 1.06 2.978 .888 4.261 -.384a3.669 3.669 0 0 0 1.038 -1.921h-2.427"/>
|
||||
<path d="M5.5 15h-1.5"/>
|
||||
<path d="M6 9h-2"/>
|
||||
<path d="M5 12h-3"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M15.695 14.305c1.061 1.06 2.953 .888 4.226 -.384c1.272 -1.273 1.444 -3.165 .384 -4.226c-1.061 -1.06 -2.953 -.888 -4.226 .384c-1.272 1.273 -1.444 3.165 -.384 4.226z"></path>
|
||||
<path d="M12.68 9.233c-1.084 -.497 -2.545 -.191 -3.591 .846c-1.284 1.273 -1.457 3.165 -.388 4.226c1.07 1.06 2.978 .888 4.261 -.384a3.669 3.669 0 0 0 1.038 -1.921h-2.427"></path>
|
||||
<path d="M5.5 15h-1.5"></path>
|
||||
<path d="M6 9h-2"></path>
|
||||
<path d="M5 12h-3"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".html" ".htm"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-html" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M2 21v-6"/>
|
||||
<path d="M5 15v6"/>
|
||||
<path d="M2 18h3"/>
|
||||
<path d="M20 15v6h2"/>
|
||||
<path d="M13 21v-6l2 3l2 -3v6"/>
|
||||
<path d="M7.5 15h3"/>
|
||||
<path d="M9 15v6"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-html" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M13 16v-8l2 5l2 -5v8"></path>
|
||||
<path d="M1 16v-8"></path>
|
||||
<path d="M5 8v8"></path>
|
||||
<path d="M1 12h4"></path>
|
||||
<path d="M7 8h4"></path>
|
||||
<path d="M9 8v8"></path>
|
||||
<path d="M20 8v8h3"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".js"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-js" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M3 15h3v4.5a1.5 1.5 0 0 1 -3 0"/>
|
||||
<path d="M9 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1"/>
|
||||
</svg>
|
||||
{{- else if .HasExt ".css"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-css" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M8 16.5a1.5 1.5 0 0 0 -3 0v3a1.5 1.5 0 0 0 3 0"/>
|
||||
<path d="M11 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<path d="M17 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-javascript" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
|
||||
<path d="M7.5 8h3v8l-2 -1"></path>
|
||||
<path d="M16.5 8h-2.5a.5 .5 0 0 0 -.5 .5v3a.5 .5 0 0 0 .5 .5h1.423a.5 .5 0 0 1 .495 .57l-.418 2.93l-2 .5"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".json" ".json5" ".jsonc"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-json" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M20 16v-8l3 8v-8"/>
|
||||
<path d="M15 8a2 2 0 0 1 2 2v4a2 2 0 1 1 -4 0v-4a2 2 0 0 1 2 -2z"/>
|
||||
<path d="M1 8h3v6.5a1.5 1.5 0 0 1 -3 0v-.5"/>
|
||||
<path d="M7 15a1 1 0 0 0 1 1h1a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h1a1 1 0 0 1 1 1"/>
|
||||
</svg>
|
||||
{{- else if .HasExt ".ts"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-ts" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M9 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<path d="M3.5 15h3"/>
|
||||
<path d="M5 15v6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M20 16v-8l3 8v-8"></path>
|
||||
<path d="M15 8a2 2 0 0 1 2 2v4a2 2 0 1 1 -4 0v-4a2 2 0 0 1 2 -2z"></path>
|
||||
<path d="M1 8h3v6.5a1.5 1.5 0 0 1 -3 0v-.5"></path>
|
||||
<path d="M7 15a1 1 0 0 0 1 1h1a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h1a1 1 0 0 1 1 1"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".sql"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-type-sql" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M18 15v6h2"/>
|
||||
<path d="M13 15a2 2 0 0 1 2 2v2a2 2 0 1 1 -4 0v-2a2 2 0 0 1 2 -2z"/>
|
||||
<path d="M14 20l1.5 1.5"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sql" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 8a2 2 0 0 1 2 2v4a2 2 0 1 1 -4 0v-4a2 2 0 0 1 2 -2z"></path>
|
||||
<path d="M17 8v8h4"></path>
|
||||
<path d="M13 15l1 1"></path>
|
||||
<path d="M3 15a1 1 0 0 0 1 1h2a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-2a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".db" ".sqlite" ".bak" ".mdb"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 6m-8 0a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"></path>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"></path>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".eml" ".email" ".mailbox" ".mbox" ".msg"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-mail" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"/>
|
||||
<path d="M3 7l9 6l9 -6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path>
|
||||
<path d="M3 7l9 6l9 -6"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".crt" ".pem" ".x509" ".cer" ".ca-bundle"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-certificate" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 15m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"/>
|
||||
<path d="M13 17.5v4.5l2 -1.5l2 1.5v-4.5"/>
|
||||
<path d="M10 19h-5a2 2 0 0 1 -2 -2v-10c0 -1.1 .9 -2 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -1 1.73"/>
|
||||
<path d="M6 9l12 0"/>
|
||||
<path d="M6 12l3 0"/>
|
||||
<path d="M6 15l2 0"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M15 15m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
|
||||
<path d="M13 17.5v4.5l2 -1.5l2 1.5v-4.5"></path>
|
||||
<path d="M10 19h-5a2 2 0 0 1 -2 -2v-10c0 -1.1 .9 -2 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -1 1.73"></path>
|
||||
<path d="M6 9l12 0"></path>
|
||||
<path d="M6 12l3 0"></path>
|
||||
<path d="M6 15l2 0"></path>
|
||||
</svg>
|
||||
{{- else if .HasExt ".key" ".keystore" ".jks" ".p12" ".pfx" ".pub"}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-key" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1 -4.069 0l-.301 -.301l-6.558 6.558a2 2 0 0 1 -1.239 .578l-.175 .008h-1.172a1 1 0 0 1 -.993 -.883l-.007 -.117v-1.172a2 2 0 0 1 .467 -1.284l.119 -.13l.414 -.414h2v-2h2v-2l2.144 -2.144l-.301 -.301a2.877 2.877 0 0 1 0 -4.069l2.643 -2.643a2.877 2.877 0 0 1 4.069 0z"/>
|
||||
<path d="M15 9h.01"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1 -4.069 0l-.301 -.301l-6.558 6.558a2 2 0 0 1 -1.239 .578l-.175 .008h-1.172a1 1 0 0 1 -.993 -.883l-.007 -.117v-1.172a2 2 0 0 1 .467 -1.284l.119 -.13l.414 -.414h2v-2h2v-2l2.144 -2.144l-.301 -.301a2.877 2.877 0 0 1 0 -4.069l2.643 -2.643a2.877 2.877 0 0 1 4.069 0z"></path>
|
||||
<path d="M15 9h.01"></path>
|
||||
</svg>
|
||||
{{- else}}
|
||||
{{- if .IsSymlink}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-symlink" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 21v-4a3 3 0 0 1 3 -3h5"/>
|
||||
<path d="M9 17l3 -3l-3 -3"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 11v-6a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-9.5"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4 21v-4a3 3 0 0 1 3 -3h5"></path>
|
||||
<path d="M9 17l3 -3l-3 -3"></path>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M5 11v-6a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-9.5"></path>
|
||||
</svg>
|
||||
{{- else}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
|
||||
</svg>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
@@ -299,7 +255,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{html .Name}}</title>
|
||||
<link rel="canonical" href="{{.Path}}/" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -594,6 +549,8 @@ td .go-up {
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));
|
||||
justify-items: center;
|
||||
align-items: start;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
@@ -798,19 +755,19 @@ footer {
|
||||
</div>
|
||||
<a href="javascript:queryParam('layout', '')" id="layout-list" class='layout{{if eq $.Layout "list" ""}}current{{end}}'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-list" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
||||
<path d="M4 14m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
|
||||
<path d="M4 14m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
|
||||
</svg>
|
||||
List
|
||||
</a>
|
||||
<a href="javascript:queryParam('layout', 'grid')" id="layout-grid" class='layout{{if eq $.Layout "grid"}}current{{end}}'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||
<path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||
<path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||
<path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path>
|
||||
</svg>
|
||||
Grid
|
||||
</a>
|
||||
@@ -835,22 +792,22 @@ footer {
|
||||
{{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}}
|
||||
<a href="?sort=namedirfirst&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 14l-6 -6l-6 6h12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 14l-6 -6l-6 6h12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}}
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 10l6 6l6 -6h-12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 10l6 6l6 -6h-12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else}}
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon sort">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 14l-6 -6l-6 6h12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 14l-6 -6l-6 6h12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- end}}
|
||||
@@ -859,16 +816,16 @@ footer {
|
||||
<a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Name
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 14l-6 -6l-6 6h12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 14l-6 -6l-6 6h12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else if and (eq .Sort "name") (ne .Order "asc")}}
|
||||
<a href="?sort=name&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Name
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 10l6 6l6 -6h-12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 10l6 6l6 -6h-12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else}}
|
||||
@@ -879,11 +836,11 @@ footer {
|
||||
|
||||
<div class="filter-container">
|
||||
<svg id="search-icon" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/>
|
||||
<path d="M21 21l-6 -6"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path>
|
||||
<path d="M21 21l-6 -6"></path>
|
||||
</svg>
|
||||
<input type="search" placeholder="Search" id="filter" onkeyup='filter()'>
|
||||
<input type="text" placeholder="Search" id="filter" onkeyup='filter()'>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
@@ -891,16 +848,16 @@ footer {
|
||||
<a href="?sort=size&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Size
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 14l-6 -6l-6 6h12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 14l-6 -6l-6 6h12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else if and (eq .Sort "size") (ne .Order "asc")}}
|
||||
<a href="?sort=size&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Size
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 10l6 6l6 -6h-12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 10l6 6l6 -6h-12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else}}
|
||||
@@ -914,16 +871,16 @@ footer {
|
||||
<a href="?sort=time&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Modified
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 14l-6 -6l-6 6h12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 14l-6 -6l-6 6h12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else if and (eq .Sort "time") (ne .Order "asc")}}
|
||||
<a href="?sort=time&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
|
||||
Modified
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-caret-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 10l6 6l6 -6h-12"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 10l6 6l6 -6h-12"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{{- else}}
|
||||
@@ -942,8 +899,8 @@ footer {
|
||||
<td>
|
||||
<a href="..">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-corner-left-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M18 18h-6a3 3 0 0 1 -3 -3v-10l-4 4m8 0l-4 -4"/>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M18 18h-6a3 3 0 0 1 -3 -3v-10l-4 4m8 0l-4 -4"></path>
|
||||
</svg>
|
||||
<span class="go-up">Up</span>
|
||||
</a>
|
||||
@@ -1084,10 +1041,7 @@ footer {
|
||||
});
|
||||
document.querySelectorAll('.size').forEach(el => {
|
||||
const size = Number(el.dataset.size);
|
||||
const sizebar = el.querySelector('.sizebar-bar');
|
||||
if (sizebar) {
|
||||
sizebar.style.width = `${size/largest * 100}%`;
|
||||
}
|
||||
el.querySelector('.sizebar-bar').style.width = `${size/largest * 100}%`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dustin/go-humanize"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (fsrv *FileServer) directoryListing(ctx context.Context, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext {
|
||||
|
||||
@@ -16,22 +16,18 @@ package fileserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -55,21 +51,12 @@ respond with a file listing.`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files")
|
||||
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
|
||||
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
|
||||
cmd.Flags().StringP("listen", "", "", "The address to which to bind the listener")
|
||||
cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
|
||||
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", "", false, "Enable the access log")
|
||||
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "export-template",
|
||||
Short: "Exports the default file browser template",
|
||||
Example: "caddy file-server export-template > browse.html",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
_, err := io.WriteString(os.Stdout, BrowseTemplate)
|
||||
return err
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/operators"
|
||||
@@ -33,10 +36,6 @@ import (
|
||||
"github.com/google/cel-go/parser"
|
||||
"go.uber.org/zap"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -29,15 +29,17 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
|
||||
caddy.RegisterModule(FileServer{})
|
||||
}
|
||||
|
||||
@@ -60,23 +62,7 @@ func init() {
|
||||
// requested directory does not have an index file, Caddy writes a
|
||||
// 404 response. Alternatively, file browsing can be enabled with
|
||||
// the "browse" parameter which shows a list of files when directories
|
||||
// are requested if no index file is present. If "browse" is enabled,
|
||||
// Caddy may serve a JSON array of the dirctory listing when the `Accept`
|
||||
// header mentions `application/json` with the following structure:
|
||||
//
|
||||
// [{
|
||||
// "name": "",
|
||||
// "size": 0,
|
||||
// "url": "",
|
||||
// "mod_time": "",
|
||||
// "mode": 0,
|
||||
// "is_dir": false,
|
||||
// "is_symlink": false
|
||||
// }]
|
||||
//
|
||||
// with the `url` being relative to the request path and `mod_time` in the RFC 3339 format
|
||||
// with sub-second precision. For any other value for the `Accept` header, the
|
||||
// respective browse template is executed with `Content-Type: text/html`.
|
||||
// are requested if no index file is present.
|
||||
//
|
||||
// By default, this handler will canonicalize URIs so that requests to
|
||||
// directories end with a slash, but requests to regular files do not.
|
||||
@@ -435,13 +421,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
// GET and HEAD, which is sensible for a static file server - reject
|
||||
// any other methods (see issue #5166)
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
// if we're in an error context, then it doesn't make sense
|
||||
// to repeat the error; just continue because we're probably
|
||||
// trying to write an error page response (see issue #5703)
|
||||
if _, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); !ok {
|
||||
w.Header().Add("Allow", "GET, HEAD")
|
||||
return caddyhttp.Error(http.StatusMethodNotAllowed, nil)
|
||||
}
|
||||
w.Header().Add("Allow", "GET, HEAD")
|
||||
return caddyhttp.Error(http.StatusMethodNotAllowed, nil)
|
||||
}
|
||||
|
||||
// set the Etag - note that a conditional If-None-Match request is handled
|
||||
|
||||
@@ -371,5 +371,5 @@ func (rww *responseWriterWrapper) Write(d []byte) (int, error) {
|
||||
var (
|
||||
_ caddy.Provisioner = (*Handler)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||
_ http.ResponseWriter = (*responseWriterWrapper)(nil)
|
||||
_ caddyhttp.HTTPInterfaces = (*responseWriterWrapper)(nil)
|
||||
)
|
||||
|
||||
@@ -16,11 +16,10 @@ package caddyhttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
@@ -43,11 +42,7 @@ func init() {
|
||||
//
|
||||
// This listener wrapper must be placed BEFORE the "tls" listener
|
||||
// wrapper, for it to work properly.
|
||||
type HTTPRedirectListenerWrapper struct {
|
||||
// MaxHeaderBytes is the maximum size to parse from a client's
|
||||
// HTTP request headers. Default: 1 MB
|
||||
MaxHeaderBytes int64 `json:"max_header_bytes,omitempty"`
|
||||
}
|
||||
type HTTPRedirectListenerWrapper struct{}
|
||||
|
||||
func (HTTPRedirectListenerWrapper) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -61,7 +56,7 @@ func (h *HTTPRedirectListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser)
|
||||
}
|
||||
|
||||
func (h *HTTPRedirectListenerWrapper) WrapListener(l net.Listener) net.Listener {
|
||||
return &httpRedirectListener{l, h.MaxHeaderBytes}
|
||||
return &httpRedirectListener{l}
|
||||
}
|
||||
|
||||
// httpRedirectListener is listener that checks the first few bytes
|
||||
@@ -69,7 +64,6 @@ func (h *HTTPRedirectListenerWrapper) WrapListener(l net.Listener) net.Listener
|
||||
// to respond to an HTTP request with a redirect.
|
||||
type httpRedirectListener struct {
|
||||
net.Listener
|
||||
maxHeaderBytes int64
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener,
|
||||
@@ -80,23 +74,16 @@ func (l *httpRedirectListener) Accept() (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxHeaderBytes := l.maxHeaderBytes
|
||||
if maxHeaderBytes == 0 {
|
||||
maxHeaderBytes = 1024 * 1024
|
||||
}
|
||||
|
||||
return &httpRedirectConn{
|
||||
Conn: c,
|
||||
limit: maxHeaderBytes,
|
||||
r: bufio.NewReader(c),
|
||||
Conn: c,
|
||||
r: bufio.NewReader(c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpRedirectConn struct {
|
||||
net.Conn
|
||||
once bool
|
||||
limit int64
|
||||
r *bufio.Reader
|
||||
once sync.Once
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
// Read tries to peek at the first few bytes of the request, and if we get
|
||||
@@ -104,58 +91,53 @@ type httpRedirectConn struct {
|
||||
// like an HTTP request, then we perform a HTTP->HTTPS redirect on the same
|
||||
// port as the original connection.
|
||||
func (c *httpRedirectConn) Read(p []byte) (int, error) {
|
||||
if c.once {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
// no need to use sync.Once - net.Conn is not read from concurrently.
|
||||
c.once = true
|
||||
var errReturn error
|
||||
c.once.Do(func() {
|
||||
firstBytes, err := c.r.Peek(5)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
firstBytes, err := c.r.Peek(5)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
// If the request doesn't look like HTTP, then it's probably
|
||||
// TLS bytes and we don't need to do anything.
|
||||
if !firstBytesLookLikeHTTP(firstBytes) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the HTTP request, so we can get the Host and URL to redirect to.
|
||||
req, err := http.ReadRequest(c.r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Build the redirect response, using the same Host and URL,
|
||||
// but replacing the scheme with https.
|
||||
headers := make(http.Header)
|
||||
headers.Add("Location", "https://"+req.Host+req.URL.String())
|
||||
resp := &http.Response{
|
||||
Proto: "HTTP/1.0",
|
||||
Status: "308 Permanent Redirect",
|
||||
StatusCode: 308,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Header: headers,
|
||||
}
|
||||
|
||||
err = resp.Write(c.Conn)
|
||||
if err != nil {
|
||||
errReturn = fmt.Errorf("couldn't write HTTP->HTTPS redirect")
|
||||
return
|
||||
}
|
||||
|
||||
errReturn = fmt.Errorf("redirected HTTP request on HTTPS port")
|
||||
c.Conn.Close()
|
||||
})
|
||||
|
||||
if errReturn != nil {
|
||||
return 0, errReturn
|
||||
}
|
||||
|
||||
// If the request doesn't look like HTTP, then it's probably
|
||||
// TLS bytes, and we don't need to do anything.
|
||||
if !firstBytesLookLikeHTTP(firstBytes) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
// From now on, we can be almost certain the request is HTTP.
|
||||
// The returned error will be non nil and caller are expected to
|
||||
// close the connection.
|
||||
|
||||
// Set the read limit, io.MultiReader is needed because
|
||||
// when resetting, *bufio.Reader discards buffered data.
|
||||
buffered, _ := c.r.Peek(c.r.Buffered())
|
||||
mr := io.MultiReader(bytes.NewReader(buffered), c.Conn)
|
||||
c.r.Reset(io.LimitReader(mr, c.limit))
|
||||
|
||||
// Parse the HTTP request, so we can get the Host and URL to redirect to.
|
||||
req, err := http.ReadRequest(c.r)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't read HTTP request")
|
||||
}
|
||||
|
||||
// Build the redirect response, using the same Host and URL,
|
||||
// but replacing the scheme with https.
|
||||
headers := make(http.Header)
|
||||
headers.Add("Location", "https://"+req.Host+req.URL.String())
|
||||
resp := &http.Response{
|
||||
Proto: "HTTP/1.0",
|
||||
Status: "308 Permanent Redirect",
|
||||
StatusCode: 308,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Header: headers,
|
||||
}
|
||||
|
||||
err = resp.Write(c.Conn)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't write HTTP->HTTPS redirect")
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("redirected HTTP request on HTTPS port")
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
// firstBytesLookLikeHTTP reports whether a TLS record header
|
||||
|
||||
@@ -23,12 +23,11 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
// MatchRemoteIP matches requests by the remote IP address,
|
||||
|
||||
@@ -20,10 +20,9 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
// ServerLogConfig describes a server's logging configuration. If
|
||||
|
||||
@@ -25,17 +25,15 @@ import (
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -397,9 +395,7 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||
// security risk (cry) if PHP files end up being served
|
||||
// as static files, exposing the source code, instead of
|
||||
// being matched by *.php to be treated as PHP scripts.
|
||||
if runtime.GOOS == "windows" { // issue #5613
|
||||
reqPath = strings.TrimRight(reqPath, ". ")
|
||||
}
|
||||
reqPath = strings.TrimRight(reqPath, ". ")
|
||||
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
@@ -1393,7 +1389,9 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
|
||||
return matcherSet, nil
|
||||
}
|
||||
|
||||
var wordRE = regexp.MustCompile(`\w+`)
|
||||
var (
|
||||
wordRE = regexp.MustCompile(`\w+`)
|
||||
)
|
||||
|
||||
const regexpPlaceholderPrefix = "http.regexp"
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -254,6 +253,11 @@ func TestPathMatcher(t *testing.T) {
|
||||
input: "/FOOOO",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"*.php"},
|
||||
input: "/foo/index.php. .",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchPath{"/foo/bar.txt"},
|
||||
input: "/foo/BAR.txt",
|
||||
@@ -431,10 +435,8 @@ func TestPathMatcher(t *testing.T) {
|
||||
|
||||
func TestPathMatcherWindows(t *testing.T) {
|
||||
// only Windows has this bug where it will ignore
|
||||
// trailing dots and spaces in a filename
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
// trailing dots and spaces in a filename, but we
|
||||
// test for it on all platforms to be more consistent
|
||||
|
||||
req := &http.Request{URL: &url.URL{Path: "/index.php . . .."}}
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
)
|
||||
|
||||
// Metrics configures metrics observations.
|
||||
|
||||
@@ -19,9 +19,8 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/mastercactapus/proxyprotocol"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/mastercactapus/proxyprotocol"
|
||||
)
|
||||
|
||||
// ListenerWrapper provides PROXY protocol support to Caddy by implementing
|
||||
|
||||
@@ -19,11 +19,10 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -252,6 +251,5 @@ const pushedLink = "http.handlers.push.pushed_link"
|
||||
var (
|
||||
_ caddy.Provisioner = (*Handler)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||
_ http.ResponseWriter = (*linkPusher)(nil)
|
||||
_ http.Pusher = (*linkPusher)(nil)
|
||||
_ caddyhttp.HTTPInterfaces = (*linkPusher)(nil)
|
||||
)
|
||||
|
||||
@@ -39,10 +39,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// NewTestReplacer creates a replacer for an http.Request
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
package requestbody
|
||||
|
||||
import (
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user