caddyhttp: keep absent optional placeholders empty
parent
56e3a88efe
commit
512c8de7aa
|
|
@ -87,6 +87,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
|
|||
return cookie.Value, true
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// http.request.tls.*
|
||||
|
|
@ -417,10 +418,23 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
|
|||
}
|
||||
|
||||
func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
|
||||
if req == nil || req.TLS == nil {
|
||||
if req == nil {
|
||||
return nil, false
|
||||
}
|
||||
state := req.TLS
|
||||
if state == nil {
|
||||
state = new(tls.ConnectionState)
|
||||
}
|
||||
value, known := getTLSReplacement(state, key)
|
||||
if req.TLS == nil {
|
||||
// Use the same field parser to recognise valid placeholders but
|
||||
// do not substitute values from the empty state for a plain HTTP request.
|
||||
return nil, known
|
||||
}
|
||||
return value, known
|
||||
}
|
||||
|
||||
func getTLSReplacement(state *tls.ConnectionState, key string) (any, bool) {
|
||||
if len(key) < len(reqTLSReplPrefix) {
|
||||
return nil, false
|
||||
}
|
||||
|
|
@ -428,7 +442,7 @@ func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
|
|||
field := strings.ToLower(key[len(reqTLSReplPrefix):])
|
||||
|
||||
if strings.HasPrefix(field, "client.") {
|
||||
cert := getTLSPeerCert(req.TLS)
|
||||
cert := getTLSPeerCert(state)
|
||||
if cert == nil {
|
||||
// Instead of returning (nil, false) here, we set it to a dummy
|
||||
// value to fix #7530. This way, even if there is no client cert,
|
||||
|
|
@ -533,20 +547,20 @@ func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
|
|||
|
||||
switch field {
|
||||
case "version":
|
||||
return caddytls.ProtocolName(req.TLS.Version), true
|
||||
return caddytls.ProtocolName(state.Version), true
|
||||
case "cipher_suite":
|
||||
return tls.CipherSuiteName(req.TLS.CipherSuite), true
|
||||
return tls.CipherSuiteName(state.CipherSuite), true
|
||||
case "resumed":
|
||||
return req.TLS.DidResume, true
|
||||
return state.DidResume, true
|
||||
case "proto":
|
||||
return req.TLS.NegotiatedProtocol, true
|
||||
return state.NegotiatedProtocol, true
|
||||
case "proto_mutual":
|
||||
// req.TLS.NegotiatedProtocolIsMutual is deprecated - it's always true.
|
||||
return true, true
|
||||
case "server_name":
|
||||
return req.TLS.ServerName, true
|
||||
return state.ServerName, true
|
||||
case "ech":
|
||||
return req.TLS.ECHAccepted, true
|
||||
return state.ECHAccepted, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,62 @@ import (
|
|||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func TestMissingCookiePlaceholder(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), VarsCtxKey, map[string]any{}))
|
||||
repl := NewTestReplacer(req)
|
||||
const input = "before-{http.request.cookie.session}-after"
|
||||
if got := repl.ReplaceKnown(input, ""); got != "before--after" {
|
||||
t.Fatalf("missing cookie = %q, want %q", got, "before--after")
|
||||
}
|
||||
req.AddCookie(&http.Cookie{Name: "session", Value: "present"})
|
||||
if got := repl.ReplaceKnown(input, ""); got != "before-present-after" {
|
||||
t.Fatalf("present cookie = %q, want %q", got, "before-present-after")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSPlaceholdersWithoutTLS(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), VarsCtxKey, map[string]any{}))
|
||||
repl := NewTestReplacer(req)
|
||||
for _, field := range []string{
|
||||
"version", "cipher_suite", "resumed", "proto", "proto_mutual", "server_name", "ech",
|
||||
"client.fingerprint", "client.public_key", "client.public_key_sha256",
|
||||
"client.issuer", "client.serial", "client.subject", "client.certificate_pem", "client.certificate_der_base64",
|
||||
"client.san.dns_names", "client.san.emails", "client.san.ips", "client.san.uris",
|
||||
"client.san.dns_names.0", "client.san.emails.0", "client.san.ips.0", "client.san.uris.0",
|
||||
} {
|
||||
t.Run(field, func(t *testing.T) {
|
||||
key := "http.request.tls." + field
|
||||
value, known := repl.Get(key)
|
||||
if !known || caddy.ToString(value) != "" {
|
||||
t.Fatalf("Get(%q) = %v, %v; want empty, known", key, value, known)
|
||||
}
|
||||
if got := repl.ReplaceKnown("before-{"+key+"}-after", ""); got != "before--after" {
|
||||
t.Fatalf("replacement = %q, want %q", got, "before--after")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownTLSPlaceholdersRemainLiteral(t *testing.T) {
|
||||
for _, state := range []*tls.ConnectionState{nil, {ServerName: "example.com"}} {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.TLS = state
|
||||
req = req.WithContext(context.WithValue(req.Context(), VarsCtxKey, map[string]any{}))
|
||||
repl := NewTestReplacer(req)
|
||||
for _, field := range []string{
|
||||
"unknown", "client.unknown", "client.san.unknown",
|
||||
"client.san.dns_names_extra", "client.san.dns_names.-1", "client.san.dns_names.nope",
|
||||
} {
|
||||
input := "{http.request.tls." + field + "}"
|
||||
if got := repl.ReplaceKnown(input, ""); got != input {
|
||||
t.Errorf("replacement = %q, want literal %q", got, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPVarReplacement(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz?a=1&b=2", nil)
|
||||
repl := caddy.NewReplacer()
|
||||
|
|
@ -334,4 +390,3 @@ func TestHTTPVarReplacementUUID(t *testing.T) {
|
|||
t.Errorf("expected stable uuid across references: %q != %q", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,30 @@ func fakeRequest() *http.Request {
|
|||
return r
|
||||
}
|
||||
|
||||
func TestStaticResponseHeadersWithAbsentRequestData(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), VarsCtxKey, map[string]any{}))
|
||||
NewTestReplacer(req)
|
||||
response := StaticResponse{Headers: http.Header{
|
||||
"Location": []string{"/login?session={http.request.cookie.session}"},
|
||||
"X-Tls": []string{"before-{http.request.tls.server_name}-after"},
|
||||
"X-Unknown": []string{"before-{unknown}-after"},
|
||||
}}
|
||||
w := httptest.NewRecorder()
|
||||
if err := response.ServeHTTP(w, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for field, want := range map[string]string{
|
||||
"Location": "/login?session=",
|
||||
"X-Tls": "before--after",
|
||||
"X-Unknown": "before-{unknown}-after",
|
||||
} {
|
||||
if got := w.Header().Get(field); got != want {
|
||||
t.Errorf("%s = %q, want %q", field, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticResponseHeadersKeepUnknownPlaceholders(t *testing.T) {
|
||||
r := fakeRequest()
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
|||
Loading…
Reference in New Issue