254 Commits
Author SHA1 Message Date
Matthew HoltandCopilot 9fca4b8ab8 caddyhttp: keep runtime CEL matcher arguments literal
Fix report dff6e149-9cd3-47fb-8e21-b4d3d4bad807 by retaining string provenance through CEL matcher factories. Runtime strings must not become templates or globs on a second pass; static leaves retain their existing matcher syntax and caching.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00
Matthew HoltandCopilot 13d000e010 fileserver: anchor file-match paths relative to root
Use filepath.Rel rather than prefix trimming for both existing and fallback candidates. Preserve dotfiles and directory suffixes, and always expose a slash-prefixed path so rewrites and downstream path guards agree.

Addresses scan report 3188624e-2157-49e6-b3e0-8c5049b72269.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00
PerryLink a97bc460cf fileserver: hide the config directory when it is relative (#8398)
FileServer.Provision hides the data directory when it is relative (#8327),
because a relative data directory sits under the working directory and
holds private keys. The config directory falls back to the same relative
"./caddy" when no config home can be resolved, and it holds autosave.json,
which is the loaded configuration.

The two directories are resolved independently, so an absolute data
directory did not cover a relative config directory: with XDG_DATA_HOME set
and no HOME or XDG_CONFIG_HOME, the data directory is absolute while the
config directory is "./caddy" under the served root.

Both directories are now hidden through the existing fallback, each entry
added only once.

Refs #8381
2026-11-10 14:56:52 +00:00
Zen Dodd 3e05c4ddf0 fileserver: preserve root directory matching in try_files (#8391) 2026-11-10 03:54:23 -05:00
Kévin Dunglas fcfdef43c5 fileserver: apply slash rules to fallback entry (#8387)
* fileserver: apply slash rules to fallback entry

The literal candidate built for the final entry of first_exist_fallback cleaned the path without the rules of #8326 and #8352. With "{path}" as that entry, "/.env/" and "/.env/./" matched the regular file "/.env", bypassing path matchers that see the trailing slash.

Both candidate builders now share the same cleaning and splitting.

* fileserver: handle Windows separators in file matcher
2026-11-09 08:22:45 +10:00
Kévin Dunglas af4d0c37d4 fileserver: ignore case for literal hide entries (#8378)
On case-insensitive filesystems (APFS and NTFS by default), a request using another letter case opens a hidden file anyway: the Caddyfile auto-hidden by httpcaddyfile is served for /caddyfile.

Entries without glob characters now match regardless of letter case. Glob patterns keep their case-sensitive matching. On case-sensitive filesystems, a literal entry also hides names that differ from it only by case.
2026-11-08 21:52:49 +00:00
Kévin Dunglas b426776cc7 filesystems: forward optional fs interfaces (#8388)
wrapperFs embeds fs.FS, which hides the StatFS, ReadDirFS and
ReadFileFS methods of the wrapped filesystem. fs.Stat on
DefaultFileSystem did open, fstat and close instead of one stat,
for every fs.Stat in the file matcher and file_server, including the
literal-path case of fs.Glob.

GlobFS is not forwarded: OsFS.Glob uses filepath.Glob, which differs
from fs.Glob (Lstat for literal patterns, OS path separators).

Stat now succeeds on files and directories that cannot be opened, and
no longer blocks on FIFOs. The file matcher matches unreadable files
instead of skipping them, file_server picks an unreadable index file
(403) instead of trying the next one, and browse lists symlinks to
unreadable directories as directories.

BenchmarkFileMatcher, Apple M5 Pro, macOS, count=10:

                  before    after     delta
existing file     18.90µs   1.570µs   -91.70%
front controller  20.85µs   3.040µs   -85.42%

existing file     936 B     728 B     22 -> 18 allocs
front controller  1312 B    1520 B    35 -> 33 allocs
2026-11-09 07:45:31 +10:00
Kévin Dunglas 5bd0b9355d fileserver: reject regular files addressed with a trailing separator (#8379)
* fileserver: don't serve regular file when path ends by slash

With canonical URIs disabled, file_server trimmed the trailing slash before
stat and served the file, while path matchers see "/secret.env/" and skip
rules such as "path *.env". Return 404 (or pass through) instead.

* fileserver: trim trailing OS separator before stat

On Windows, SanitizedPathJoin appends a backslash for a path ending in "/",
which the "/" trim left in place. Stat then failed on a regular file, so
"/file/" returned 400 instead of the canonical redirect or a 404.

* fileserver: keep trailing separator of a filesystem root

Trimming it turned a root of `C:\` into `C:`, the current directory of the
drive, and a root of `\` or `/` into an empty path, so requests for "/"
listed or indexed the wrong directory, or failed.

* fileserver: log request path on trailing slash 404

The logged filename has its trailing separator trimmed, so it didn't show the slash that caused the 404.

* fileserver: check trailing slash on matcher path
2026-11-09 07:33:40 +10:00
Kévin Dunglas 9b07062e9b fileserver: test hide against Windows trailing dot and space aliases (#8377) 2026-11-07 06:30:59 -05:00
Kévin Dunglas 7145e5eba8 fastcgi: reject Win 8.3 aliases and alt data streams paths (#8356) 2026-11-04 14:34:04 -05:00
Kévin Dunglas b5d67d6540 fileserver: only match a dir when the pattern ends by / (#8352) 2026-11-04 10:45:35 -05:00
Steffen Busch 644dbe695d fileserver: configure plain browse time format (#8084) 2026-11-04 17:05:11 +10:00
D'Ali Ben Macha cce035d7ad fileserver: handle empty files in size try policies (#8322)
Use the chosen file info, not a zero size, to tell whether a candidate
was chosen, so smallest_size keeps an empty file and largest_size can
choose one.
2026-11-03 06:59:33 +00:00
Kévin Dunglas 9dd12fdb5d fileserver: hide the data directory when it is relative (#8327) 2026-11-01 15:57:49 -05:00
Kévin Dunglas aea046c978 fileserver: don't match regular file when path ends by slash (#8326) 2026-11-01 15:57:10 -05:00
pamnarioandClaude Opus 5.5 4ec5a5c39a Merge commit from fork
* fastcgi: derive SCRIPT_FILENAME from the normalized request path

Path matchers evaluate a normalized request path — MatchPath applies
normalizeWindowsPath and then CleanPath before comparing — but the FastCGI
transport split r.URL.Path verbatim. Caddy does not rewrite r.URL.Path itself,
so the two disagreed whenever the path contained dot segments after the split
token: /admin.php/../../other.php was matched as /other.php while
SCRIPT_FILENAME resolved to <root>/admin.php, and on Windows the same held for
/admin.php\..\other.php. A route gated by a path matcher could therefore execute
a script that matcher never evaluated.

php_fastcgi generates a rewrite route that normally normalizes the path first,
but that route can fail to match, so the transport establishes the invariant
itself rather than inheriting it. The reason it can fail to match is addressed
in the following commit; this change does not depend on it.

caddyhttp.NormalizedPath is added so a consumer acting on a path a matcher has
already judged derives it exactly as the matcher did, rather than reassembling
the two steps and risking drift.

Scope — two divergences of the same class are NOT addressed here:

  * SCRIPT_NAME remains a prefix of the request path, so a matcher naming a
    script exactly (path /admin.php) is still reachable via PATH_INFO at
    /admin.php/anything. Matchers protecting a script need a trailing wildcard
    regardless; that is inherent to CGI, not to this bug.
  * On Windows, NTFS 8.3 short names are not rejected here. fileserver's
    ServeHTTP rejects them (and ':' for ADS) precisely because matchers cannot
    see through them, so GET /ADMIN~1.PHP is judged as /admin~1.php by a
    path matcher while NTFS resolves SCRIPT_FILENAME to admin.php. Closing that
    means sharing fileserver's hasWindowsShortName across packages, which is a
    larger change than this fix; it is reported alongside this one and a patch
    is available on request.

Side effects: PATH_INFO and DOCUMENT_URI are now derived from the normalized
path, so repeated slashes are merged and "." segments removed (/index.php//a//b
yields PATH_INFO=/a/b rather than //a//b), and on Windows per-component trailing
dots and spaces are folded. An application routing on a raw PATH_INFO will see
this. An empty URL.Path stays empty rather than becoming ".". REQUEST_URI is
unchanged and still carries the original target.

Tests: a buildEnv regression over literal and percent-encoded dot segments,
covering that legitimate PATH_INFO is preserved and an empty path stays empty; a
Windows-only counterpart whose cases each fail without the Windows
normalization; and an end-to-end test of a basic_auth-protected php_fastcgi
route against a FastCGI responder reporting the SCRIPT_FILENAME it received. The
end-to-end test uses `index off` so no rewrite route exists, ensuring it
exercises the transport rather than the rewrite.

* fileserver: make first_exist_fallback fall back when the file is absent

The try_policy first_exist_fallback is documented to match unconditionally,
falling back to the last try_files entry when no earlier candidate exists. It
did not: candidates are built with fs.Glob, which returns only paths that exist,
so an absent final entry left the list empty and the fallback branch — nested
inside the loop over that list — was unreachable.

php_fastcgi hard-codes this policy so its generated rewrite route always runs;
when the fallback failed, that rewrite was silently skipped.

Keep the pattern itself as a candidate when nothing globbed. This also aligns
Linux with Windows, where globResults is populated without an existence check
and the fallback already fired.

Behaviour change worth calling out: on a php_fastcgi site whose root has no
index.php (or no file matching the configured index), the rewrite now always
runs, so a request for a path that exists neither on the filesystem nor as a PHP
script is proxied to the backend as <root>/index.php instead of falling through
to file_server and returning 404. That is what the documented fallback promises,
but for a site of standalone .php scripts plus static assets it is a visible
change: unmatched requests now reach PHP-FPM.

The other try policies still test existence explicitly, so a candidate that does
not exist is skipped — but note this applies to ANY pattern that expands to
nothing, not only a malformed glob: a file whose literal name contains glob
metacharacters can now match under first_exist, largest_size and smallest_size
where previously globbing found it only via expansion.

* fileserver: synthesize the literal candidate only for the fallback entry

Address review: the previous commit appended the pattern itself as a
candidate inside makeCandidates whenever globbing found nothing. Since
makeCandidates is shared by every try_policy, first_exist, largest_size,
smallest_size and most_recently_modified could then stat the literal
pattern, so a file whose name contains glob metacharacters (for example
"lit[1].txt") matched where it did not before.

Restore makeCandidates to its original behaviour and build the literal
candidate only for the final entry of first_exist_fallback, the one
entry that is documented to match unconditionally. Placeholders in that
entry are expanded without glob escaping, since nothing is globbed, so
a request for /a[b].txt falls back to /a[b].txt rather than /a\[b].txt.
The path is still cleaned and joined with SanitizedPathJoin, so it
cannot escape the root.

This supersedes the last paragraph of the previous commit message: the
other policies now behave exactly as they did before that commit. The
php_fastcgi behaviour change described there still applies.

Add a regression test that creates a file literally named "lit[1].txt"
and checks that only the fallback entry of first_exist_fallback matches
it by its literal name, including the split_path remainder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fastcgi: add license headers to new test files

Address review: phpfastcgi_test.go and fastcgi_windows_test.go were
added without the Apache license header used across the codebase. On
the Windows test, the header goes above the build constraint, matching
replacer_fuzz.go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fastcgi: avoid hardcoded Unix paths in dot-segment tests

Address review: the new tests compared SCRIPT_FILENAME against strings
with forward slashes ("/srv/other.php", a "/admin.php" suffix), but the
transport makes the root absolute and joins it with filepath.Join, so
on Windows the value is C:\srv\other.php and the assertions would fail.
Use a temporary directory as the root and build the expected paths with
filepath instead.

Also lower the bcrypt cost of the integration test's basic_auth hash to
the minimum. At cost 14 the authenticated request exceeded the 5s test
client timeout under -race.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 10:13:03 -06:00
Shane 69f22e2442 fileserver: return an empty listing for offsets past the end (#8213)
Signed-off-by: Shane Jonas <jonas.shane@gmail.com>
2026-10-14 01:36:53 +10:00
ammann d65583e6d2 fileserver: apply hide rules to browse entries and sidecars (#8155) 2026-10-08 13:26:26 +10:00
awss 96eadf18f6 fileserver: stat symlink targets by decoded path in browse listings (#8152)
directoryListing built each symlink's lookup path from the escaped
request path. In a directory whose name needs escaping, such as one with
a space, the stat failed, so directory symlinks were listed as files,
file symlinks showed the link's own size, and reveal_symlinks showed no
target.

Use the decoded path, which the function already computes for the page
title. The escaped path is still used for the page path and item URLs.
2026-10-05 22:42:50 +00:00
klokv 7ba5e147ac fileserver: treat a path below a regular file as not found (#8131)
Since #7017, every *fs.PathError from stat becomes fs.ErrInvalid, and
that includes ENOTDIR. So a request like /index.php/foo got 400 instead
of 404, and pass_thru no longer fell through to the next handler.

Map ENOTDIR to fs.ErrNotExist before that check. Other path errors, such
as NUL bytes, overly long names, or transient I/O failures, keep their
current handling.
2026-10-04 22:13:24 +10:00
Zen Dodd 90ccea768f build: bump all dependencies - (Go 1.26 floor) (#8056)
* build: bump deps

* chore: note HTTP/2 migration follow-ups
2026-09-30 10:54:12 -06:00
techknowlogick df77f8bde1 chore(deps): bump cel-go and switch import to new url (#8030) 2026-09-17 04:09:23 +03:00
Sash 97a08d8251 fileserver: reject non-integer browse file_limit (#7988) 2026-09-04 16:37:02 +10:00
David Carliez 3244ef4105 fileserver: reject short names in every path component (#7952)
* fileserver: reject short names in every path component
Signed-off-by: DavidCarliez <271374756+DavidCarliez@users.noreply.github.com>

* fileserver: validate short-name characters
Signed-off-by: DavidCarliez <271374756+DavidCarliez@users.noreply.github.com>

* fileserver: fail closed on extended short names
Signed-off-by: DavidCarliez <271374756+DavidCarliez@users.noreply.github.com>
2026-08-28 09:09:23 +00:00
Faiyaz Rahman 4974956b9c chore: fix lint errors from newer golangci-lint (#7958)
Signed-off-by: Faiyaz Rahman <faiyazrahman03@gmail.com>
2026-08-25 13:12:10 +10:00
Kévin Dunglas d6f7f18b04 Merge commit from fork
* fileserver: add failing test for calculateEtag collision

calculateEtag concatenates base36(mtime) and base36(size) with no
separator, so distinct (mtime, size) pairs can yield identical digit
strings and thus identical ETags.

* fileserver: prevent ETag collisions by separating mtime and size components

calculateEtag concatenated base36(mtime) and base36(size) with no
separator, so distinct (mtime, size) pairs could decode to the same
digit string and produce identical ETags.
2026-08-11 13:23:18 -06:00
Christian Mehlmauer 2adc763020 fileserver: speed up directory browsing for large directories (#7903)
* improve fileserver performance and allocations
* skip instead of fatal
* fix comment
* implement more suggestions
* use filesystems.OsFS instead of os.DirFS
* fmt
* remove outdated benchmarks
2026-08-01 00:38:04 +10:00
Matthew Holt 6ab855d3c4 browse: Update Caddy logo 2026-06-20 13:45:10 -06:00
Luccin Masirika 39c9a85f80 fileserver: append repeated hide subdirectives instead of overwriting (#7817)
Multiple `hide` subdirectives in a file_server Caddyfile block silently overwrote each other, so only the last one took effect. Append to the list instead, so repeated entries accumulate and imported snippets can compose with site-specific hides.
2026-06-13 22:44:19 +10:00
Sam Ottenhoff 997d3f6b0a encode: add standard benchmark and conformance harness (#7804)
This is a shared encode_test harness with HTML/JSON/JS/CSS payloads taken from caddyserver.com

Benchmarks:
- BenchmarkStandardEncodingPayloads: raw encoder NewEncoder/Write/Close path
- BenchmarkEncodeHandlerCorpus: full Encode.ServeHTTP middleware path
- Grid: 4 payloads × gzip levels 1/5/9 × zstd fastest/default/best
- Each subtest runs with 4 parallel workers; compare runs on MB/s and allocs/op

Conformance tests:
- Encoder contract: Reset, Flush, Close, and pool-style Reset-after-Close reuse
- Corpus HTTP encoding: Content-Encoding, Vary, ETag suffix, header stripping
- Response semantics: minimum_length, 304, HEAD, range, WebSocket bypass,
  If-None-Match rewrite, Cache-Control no-transform, content-type matcher rejection
2026-06-11 17:55:18 -06:00
Zen Dodd 88037f1666 chore: clean up wording and typo fixes (#7745)
* chore: clean up wording and typo fixes
* chore: ASCII -> alphanumeric in lexer for heredoc marker
2026-05-20 16:36:30 +10:00
Brett Bethke 0125ae39cc caddyhttp: omit Last-Modified for unusable mod times (#7740)
See #5548 and #7730
2026-05-20 16:19:11 +10:00
Rayan Salhabandcyphercodes a4a38c3e88 rewrite: escape file matcher paths before rewriting (#7683)
* fix: escape file matcher paths in rewrites

Preserve matched file paths containing literal '?' or '%' when try_files rewrites to http.matchers.file.relative.

* test: cover nested escaped try_files rewrite paths

* test: cover encoded slash try_files rewrite paths

* fix: assert file matcher placeholder as string

---------

Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
2026-05-11 17:16:33 -06:00
Zen Dodd d2172bea61 chore: Fix golangci-lint 2.12.1 findings (#7690) 2026-05-07 03:40:26 -04:00
Max Truxa 7586e68e27 fileserver: show symlink targets verbatim (#7579)
`reveal_symlinks` was exposing symlink targets as fully resolved absolute paths, even if the target is a relative path. With this change the link target is shown as-is, without resolving anything.
2026-04-15 04:49:30 +10:00
Oleh Konko | semantic verification for trust infra | LLM-augmented operations pipeline (precision-first, claim≤evidence, submit-human) | verify the payload, not the signer 566e710991 fileserver: document hide case-sensitivity (F-CADDY-FILESERVER-HIDE-CASE-001) (#7548) 2026-03-04 17:00:10 -05:00
Matthew Holt cb436f0a0e fileserver: Fix tests on Windows 2026-02-20 11:46:45 -07:00
Matthew Holt a2825c5dd9 fileserver: Replace \ with \\ in file matcher paths 2026-02-19 13:18:14 -07:00
Matt Holt 95941a71e8 chore: Add nolints to work around haywire linters (#7493)
* chore: Add nolints to work around haywire linters

* More lint wrangling
2026-02-17 16:52:54 -07:00
WeidiDeng 47f3e8f8dc use math/rand/v2 instead of math/rand (#7413) 2026-02-11 09:15:51 -07:00
Francis Lavoie 90972fbebc chore: Dumb prealloc lint fix (#7430) 2026-01-13 14:13:43 -05:00
WeidiDengandMatt Holt b462615439 fileserver: set Content-Length for precompressed files (#7251)
* set Content-Length for precompressed files

* Update modules/caddyhttp/fileserver/staticfiles.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-09-10 22:48:03 -06:00
WeidiDengandMatt Holt 67debd0e11 fileserver: set Range header for precompressed static files to force Content Length header to appear (#7042)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-22 08:23:13 -06:00
Luka T. KorošecandMatt Holt b9710c6af4 fileserver: Add a few doc lines about Etag file content (#7173)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-21 16:02:10 -06:00
Cédric Félizard 790f3e0885 fileserver: denote license for embedded JavaScript for LibreJS (#7127)
This commit adds support for LibreJS (https://en.wikipedia.org/wiki/GNU_LibreJS).

LibreJS would block this embedded JavaScript because its license is not stated in a machine-readable format.

Signed-off-by: Cédric Félizard <cedric@felizard.fr>
2025-07-16 11:49:49 -06:00
gopherorg 33c88bd2bb refactor: replace HasPrefix+TrimPrefix with CutPrefix (#7095)
Signed-off-by: gopherorg <gopherworld@icloud.com>
2025-06-27 22:04:09 +03:00
filipRatajczak 3b4d966fba fileserver: Add sort buttons in grid mode (#7089)
* [ADD] sort buttons in grid mode

* [CHANGE] replace spaces with tabs
2025-06-23 13:26:45 -06:00
Mohammed Al Sahaf e039a5bb5c chore: upgrade .golangci.yml and workflow to v2 (#6924)
* chore: upgrade .golangci.yml and workflow to v2

run `golangci-lint fmt`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* run `golangci-lint run --fix`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* more lint fixes

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* bring back comments to .golangci.yml

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* appease the linter some more

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* oops

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* use embedded structs

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* use embedded structs where they were used before

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* disable rule  `-QF1006`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* missed a spot

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-06-03 02:24:32 +03:00
Jimmy Lipham 94147caf31 fileserver: map invalid path errors to fs.ErrInvalid, and return 400 for any invalid path errors. (close #7008) (#7017) 2025-05-13 07:43:27 -06:00
Steffen Busch 54d03ced48 fileserver: Add support for .avif image format (#6988) 2025-04-28 08:32:59 -06:00