mirror of
https://github.com/golang/net.git
synced 2026-03-31 10:27:08 +09:00
html: properly handle trailing solidus in unquoted attribute value in foreign content
The parser properly treats tags like <p a=/> as <p a="/">, but the tokenizer emits the SelfClosingTagToken token incorrectly. When the parser is used to parse foreign content, this results in an incorrect DOM. Thanks to Sean Ng (https://ensy.zip) for reporting this issue. Fixes golang/go#73070 Fixes CVE-2025-22872 Change-Id: I65c18df6d6244bf943b61e6c7a87895929e78f4f Reviewed-on: https://go-review.googlesource.com/c/net/+/661256 Reviewed-by: Neal Patel <nealpatel@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Gopher Robot <gobot@golang.org>
This commit is contained in:
committed by
Gopher Robot
parent
ebed060e8f
commit
e1fcd82abb
@@ -839,8 +839,22 @@ func (z *Tokenizer) readStartTag() TokenType {
|
||||
if raw {
|
||||
z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end]))
|
||||
}
|
||||
// Look for a self-closing token like "<br/>".
|
||||
if z.err == nil && z.buf[z.raw.end-2] == '/' {
|
||||
// Look for a self-closing token (e.g. <br/>).
|
||||
//
|
||||
// Originally, we did this by just checking that the last character of the
|
||||
// tag (ignoring the closing bracket) was a solidus (/) character, but this
|
||||
// is not always accurate.
|
||||
//
|
||||
// We need to be careful that we don't misinterpret a non-self-closing tag
|
||||
// as self-closing, as can happen if the tag contains unquoted attribute
|
||||
// values (i.e. <p a=/>).
|
||||
//
|
||||
// To avoid this, we check that the last non-bracket character of the tag
|
||||
// (z.raw.end-2) isn't the same character as the last non-quote character of
|
||||
// the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has
|
||||
// attributes.
|
||||
nAttrs := len(z.attr)
|
||||
if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) {
|
||||
return SelfClosingTagToken
|
||||
}
|
||||
return StartTagToken
|
||||
|
||||
@@ -616,6 +616,16 @@ var tokenTests = []tokenTest{
|
||||
`<p a/ ="">`,
|
||||
`<p a="" =""="">`,
|
||||
},
|
||||
{
|
||||
"slash at end of unquoted attribute value",
|
||||
`<p a="\">`,
|
||||
`<p a="\">`,
|
||||
},
|
||||
{
|
||||
"self-closing tag with attribute",
|
||||
`<p a=/>`,
|
||||
`<p a="/">`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestTokenizer(t *testing.T) {
|
||||
@@ -815,6 +825,14 @@ func TestReaderEdgeCases(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfClosingTagValueConfusion(t *testing.T) {
|
||||
z := NewTokenizer(strings.NewReader(`<p a=/>`))
|
||||
tok := z.Next()
|
||||
if tok != StartTagToken {
|
||||
t.Fatalf("unexpected token type: got %s, want %s", tok, StartTagToken)
|
||||
}
|
||||
}
|
||||
|
||||
// zeroOneByteReader is like a strings.Reader that alternates between
|
||||
// returning 0 bytes and 1 byte at a time.
|
||||
type zeroOneByteReader struct {
|
||||
|
||||
Reference in New Issue
Block a user