src/html/template/escape.go | 5 ++--- src/html/template/escape_test.go | 15 +++++++++++++++ src/html/template/html.go | 3 +++ diff --git a/src/html/template/escape.go b/src/html/template/escape.go index 23ece7a72fbacc9e62ea272780f976dda7696417..c262d1698de6140cc7e1f4faa4b6caade3202794 100644 --- a/src/html/template/escape.go +++ b/src/html/template/escape.go @@ -381,9 +381,8 @@ // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x) // for all x. var redundantFuncs = map[string]map[string]bool{ "_html_template_commentescaper": { - "_html_template_attrescaper": true, - "_html_template_nospaceescaper": true, - "_html_template_htmlescaper": true, + "_html_template_attrescaper": true, + "_html_template_htmlescaper": true, }, "_html_template_cssescaper": { "_html_template_attrescaper": true, diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go index 3dd212bac94061e87de155269341d9e63a703b6c..f8b2b448f2dfa7394053fd75b48b76cd9fefef80 100644 --- a/src/html/template/escape_test.go +++ b/src/html/template/escape_test.go @@ -678,6 +678,21 @@ "srcset buffer growth", ``, ``, }, + { + "unquoted empty attribute value (plaintext)", + "

", + "

", + }, + { + "unquoted empty attribute value (url)", + "

", + "

", + }, + { + "quoted empty attribute value", + "

", + "

", + }, } for _, test := range tests { diff --git a/src/html/template/html.go b/src/html/template/html.go index bcca0b51a0ef901de193509f3fa709748b679381..a181699a5bda82fb0ad9df25a9eb6f576138c6bd 100644 --- a/src/html/template/html.go +++ b/src/html/template/html.go @@ -14,6 +14,9 @@ // htmlNospaceEscaper escapes for inclusion in unquoted attribute values. func htmlNospaceEscaper(args ...any) string { s, t := stringify(args...) + if s == "" { + return filterFailsafe + } if t == contentTypeHTML { return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false) }