mirror of
https://github.com/caddyserver/caddy.git
synced 2026-03-17 14:34:03 +00:00
This commit is contained in:
@@ -143,3 +143,26 @@ func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSit
|
||||
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo")
|
||||
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Foo")
|
||||
}
|
||||
|
||||
func TestAutoHTTPSRedirectSortingExactMatchOverWildcard(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
local_certs
|
||||
}
|
||||
*.localhost:10443 {
|
||||
respond "Wildcard"
|
||||
}
|
||||
dev.localhost {
|
||||
respond "Exact"
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertRedirect("http://dev.localhost:9080/", "https://dev.localhost/", http.StatusPermanentRedirect)
|
||||
|
||||
tester.AssertRedirect("http://foo.localhost:9080/", "https://foo.localhost:10443/", http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
@@ -424,6 +424,40 @@ redirServersLoop:
|
||||
// we'll create a new server for all the listener addresses
|
||||
// that are unused and serve the remaining redirects from it
|
||||
|
||||
// Sort redirect routes by host specificity to ensure exact matches
|
||||
// take precedence over wildcards, preventing ambiguous routing.
|
||||
slices.SortFunc(routes, func(a, b Route) int {
|
||||
hostA := getFirstHostFromRoute(a)
|
||||
hostB := getFirstHostFromRoute(b)
|
||||
|
||||
// Catch-all routes (empty host) have the lowest priority
|
||||
if hostA == "" && hostB != "" {
|
||||
return 1
|
||||
}
|
||||
if hostB == "" && hostA != "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
hasWildcardA := strings.Contains(hostA, "*")
|
||||
hasWildcardB := strings.Contains(hostB, "*")
|
||||
|
||||
// Exact domains take precedence over wildcards
|
||||
if !hasWildcardA && hasWildcardB {
|
||||
return -1
|
||||
}
|
||||
if hasWildcardA && !hasWildcardB {
|
||||
return 1
|
||||
}
|
||||
|
||||
// If both are exact or both are wildcards, the longer one is more specific
|
||||
if len(hostA) != len(hostB) {
|
||||
return len(hostB) - len(hostA)
|
||||
}
|
||||
|
||||
// Tie-breaker: alphabetical order to ensure determinism
|
||||
return strings.Compare(hostA, hostB)
|
||||
})
|
||||
|
||||
// Use the sorted srvNames to consistently find the target server
|
||||
for _, srvName := range srvNames {
|
||||
srv := app.Servers[srvName]
|
||||
@@ -793,3 +827,26 @@ func isTailscaleDomain(name string) bool {
|
||||
}
|
||||
|
||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||
|
||||
// getFirstHostFromRoute traverses a route's matchers to find the Host rule.
|
||||
// Since we are dealing with internally generated redirect routes, the host
|
||||
// is typically the first string within the MatchHost.
|
||||
func getFirstHostFromRoute(r Route) string {
|
||||
for _, matcherSet := range r.MatcherSets {
|
||||
for _, m := range matcherSet {
|
||||
// Check if the matcher is of type MatchHost (value or pointer)
|
||||
switch hm := m.(type) {
|
||||
case MatchHost:
|
||||
if len(hm) > 0 {
|
||||
return hm[0]
|
||||
}
|
||||
case *MatchHost:
|
||||
if len(*hm) > 0 {
|
||||
return (*hm)[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return an empty string if it's a catch-all route (no specific host)
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user