105 Commits
Author SHA1 Message Date
sam-kota 32bba15459 fastcgi: keep the php_fastcgi directory redirect on the same origin (#8285)
* fastcgi: keep the php_fastcgi directory redirect on the same origin
* fastcgi: build the directory redirect from the escaped original segment
2026-10-28 09:45:37 +00:00
Sulaiman Dauda b058e3f3f6 fastcgi: stop response bodies at the declared Content-Length (#8212)
* fastcgi: Stop response bodies at the declared Content-Length
* fastcgi: Use a cancellable test context in proxy tests
2026-10-26 19:32:45 +00:00
Krish Singh Hura e91867d35c reverseproxy: preserve request trailer values (#8246) 2026-10-19 09:16:58 +00: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
Jay Hemnani 1962b20401 fastcgi: Use 302 for a Location response without Status (#8209)
A CGI script that redirects by printing only a Location header got a
200 response with that header, which clients do not follow. RFC 3875
section 6.2.3 says the server sends 302 Found for this response, and
net/http/cgi, nginx and Apache do so.
2026-10-14 00:27:51 +10:00
zemamba 221ebc4503 fix: preserve FastCGI dial errors for retry (#8172)
Wrap backend dial failures as reverseproxy.DialError so non-GET requests can be retried when no connection was made.

AI-assisted implementation and test; reviewed by zemamba.

Signed-off-by: zemamba <zemamba@gmail.com>
2026-10-08 14:02:20 +10:00
Kévin DunglasandZen Dodd 2f34b936f5 feat: implement the Incremental header field (RFC 10036) (#8020)
Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-10-02 20:59:34 -04:00
c798d4c238 fastcgi: explain the 411 a body without a length gets (#7956)
A request whose body has no length of its own — Transfer-Encoding: chunked, or
HTTP/2 and HTTP/3 requests sent without the header — cannot be forwarded over
FastCGI until it has been buffered in full, because CGI/1.1 requires
CONTENT_LENGTH and php-fpm hangs when it is absent or wrong and the body is not
empty. When request_buffers is too small to hold the whole body, the length
stays unknown and the request is refused with 411.

The refusal said none of that. RoundTrip passes r.ContentLength to Post, which
is -1 for such a request, so the operator got a bare "411 Length Required" with
either no error at all or strconv's "invalid syntax" on a value they never
wrote. On #7386 that sent two people looking for the fault in their backend.

The 411 now carries the reason and the remedy, and the unknown-length case is
told apart from a genuinely malformed value. ParseUint becomes ParseInt plus an
explicit negative check, which is strictly tighter: values above MaxInt64 used
to be accepted and are unreachable anyway, since CONTENT_LENGTH is always
FormatInt of an int64 by the time Do sees it.

No status code changes. Every request that was refused before is still refused;
raising request_buffers is still what makes a large chunked body work.

Tests: the reachable path through Post, the unusable CONTENT_LENGTH values Do
itself guards against, an integration test driving a chunked body through a
fastcgi reverse_proxy over a unix socket at each side of the buffer boundary
(including request_buffers -1, which succeeds at any size and is the remedy),
and a boundary test recording that a body exactly the size of the buffer counts
as partial.

That last one is deliberately not changed here. Telling "exactly the limit"
apart from "more to come" costs a read that a paused stream may never answer,
and bufferedBody also backs response_buffers: measured against a body that
delivers exactly the limit and stops, the current code hands the buffered
prefix on immediately, while peeking one byte first delivers nothing at all.

Co-authored-by: Aditya <205600203+Rohilalala@users.noreply.github.com>
Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-09-23 09:31:54 -06:00
bzyy1024 45ba3278b5 fastcgi: fix HTTPoxy vulnerability (#7934)
* Implement HTTPoxy mitigation in FastCGI
Added HTTPoxy mitigation to prevent trusting client-supplied Proxy header for HTTP_PROXY environment variable.

* Implement test for HTTPoxy vulnerability protection
Add test to ensure HTTPoxy vulnerability is mitigated by dropping client-supplied Proxy headers.

* gofmt: format code
* revert unrelated gofmt change to replacer_test.go
2026-08-19 13:07:24 +10:00
Kévin Dunglas 947087cadd Merge commit from fork
Caddy's underscore header filter (GHSA-f59h-q822-g45g) only checked
for `_`. PHP folds `.` to `_` when registering $_SERVER keys the same
way CGI/FastCGI folds `-` to `_`, so a dotted alias (e.g. Remote.User)
survived the filter and collided with the legitimate hyphenated
header once it reached a PHP/FastCGI backend, bypassing forward_auth
copy_headers the same way the underscore alias did.

Extends the filter to drop `.` symmetrically, adds an
`expected_dot_headers` allowlist mirroring `expected_underscore_headers`,
and handles header names containing both separators (only an exact
allowlist entry is honored there, since a prefix glob's free-form
suffix can't be vetted for an embedded second separator).

Root cause identified by @iliaal in the FrankenPHP advisory
GHSA-49wc-4hcv-v58q.
2026-08-11 08:00:15 -06:00
Renich Bon Ciric 546752e214 feat(fastcgi): populate SERVER_ADDR by default (#7912)
* feat(fastcgi): populate SERVER_ADDR by default

Populate the SERVER_ADDR FastCGI environment variable using the local socket address of the incoming connection from the request context. This improves out-of-the-box compatibility for PHP applications that rely on $_SERVER['SERVER_ADDR'] (such as legacy frameworks and custom telemetry packages) when migrating from Nginx or Apache.

Co-developed-by: Gemini AI <renich+gemini@woralelandia.com>
Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>

* fix(fastcgi): validate SERVER_ADDR IP format and add test coverage

Validate that SERVER_ADDR is only populated when the local address parses as a valid IP address, preventing Unix socket paths from being assigned if SplitHostPort fails. Add test cases covering IPv4, IPv6, missing context, Unix sockets, and explicit overrides.

Co-developed-by: Gemini AI <renich+gemini@woralelandia.com>
Signed-off-by: Rénich Bon Ćirić <renich@woralelandia.com>
2026-08-10 15:24:13 +10:00
Kévin Dunglas 3eb8e48ff0 Merge commit from fork
* feat: drop headers with underscore in their names

* feat: Caddyfile binding and tests for underscore-in-header drop

Add the `allow_underscore_in_headers` global server option, refine the
doc comment, and cover the filter end-to-end: server-level unit tests
(drop, opt-out, debug log, RFC-7230 space rejection), a fastcgi unit
test for the trimmed header name replacer, and forward_auth integration
tests for both the default-drop and opt-out paths.

* remove allow_underscore_in_headers option for now
2026-05-29 11:37:17 -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
Matthew Holt 9c78b97f9e fastcgi: Fix lint 2026-05-08 10:46:28 -06:00
Kévin Dunglas fb324331f4 Merge commit from fork
Both fallbacks in splitPos relied on golang.org/x/text/search with
search.IgnoreCase, which performs Unicode equivalence matching far beyond
ASCII case folding. Combined with the validated-ASCII guarantee on every
SplitPath entry, that fallback turned non-PHP filenames into PHP scripts:

- when the inner loop hit a non-ASCII byte and the IndexString fallback
  returned -1, the loop broke without resetting match=false, so a stale
  match=true caused a non-existent .php to be reported (PoC:
  "/name.<U+00A1>.txt").
- search.IgnoreCase folded fullwidth, mathematical and circled letters
  onto ASCII, so "/shell.<math sans-serif php>",
  "/shell.<fullwidth p>hp", "/shell.<circled php>" were all detected as
  ".php" files.

Replace the fallback with strict byte-level ASCII case-insensitive
matching: any byte >= utf8.RuneSelf in the path can never be part of a
match, since SplitPath entries are validated ASCII-only and lower-cased
in Provision(). This keeps the hot path branch-light and removes the
x/text/search dependency from the main module.

Reported against FrankenPHP as GHSA-3g8v-8r37-cgjm and
GHSA-v4h7-cj44-8fc8. The vulnerable function in this module was adapted
from the same FrankenPHP code.
2026-05-07 13:59:42 -06:00
Oleksandr Redko 72eaf2583a chore: Enable modernize linter (#7519) 2026-02-26 14:01:35 -07:00
WeidiDeng 47f3e8f8dc use math/rand/v2 instead of math/rand (#7413) 2026-02-11 09:15:51 -07:00
Kévin Dunglas 7c28c0c07a Merge commit from fork
* fix: FastCGI split SCRIPT_NAME/PATH_INFO confusion

* fix comment
2026-02-10 11:52:36 -07:00
WeidiDeng a6da1acdc8 reverse_proxy: use interfaces to modify the behaviors of the transports (#7353) 2025-11-17 09:51:37 -07:00
Cooper de Nicola 895b56063a chore: fix golangci-lint error G602 in caddyhttp (#7334) 2025-11-03 03:04:55 +00:00
Mohammed Al Sahaf 25be2f26fc chore: ugh, lint fix... (#7275)
* chore: ugh, lint fix...

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

* more lint fixes

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

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-09-26 03:14:48 -04:00
tongjicoder 5b2eb66418 Use slices.Contains to simplify code (#7039)
Signed-off-by: tongjicoder <tongjicoder@icloud.com>
2025-05-31 12:03:06 -06:00
Kévin Dunglas 1f35a8a402 fastcgi: improve parsePHPFastCGI docs (#6779) 2025-01-09 11:54:44 -07:00
WeidiDengandKévin Dunglas 6790c0e38a fastcgi: check for CONTENT_LENGTH when sending requests (#6661)
* fastcgi: check for CONTENT_LENGTH when sending requests

* order imports

* use strconv.ParseUint instead of strconv.ParseInt

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2024-12-18 00:22:12 +00:00
Kévin Dunglas efd9251ad3 fileserver: Add first_exist_fallback strategy for try_files (#6699)
* feat: add first_exist_or_fallback strategy for try_files

* fix tests

* linter
2024-12-03 05:44:49 -07:00
Francis Lavoie b116dcea3d caddyhttp: Add {?query} placeholder (#6714)
* caddyhttp: Add `{prefixed_query}` placeholder

* fastcgi: Preserve query during canonical redirect

* Use orig_uri instead for the redirect, shorter Caddyfile shortcut
2024-12-02 08:06:38 -05:00
Kévin Dunglas eddbccd298 fastcgi: remove dir redirection when useless in php_fastcgi (#6698)
* perf: remove dir redirection when useless in php_fastcgi

* fix test

* review

* fix

* fix

* simplify

* simplify again

* restore test

* add test
2024-11-21 10:38:31 -07:00
Francis Lavoie 315715e90f core: Implement FastAbs to avoid repeated os.Getwd calls (#6687)
* core: Implement FastAbs to avoid repeated os.Getwd calls

* Lint

* Rename files
2024-11-13 03:55:51 -05:00
Kévin Dunglas f4bf4e0097 perf: use zap's Check() to prevent useless allocs (#6560)
* perf: use zap's Check() to prevent useless allocs

* fix

* fix

* fix

* fix

* restore previous replacer behavior

* fix linter
2024-09-13 11:16:37 -06:00
Matt Holt 81413caea2 caddytls: Upgrade ACMEz to v2; support ZeroSSL API; various fixes (#6229)
* WIP: acmez v2, CertMagic, and ZeroSSL issuer upgrades

* caddytls: ZeroSSLIssuer now uses ZeroSSL API instead of ACME

* Fix go.mod

* caddytls: Fix automation related to managers (fix #6060)

* Fix typo (appease linter)

* Fix HTTP validation with ZeroSSL API
2024-04-13 21:31:43 -04:00
Yolan RomaillerandFrancis Lavoie 2fe69a828f chore: enabling a few more linters (#5961)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2024-01-25 15:24:58 +00:00
Francis Lavoie 750d0b8331 caddyfile: Normalize & flatten all unmarshalers (#6037) 2024-01-23 19:36:59 -05:00
a c839a98ff5 filesystem: Globally declared filesystems, fs directive (#5833) 2024-01-13 20:12:43 +00:00
Jacob Gadikian d6f86cccf5 ci: use gci linter (#5708)
* use gofmput to format code

* use gci to format imports

* reconfigure gci

* linter autofixes

* rearrange imports a little

* export GOOS=windows golangci-lint run ./... --fix
2023-08-14 09:41:15 -06:00
Jacob Gadikian b32f265eca ci: Use gofumpt to format code (#5707) 2023-08-07 19:40:31 +00:00
eanavitarte c8032867b1 fastcgi: Fix capture_stderr (#5515) 2023-05-04 00:40:49 +00:00
Dave HendersonandFrancis Lavoie f0e3981774 logging: Add traceID field to access logs when tracing is active (#5507)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2023-04-27 02:46:41 +00:00
Francis Lavoie 53b6fab125 caddyfile: Stricter parsing, error for brace on new line (#5505) 2023-04-20 18:43:51 +00:00
Francis Lavoie 960150bb03 caddyfile: Implement heredoc support (#5385) 2023-02-26 00:34:27 +00:00
Matthew Holt e07a267276 caddytest: Revise sleep durations
Attempt to reduce flakiness a bit more

Test suite needs to be rewritten.
2022-10-05 11:40:41 -06:00
Matthew Holt f7c1a51efb fastcgi: Redirect using original URI path (fix #5073) 2022-09-23 14:36:38 -06:00
Matthew Holt e43b6d8178 core: Variadic Context.Logger(); soft deprecation
Ideally I'd just remove the parameter to caddy.Context.Logger(), but
this would break most Caddy plugins.

Instead, I'm making it variadic and marking it as partially deprecated.
In the future, I might completely remove the parameter once most
plugins have updated.
2022-09-16 16:55:36 -06:00
WeidiDengandMatthew Holt 83b26975bd fastcgi: Optimize FastCGI transport (#4978)
* break up code and use lazy reading and pool bufio.Writer

* close underlying connection when operation failed

* allocate bufWriter and streamWriter only once

* refactor record writing

* rebase from master

* handle err

* Fix type assertion

Also reduce some duplication

* Refactor client and clientCloser for logging

Should reduce allocations

* Minor cosmetic adjustments; apply Apache license

* Appease the linter

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-09-02 16:57:55 -06:00
fleandroandflga f2a7e7c966 fastcgi: allow users to log stderr output (#4967) (#5004)
Co-authored-by: flga <flga@users.noreply.github.com>
2022-09-02 00:02:48 -04:00
WilczyńskiT 2642bd72b7 Replace strings.Index usages with strings.Cut (#4930) 2022-08-04 11:17:35 -06:00
Matt Holt 17ae5acaba cmd: Use newly-available version information (#4931) 2022-08-04 11:16:59 -06:00
Francis Lavoie f6900fcf53 reverseproxy: Support performing pre-check requests (#4739) 2022-05-06 10:50:26 -04:00
Francis Lavoie c8f2834b51 fastcgi: Protect against requests with null bytes in the path (#4614) 2022-03-07 10:06:33 -07:00
ttys3 de490c7cad fastcgi: Set SERVER_PORT to 80 or 443 depending on scheme (#4572) 2022-03-02 11:24:16 -07:00
Francis LavoieandMohammed Al Sahaf 8e5aafa5cd fastcgi: Fix a TODO, prevent zap using reflection for logging env (#4437)
* fastcgi: Fix a TODO, prevent zap using reflection for logging env

* Update modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2021-12-02 13:23:19 -07:00