Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

encoding/xml: treat a namespaced name as two names, not one #69196

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/encoding/xml/xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -1169,15 +1169,28 @@ func (d *Decoder) nsname() (name Name, ok bool) {
if !ok {
return
}
if strings.Count(s, ":") > 1 {
return name, false
} else if space, local, ok := strings.Cut(s, ":"); !ok || space == "" || local == "" {
// XML does not allow a document to end with a name, so there must
// be another byte.
b, ok := d.mustgetc()
if !ok {
return
}
if b != ':' {
d.ungetc(b)
name.Local = s
} else {
name.Space = space
name.Local = local
return
}
return name, true
n, ok := d.name()
if ok {
// give a better error message than would otherwise be possible
if d.nextByte == ':' {
d.err = d.syntaxError("colon after prefixed XML name " + string(s) + ":" + string(n))
return name, false
}
name.Space = s
name.Local = n
}
return
}

// Get name: /first(first|second)*/
Expand Down Expand Up @@ -1229,7 +1242,7 @@ func isNameByte(c byte) bool {
return 'A' <= c && c <= 'Z' ||
'a' <= c && c <= 'z' ||
'0' <= c && c <= '9' ||
c == '_' || c == ':' || c == '.' || c == '-'
c == '_' || c == '.' || c == '-'
}

func isName(s []byte) bool {
Expand Down Expand Up @@ -1287,7 +1300,6 @@ func isNameString(s string) bool {

var first = &unicode.RangeTable{
R16: []unicode.Range16{
{0x003A, 0x003A, 1},
{0x0041, 0x005A, 1},
{0x005F, 0x005F, 1},
{0x0061, 0x007A, 1},
Expand Down
88 changes: 85 additions & 3 deletions src/encoding/xml/xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,89 @@ func (t *toks) Token() (Token, error) {
return tok, nil
}

func TestDecodeBadName(t *testing.T) {
tests := []struct {
name string
invalid string
message string
}{
{
name: "Number after colon",
invalid: `<a:1/>`,
message: "invalid XML name: 1",
},
{
name: "Two colons at end",
invalid: `<a::/>`,
message: "expected element name after <",
},
{
name: "Two colons together in middle",
invalid: "<a::a/>",
message: "expected element name after <",
},
{
name: "Colon at end",
invalid: "<a:/>",
message: "expected element name after <",
},
{
name: "Colon at start",
invalid: "<:a/>",
message: "expected element name after <",
},
{
name: "Number after colon in attribute",
invalid: `<a a:1=""/>`,
message: "invalid XML name: 1",
},
{
name: "Two colons separate",
invalid: `<a a:b:c="1"/>`,
message: "colon after prefixed XML name a:b",
},
{
name: "Two colons at end",
invalid: `<a a::="1"/>`,
message: "expected attribute name in element",
},
{
name: "Two colons together in middle",
invalid: `<a a::a="1"/>`,
message: "expected attribute name in element",
},
{
name: "Colon at end",
invalid: `<a a:="1"/>`,
message: "expected attribute name in element",
},
{
name: "Colon at start",
invalid: `<a :a="1"/>`,
message: "expected attribute name in element",
},
}
for i, j := range tests {
t.Run(j.name, func(t *testing.T) {
d := NewDecoder(strings.NewReader(j.invalid))
tok, err := d.RawToken()
if tok != nil {
t.Fatalf("%d: d.Decode: expected nil token, got %#v", i, tok)
}
if err == nil {
t.Fatalf("%d: d.Decode: expected non-nil error, got nil", i)
}
syntaxError, ok := err.(*SyntaxError)
if !ok {
t.Fatalf("%d: d.Decode: expected syntax error", i)
}
if syntaxError.Msg != j.message {
t.Errorf("%d: bad message: expected %q, got %q", i, j.message, syntaxError.Msg)
}
})
}
}

func TestDecodeEOF(t *testing.T) {
start := StartElement{Name: Name{Local: "test"}}
tests := []struct {
Expand Down Expand Up @@ -1130,12 +1213,12 @@ func TestIssue20396(t *testing.T) {
wantErr error
}{
{`<a:te:st xmlns:a="abcd"/>`, // Issue 20396
UnmarshalError("XML syntax error on line 1: expected element name after <")},
UnmarshalError("XML syntax error on line 1: colon after prefixed XML name a:te")},
{`<a:te=st xmlns:a="abcd"/>`, attrError},
{`<a:te&st xmlns:a="abcd"/>`, attrError},
{`<a:test xmlns:a="abcd"/>`, nil},
{`<a:te:st xmlns:a="abcd">1</a:te:st>`,
UnmarshalError("XML syntax error on line 1: expected element name after <")},
UnmarshalError("XML syntax error on line 1: colon after prefixed XML name a:te")},
{`<a:te=st xmlns:a="abcd">1</a:te=st>`, attrError},
{`<a:te&st xmlns:a="abcd">1</a:te&st>`, attrError},
{`<a:test xmlns:a="abcd">1</a:test>`, nil},
Expand Down Expand Up @@ -1324,7 +1407,6 @@ func testRoundTrip(t *testing.T, input string) {

func TestRoundTrip(t *testing.T) {
tests := map[string]string{
"trailing colon": `<foo abc:="x"></foo>`,
"comments in directives": `<!ENTITY x<!<!-- c1 [ " -->--x --> > <e></e> <!DOCTYPE xxx [ x<!-- c2 " -->--x ]>`,
}
for name, input := range tests {
Expand Down