diff --git a/listeners.go b/listeners.go index 12d95396e..e13fee6ff 100644 --- a/listeners.go +++ b/listeners.go @@ -891,27 +891,18 @@ func isValidInterfaceChar(r rune) bool { } // resolveInterfacePlaceholder resolves Caddy placeholders in interface names. -// Only env.* and file.* placeholders are supported for interface binding. -// Returns the resolved interface name and whether it contains only supported placeholders. +// Returns the resolved interface name and whether resolution was successful. +// Any placeholder available in the global replacer context can be used. func resolveInterfacePlaceholder(s string) (string, bool) { // If no placeholders, return as-is if !strings.Contains(s, "{") { return s, true } - // Only allow env and file placeholders for interface names - if !strings.Contains(s, "{env.") && !strings.Contains(s, "{file.") { - return "", false - } - - // Check for other placeholders that ReplaceKnown would process but we don't want - if strings.Contains(s, "{system.") || strings.Contains(s, "{time.") { - return "", false - } - repl := NewReplacer() resolved := repl.ReplaceKnown(s, "") + // If no replacements were made or result is empty, reject it if resolved == s || resolved == "" { return "", false } diff --git a/listeners_interface_test.go b/listeners_interface_test.go index 75228f5e2..371a05736 100644 --- a/listeners_interface_test.go +++ b/listeners_interface_test.go @@ -56,12 +56,10 @@ func TestIsInterfaceName(t *testing.T) { {"eth\t0", false, "interface with tab"}, {"eth\x00", false, "interface with null character"}, - // Invalid interface names (unsupported Caddy placeholders) - {"{upstream}", false, "Caddy upstream placeholder"}, - {"{http.request.host}", false, "Caddy HTTP placeholder"}, - {"{vars.interface}", false, "Caddy variable placeholder"}, - {"{system.hostname}", false, "Caddy system placeholder"}, - {"{time.now}", false, "Caddy time placeholder"}, + // Invalid interface names (unregistered Caddy placeholders that won't be replaced) + {"{upstream}", false, "Caddy upstream placeholder (not registered in global replacer)"}, + {"{http.request.host}", false, "Caddy HTTP placeholder (not registered in global replacer)"}, + {"{vars.interface}", false, "Caddy variable placeholder (not registered in global replacer)"}, } for _, test := range tests { @@ -322,18 +320,14 @@ func TestIsInterfaceNameWithPlaceholders(t *testing.T) { {"{env.TEST_INVALID_INTERFACE}", false, "env placeholder resolving to IP address"}, {"{file." + invalidTempFile.Name() + "}", false, "file placeholder resolving to hostname"}, - // Unsupported placeholders - {"{http.request.host}", false, "unsupported HTTP placeholder"}, - {"{vars.interface}", false, "unsupported variable placeholder"}, - {"{system.hostname}", false, "unsupported system placeholder"}, - {"{time.now}", false, "unsupported time placeholder"}, - {"{upstream}", false, "unsupported upstream placeholder"}, + // Unregistered placeholders (not in global replacer, won't be replaced) + {"{http.request.host}", false, "HTTP placeholder (not in global replacer)"}, + {"{vars.interface}", false, "vars placeholder (not in global replacer)"}, + {"{upstream}", false, "upstream placeholder (not in global replacer)"}, - // Mixed placeholders (supported + unsupported) - {"eth{env.INTERFACE_NUM}-{http.request.host}", false, "mixed env and HTTP placeholders"}, - {"{env.PREFIX}-{vars.suffix}", false, "mixed env and vars placeholders"}, - {"eth{env.INTERFACE_NUM}{system.hostname}", false, "mixed env and system placeholders - system not allowed"}, - {"{env.PREFIX}-{time.now}", false, "mixed env and time placeholders - time not allowed"}, + // Mixed with unregistered placeholders (partial replacement will fail) + {"eth{env.INTERFACE_NUM}-{http.request.host}", false, "mixed env and HTTP (HTTP not replaced, contains {)"}, + {"{env.PREFIX}-{vars.suffix}", false, "mixed env and vars (vars not replaced, contains {)"}, // Invalid placeholder resolution {"{env.NONEXISTENT}", false, "nonexistent environment variable"},