2634 Commits

Author SHA1 Message Date
SillyZir
54a6b8bc9e caddyconfig: Register nested named-route invokes transitively (#7898) 2026-07-30 10:55:49 +00:00
Kévin Dunglas
3be8dabc89 caddyhttp: use canonical header key casing to avoid re-canonicalization (#7911)
Header.Get/Set re-canonicalize and allocate whenever the passed key
isn't already in canonical MIME header form. Sec-WebSocket-Key,
WWW-Authenticate, and content-type all miss the fast path; switch to
their canonical forms (Sec-Websocket-Key, Www-Authenticate,
Content-Type).

Enable the canonicalheader linter to catch future regressions,
excluded in test files since those already use non-canonical casing
in several places without a perf-sensitive path behind them.
2026-07-30 20:40:35 +10:00
Saleh
c96bca1269 rewrite: preserve non-canonical path encoding after uri replace (#7907)
changePath cleared RawPath whenever it equalled url.Path, which
discarded a valid non-canonical percent-encoding produced by a
replacement. This compare `RawPath` against the default escaping of Path
instead.
2026-07-26 08:49:44 +10:00
Salynn
c5b66cf8ea Set the QUIC InitialPacketSize to 1200 bytes (#7886)
quic-go defaults the `InitialPacketSize` to 1280 bytes. This is used for
the size of the payload plus UDP header. When quic-go creates its
initial handshake packet, it pads it to this size as an optimization
for the anti-amplification limit (which does not really apply to
Caddy's server connections).

Tailscale uses an MTU of 1280 (the minimum IPv6 MTU) because it is a
tunnel operating in unknown environments, possibly inside other
tunnels.

When wrapped in an IP header (either v4 or v6), the 1280 byte QUIC packet
constructed by quic-go exceeds the Tailscale MTU and gets dropped. This
makes it impossible to respond to an HTTP3 connection attempt over
Tailscale when using the default `InitialPacketSize`.

We explicitly set the `InitialPacketSize` to 1200 (its minimum) to support
HTTP3 connections over Tailscale and other low MTU connections.

Once the connection is established, quic-go can perform MTU discovery to
increase the packet size to the maximum supported by the connection, so
any throughput loss due to the lowered `InitialPacketSize` is
short-lived.
2026-07-26 08:48:25 +10:00
dependabot[bot]
ff6da1216b build(deps): bump google.golang.org/grpc from 1.81.1 to 1.82.1 (#7908)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.81.1 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.81.1...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 10:40:28 +00:00
Zen Dodd
cba4f9aabb deps: update GitHub Actions and Go modules (#7876)
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-07-25 10:32:30 +00:00
Mohammed Al Sahaf
31daa220fc log: support d (day) in roll_interval directive (#7900)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-07-22 01:39:33 +03:00
Herman Slatman
9738f49b95 pki: Handle error immediately after reading root from disk (#7896)
In https://github.com/caddyserver/caddy/pull/7057, the behavior
of `KeyPair.Load` was changed to return a certificate chain. While
the change was primarily meant for intermediates, it also affected
how a root (or roots) were loaded. The existing error handling
logic relied on a shared `err != nil` check, but with the new
behavior there's no guarantee that there's actually a root in
the PEM file on disk.

This commit handles the error immediately after reading the PEM
from disk.

Fixes: #7895
2026-07-19 23:59:46 -04:00
futurehua
93c0721156 chore: fix sabotage spelling in nolint comments (#7892)
Signed-off-by: futurehua <futurehua@outlook.com>
2026-07-18 08:14:38 +00:00
Pichu Chen
986753a7c6 logging: hash query parameter values in QueryFilter (#7884) 2026-07-15 16:38:58 +00:00
a
873fac5fc0 caddyfile: treat quoted braces as literal arguments (#7875) 2026-07-12 10:33:32 +10:00
TowyTowy
b2be548275 reverseproxy: fix broken reservoir sampling in random_choose policy (#7873)
The random_choose selection policy is meant to implement
power-of-d-choices: sample d available upstreams uniformly, then pick
the least-loaded of that sample. The sampling loop, however, was not a
correct reservoir sample (Algorithm R): it never filled the first k
reservoir slots unconditionally, instead writing every candidate to a
random slot j = rand(i+1), which can evict an earlier candidate while
leaving another slot nil. It also derived j from the upstream's index
in the pool rather than from the number of available upstreams seen,
skewing the sample whenever unavailable upstreams precede available
ones.

As a result the reservoir frequently held fewer than min(k, available)
upstreams, so the least-load comparison often never happened. Most
notably, with two upstreams and 'random_choose 2' (the canonical
power-of-two-choices setup), half of all requests were routed to the
more-loaded upstream even when it was saturated and the other idle --
identical behavior to plain 'random'. The nil slots this leaves behind
were also the cause of the panic reported in #3810, which was patched
by skipping nils in leastRequests rather than by fixing the sampling.

Replace the loop with a standard Algorithm R reservoir sample over the
available upstreams: fill the first k slots, then replace a random slot
with probability k/n. Every available upstream is now sampled uniformly
and the reservoir always holds min(k, available) candidates.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:25:31 +10:00
techknowlogick
b2693fb63a build(deps): bump cel-go from v0.28.1 to v0.29.2 (#7872) 2026-07-12 09:17:08 +10:00
TowyTowy
c1907df2ed intercept: fix misleading handle_response error for extra matcher args (#7871)
Same obsolete two-argument check as the reverse_proxy one removed in
#7869: it referenced the long-removed inline status-code-replacement
syntax and pointed users at replace_status even when no status
replacement was involved. The generic check now rejects all
excess-argument cases with an accurate message.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:16:34 +10:00
Ta Duc Thien
c6180a0852 caddyhttp: fix path_regexp (MatchPathRE) Windows backslash bypass (#7858)
* caddyhttp: normalize Windows path in path_regexp matcher, shared with path matcher

Apply the same Windows path normalization to MatchPathRE that MatchPath already had, and factor it into a shared normalizeWindowsPath helper so both matchers use one implementation.

Also strip trailing dots and spaces per path component (not only at the end of the whole path), matching how Windows resolves paths; this fixes the same gap in the path matcher too. Adds regression tests for both matchers.

See #5613.

* caddyhttp: normalize trailing dots/spaces in escaped-path branch too

The MatchPath escaped-path branch (taken when a matcher pattern contains
'%') only folded backslash separators via windowsEscapedPathSeparatorRepl;
it skipped the per-component trailing dot/space normalization now applied
on the decoded branch. On Windows this left a bypass: a matcher such as
`path /private%2f*` could be evaded by GET /private.%5csecret.txt, since
`private.` and `private` resolve to the same directory on NTFS.

Add normalizeWindowsEscapedPath, which trims trailing dots and spaces —
literal ("." / " ") and percent-encoded ("%2e" / "%20") — from every
component in raw/escaped space, splitting on both '/' and encoded '%2f'
separators while preserving them, and leaving "." / ".." for CleanPath.
Regression tests cover the literal and percent-encoded variants against a
'%'-containing matcher.

---------

Co-authored-by: thientd <thien.taduc@ninhthanh.com>
2026-07-11 08:37:44 +10:00
WeidiDeng
1830809afe reverseproxy: save dial info in a context key instead of a variable key to avoid race conditions when forward_auth is used (#7859) 2026-07-10 09:37:07 -06:00
TowyTowy
945d199724 reverseproxy: fix misleading handle_response error for extra matcher args (#7869)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 05:40:23 +00:00
alhuda
75c988d118 reverseproxy: compare sticky-session cookie hash in constant time (#7853) 2026-07-10 09:16:25 +10:00
Kévin Dunglas
4e62095245 core: preserve metrics registry in Context.WithValue (#7861)
* core: preserve metrics registry in Context.WithValue

Context.WithValue rebuilt the Context without copying the unexported
metricsRegistry field, so any module provisioned under a context
derived via WithValue saw a nil registry from GetMetricsRegistry().

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 04:52:36 +10:00
alhuda
08ad064160 caddyhttp: fix escaped path matcher over-matching longer paths (#7828) 2026-07-08 04:29:44 +10:00
Kévin Dunglas
13a4c3f43c caddyhttp: add URL pattern request matcher (#7787)
* caddyhttp: add url_pattern request matcher

Match requests against a URLPattern (https://urlpattern.spec.whatwg.org/),
supporting named groups, wildcards and regexp components beyond the path
matcher. Relative patterns match any origin; absolute patterns or base_url
scope to scheme and host.

Exposes a url_pattern CEL function and publishes captured groups as
{http.url_pattern.<component>.<group>} placeholders.

* caddyhttp: add Caddyfile adapt test for url_pattern matcher
2026-06-22 10:25:43 -06:00
yintaisha
f450068460 http: normalize method names to uppercase in MatchMethod.Provision (#7832)
Signed-off-by: yintaisha <yintaishan@outlook.com>
2026-06-21 08:19:21 +00:00
Matthew Holt
51a4bde176 Update human and agent contributing guidelines 2026-06-20 14:19:24 -06:00
Matthew Holt
5760382213 caddyhttp: Clean up variable scope in vars matcher 2026-06-20 13:45:10 -06:00
Matthew Holt
30f0ddd912 caddyhttp: Document dropping underscore headers 2026-06-20 13:45:10 -06:00
Matthew Holt
6ab855d3c4 browse: Update Caddy logo 2026-06-20 13:45:10 -06:00
wangdongyong
69d6ace32e tracing: fix BatchSpanProcessor goroutine leak on config reload (#7826)
* tracing: fix BatchSpanProcessor goroutine leak on config reload

When the `tracing` directive is enabled, each config reload leaks one
`go.opentelemetry.io/otel/sdk/trace.(*batchSpanProcessor).processQueue`
goroutine. On a server that reloads frequently (e.g. polling a remote
config source every few seconds) this accumulates into tens of thousands
of leaked goroutines over time.

A goroutine dump shows many identical stacks:

```
goroutine ... [select]:
go.opentelemetry.io/otel/sdk/trace.(*batchSpanProcessor).processQueue(...)
	.../sdk/trace/batch_span_processor.go:327
go.opentelemetry.io/otel/sdk/trace.NewBatchSpanProcessor.func2()
	.../sdk/trace/batch_span_processor.go:129
created by go.opentelemetry.io/otel/sdk/trace.NewBatchSpanProcessor
	.../sdk/trace/batch_span_processor.go:127
```

`tracing` keeps a global, reference-counted `TracerProvider` so it can be
reused across reloads (`tracerProvider.getTracerProvider`). Caddy reloads
provision the new config *before* cleaning up the old one, so the counter
never drops to 0 and the provider is correctly reused — `Shutdown` is
never called, by design.

The problem is on the caller side in `newOpenTelemetryWrapper`:

```go
traceExporter, err := autoexport.NewSpanExporter(ctx)
...
tracerProvider := globalTracerProvider.getTracerProvider(
    sdktrace.WithBatcher(traceExporter),   // evaluated on every reload
    sdktrace.WithResource(res),
)
```

`sdktrace.WithBatcher(e)` is `WithSpanProcessor(NewBatchSpanProcessor(e))`,
and `NewBatchSpanProcessor` **starts its `processQueue` goroutine eagerly at
construction time** — not when the option is applied. The option is built
on every `Provision` (every reload), but `getTracerProvider` only applies
it when it actually creates a new provider (`t.tracerProvider == nil`). On
the reuse path the option is silently discarded, so the just-started
BatchSpanProcessor goroutine is orphaned: it is never registered with any
provider and therefore never shut down. Result: one leaked goroutine (plus
a leaked exporter) per reload.

Defer construction of the exporter/batcher until a new provider is actually
needed. `getTracerProvider` now takes a `buildOpts` factory that is invoked
only on the create path, so nothing with a side effect is built on the
reuse path.

The reference counter is now incremented only after the provider is
successfully obtained, preserving the previous semantics where a failed
exporter creation did not affect the counter.

- `Test_tracersProvider_buildOptsOnlyOnCreate` — asserts `buildOpts` runs
  exactly once across one create + five reuses (the regression guard).
- `Test_tracersProvider_buildOptsError` — asserts that on a build error the
  provider stays nil and the counter is not incremented.
- Existing tracing tests updated for the new signature and still pass.

Verified manually with a reload loop: before the fix, 50 reloads leaked 50
`processQueue` goroutines; after the fix, 0 are leaked while the provider is
still reused (counter stays at 1).

* add test for buldOpts error path

---------

Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-06-18 11:42:58 -06:00
Saleh
d2e0ad1e92 reverseproxy: log status 499 instead of 0 when client disconnects (#7827) 2026-06-18 13:31:02 +10:00
Matthew Holt
ab56721a73 Remove -v from tests
Also add release notes to TODO for release proposal flow
2026-06-17 10:38:00 -06:00
Matt Holt
4dbe0a93be readme: Update logo
Signed-off-by: Matt Holt <mholt@users.noreply.github.com>
2026-06-16 23:39:45 -06:00
alhuda
ae9bc028e6 rewrite: scope keyed query replace to its named key (#7818)
* rewrite: scope keyed query replace to its named key

* rewrite: cover keyed search_regexp query replace in test

* rewrite: provision query replace test via Provision path
2026-06-16 01:01:05 +10: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
long.black
16235cced5 intercept: fix replace_status being silently dropped (#7810)
* test: add failing tests for intercept replace_status (#7805)

Add integration tests that verify replace_status actually substitutes
the HTTP status code sent to the client. Currently these tests fail
because replace_status is silently a no-op due to value-receiver
boxing and shouldBuffer returning false.

Tests added:
- TestInterceptReplaceStatusWithMatcher: 500 -> 200 with @err matcher
- TestInterceptReplaceStatusWithoutMatcher: 403 -> 200 unconditionally
- TestInterceptReplaceStatusNotMatched: 200 passes through unchanged

* fix: make intercept replace_status actually substitute the status code

Fix #7805: replace_status was silently a no-op because:
1. shouldBuffer returned false when a replacement status was set,
   causing the original status to be streamed directly to the wire
2. The value-receiver WriteHeader method operated on a stale copy

The fix:
- shouldBuffer now returns true when replace_status matches, so
  the response is buffered instead of streamed
- After next.ServeHTTP returns, if routes are nil (replace_status
  only), write the substituted status and buffered body to the client

The interceptedResponseHandler.WriteHeader substitution branch is no
longer needed for this path since substitution happens post-ServeHTTP.

* refactor: remove dead WriteHeader method and resolved TODO

The value-receiver WriteHeader on interceptedResponseHandler was
unreachable dead code — the substitution is now handled post-ServeHTTP
via buffering. Remove it along with the TODO comment that noted
status code replacement was unfinished.

* style: apply nit suggestions from dunglas code review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:50:58 -06:00
alhuda
52dc6709fb rewrite: fix wrong index check in trimPathPrefix (#7812) 2026-06-12 14:11:21 -06:00
Dionis Ramadani
25b3eab6db Merge commit from fork
* fix(reverseproxy): hop-by-hop header fix

* test(headers): Added unit tests for hop-by-hope header behavior
2026-06-12 12:39:01 -06:00
Bluegate Studio
fcba554d65 caddyhttp: New expected_underscore_headers server option (#7809)
* caddyhttp: restore allow_underscore_in_headers server option (#7808)

* caddyhttp: mark insecure_allow_underscore_in_headers as EXPERIMENTAL

* caddyhttp: replace underscore bool with expected_underscore_headers allowlist

* fix gofmt alignment in serveroptions.go

* caddyhttp: drop repeated allowlisted underscore headers

* caddyhttp: add tests for repeated-value drop and variant-drop logging
2026-06-11 22:26:21 -06: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
Rhul
0f7f8e9cf6 forwardauth: error on duplicate uri subdirective (#7814) 2026-06-10 23:31:04 -04:00
Matthew Holt
4fd8c87f56 caddyhttp: Default max_header_bytes to 16 KiB 2026-06-09 12:07:27 -06:00
Rhul
55b3397a2d reverseproxy: validate on weighted_round_robin loadbalancing policy (#7807)
* reverseproxy: validate on weighted_round_robin policy

Validate that weighted_round_robin has a non-zero total weight.
This prevents configurations such as:
    weighted_round_robin 0 0
from being accepted and causing a divide-by-zero panic during request handling.

* test: validation test on zero weight upstreams.

* test: provision called instead of totalweight setting

* reverseproxy: validate on negative upstream weights

* test: regression test on weighted_round_robin selection policy
2026-06-08 02:18:20 +10:00
Micky
d3986f824d Add missing "is" (#7802)
Signed-off-by: Micky <60691199+AliMickey@users.noreply.github.com>
2026-06-06 21:02:51 +00:00
Y.Horie
d730df2a83 cmd: colored error message in WrapCommandFuncForCobra (#7760) (#7768)
Signed-off-by: Y.Horie <u5.horie@gmail.com>
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-06-04 21:41:35 -04:00
Rhul
3b7bde8f25 httpcaddyfile: error on duplicate named_routes (#7800)
* fix: error on duplicate named_routes

Fixes issue #7798

Validate named route names before inserting them into the
named route map.

This prevents later definitions from overwriting existing
named routes and returns an error when a route name is
defined more than once.

* test: add test for duplicate named_routes
2026-06-04 14:55:08 -04:00
Muhammad Syafri, S.Kom
915793f6e0 caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names (#7782)
* caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names

{http.request.proto} exposes Go's raw http.Request.Proto field which
returns HTTP/2.0 and HTTP/3.0 for HTTP/2 and HTTP/3 respectively.
These strings are non-standard since the specs define them as HTTP/2
and HTTP/3.

To preserve backward compat (especially CGI/FastCGI expectations),
{http.request.proto} is kept as-is. A new {http.request.proto_name}
placeholder is introduced that normalises the version string to the
spec-defined form:
  HTTP/2.0 -> HTTP/2
  HTTP/3.0 -> HTTP/3
  all others returned unchanged

Closes #7734

* caddyhttp: Use ProtoMajor for proto_name normalization and update docs

---------

Co-authored-by: jalikajalika5 <105954036+jalikajalika5@users.noreply.github.com>
2026-06-04 11:03:19 -04:00
WeidiDeng
fcc7860d03 reverseproxy: replace placeholders specified for sni while using http3 (#7737)
* reverseproxy: replace placeholders specified for sni while using http3

* add test for placeholder

* reverseproxy: replace placeholders specified for sni while using http3

* add test for placeholder

* reverseproxy: test HTTP/3 SNI host placeholder

---------

Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-06-02 21:49:00 -06:00
JM Sanchez
e2eee6a7fc templates: Patch for GHSA-vcc4-2c75-vc9v (#7785)
* Patch GHSA-vcc4-2c75-vc9v in stripHTML

templates: fix funcStripHTML bypass via depth counter

The previous false-start approach allowed XSS bypass via inputs like <<>img src=x onerror=alert(1)> and failed on stacked angle brackets.

Replace the tagStart/inTag state machine with a depth counter that mirrors PHP strip_tags behaviour: each '<' increments depth, each '>' decrements it, and text is only emitted at depth zero. Quoted attribute values (both single and double) are tracked so '>' inside href values does not prematurely close a tag.

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>

* Update tplcontext_test.go

Templates: expand TestStripHTML with attack path coverage

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>

---------

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>
v2.11.4
2026-06-01 13:35:02 -06:00
Bruno Teixeira Lopes
0e8eb41b87 httpcaddyfile: fix incorrect error message on duplicate matchers (#7780)
Parse each matcher segment individually using NewDispenser(segment) instead
of DispenseDirective(dir), which coalesced all same-name segments into one
token stream. This caused the second definition name to be misinterpreted
as a matcher module name, producing 'module not registered: http.matchers.@name'
instead of the correct 'matcher is defined more than once' error.

By parsing segments individually, the existing duplicate check in
parseMatcherDefinitions naturally catches the duplicate on the second pass.

Signed-off-by: Brunotlps <brunoteixlps@gmail.com>
2026-05-30 07:05:41 +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
Muhammad Syafri, S.Kom
03e08ee6a9 encode: prioritize zstd and br over gzip in content negotiation (#7772)
* fix(encode): prioritize zstd and br over gzip in content negotiation

* test(encode): update unit tests to reflect new default priority ties

* fix(encode): move default preferences to dynamic encode handler and restore generic negotiation helper

* test(encode): call real Provision function in served-response test

* test(encode): rename served-response test to TestServeHTTPDefaultEncodingPreference

* refactor(encode): use slices.SortStableFunc and httptest.NewRecorder as recommended

* refactor(encode): simplify sorting with cmp.Compare and check request error in test

* test(encode): fix variable redeclaration in TestServeHTTPDefaultEncodingPreference

Fix 'no new variables on left side of :=' error by changing 'err :=' to 'err ='
on line 347, since err was already declared on line 332.

This fixes the build failure in the encode module tests.
2026-05-29 05:26:19 +10:00
gelsomino
86121c860f caddytls: skip idna.ToASCII for pure ASCII SNI values (#7770)
SNI is always ASCII on the wire (RFC 6066), and most config
patterns are also ASCII. For pure ASCII input, idna.ToASCII
only validates and lowercases, which is equivalent to a simple
strings.ToLower. Add a fast path to avoid the overhead of
idna.ToASCII in the common case.
2026-05-28 11:18:09 +10:00