1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package xml
6
7import (
8	"bytes"
9	"errors"
10	"io"
11	"reflect"
12	"runtime"
13	"strings"
14	"testing"
15	"time"
16)
17
18// Stripped down Atom feed data structures.
19
20func TestUnmarshalFeed(t *testing.T) {
21	var f Feed
22	if err := Unmarshal([]byte(atomFeedString), &f); err != nil {
23		t.Fatalf("Unmarshal: %s", err)
24	}
25	if !reflect.DeepEqual(f, atomFeed) {
26		t.Fatalf("have %#v\nwant %#v", f, atomFeed)
27	}
28}
29
30// hget http://codereview.appspot.com/rss/mine/rsc
31const atomFeedString = `
32<?xml version="1.0" encoding="utf-8"?>
33<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
34</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
35  An attempt at adding pubsubhubbub support to Rietveld.
36http://code.google.com/p/pubsubhubbub
37http://code.google.com/p/rietveld/issues/detail?id=155
38
39The server side of the protocol is trivial:
40  1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
41     feeds that will be pubsubhubbubbed.
42  2. every time one of those feeds changes, tell the hub
43     with a simple POST request.
44
45I have tested this by adding debug prints to a local hub
46server and checking that the server got the right publish
47requests.
48
49I can&amp;#39;t quite get the server to work, but I think the bug
50is not in my code.  I think that the server expects to be
51able to grab the feed and see the feed&amp;#39;s actual URL in
52the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
53the :port from the URL, and I cannot for the life of me
54figure out how to get the Atom generator deep inside
55django not to do that, or even where it is doing that,
56or even what code is running to generate the Atom feed.
57(I thought I knew but I added some assert False statements
58and it kept running!)
59
60Ignoring that particular problem, I would appreciate
61feedback on the right way to get the two values at
62the top of feeds.py marked NOTE(rsc).
63
64
65</summary></entry><entry><title>rietveld: correct tab handling
66</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html">
67  This fixes the buggy tab rendering that can be seen at
68http://codereview.appspot.com/116075/diff/1/2
69
70The fundamental problem was that the tab code was
71not being told what column the text began in, so it
72didn&amp;#39;t know where to put the tab stops.  Another problem
73was that some of the code assumed that string byte
74offsets were the same as column offsets, which is only
75true if there are no tabs.
76
77In the process of fixing this, I cleaned up the arguments
78to Fold and ExpandTabs and renamed them Break and
79_ExpandTabs so that I could be sure that I found all the
80call sites.  I also wanted to verify that ExpandTabs was
81not being used from outside intra_region_diff.py.
82
83
84</summary></entry></feed> 	   `
85
86type Feed struct {
87	XMLName Name      `xml:"http://www.w3.org/2005/Atom feed"`
88	Title   string    `xml:"title"`
89	ID      string    `xml:"id"`
90	Link    []Link    `xml:"link"`
91	Updated time.Time `xml:"updated,attr"`
92	Author  Person    `xml:"author"`
93	Entry   []Entry   `xml:"entry"`
94}
95
96type Entry struct {
97	Title   string    `xml:"title"`
98	ID      string    `xml:"id"`
99	Link    []Link    `xml:"link"`
100	Updated time.Time `xml:"updated"`
101	Author  Person    `xml:"author"`
102	Summary Text      `xml:"summary"`
103}
104
105type Link struct {
106	Rel  string `xml:"rel,attr,omitempty"`
107	Href string `xml:"href,attr"`
108}
109
110type Person struct {
111	Name     string `xml:"name"`
112	URI      string `xml:"uri"`
113	Email    string `xml:"email"`
114	InnerXML string `xml:",innerxml"`
115}
116
117type Text struct {
118	Type string `xml:"type,attr,omitempty"`
119	Body string `xml:",chardata"`
120}
121
122var atomFeed = Feed{
123	XMLName: Name{"http://www.w3.org/2005/Atom", "feed"},
124	Title:   "Code Review - My issues",
125	Link: []Link{
126		{Rel: "alternate", Href: "http://codereview.appspot.com/"},
127		{Rel: "self", Href: "http://codereview.appspot.com/rss/mine/rsc"},
128	},
129	ID:      "http://codereview.appspot.com/",
130	Updated: ParseTime("2009-10-04T01:35:58+00:00"),
131	Author: Person{
132		Name:     "rietveld<>",
133		InnerXML: "<name>rietveld&lt;&gt;</name>",
134	},
135	Entry: []Entry{
136		{
137			Title: "rietveld: an attempt at pubsubhubbub\n",
138			Link: []Link{
139				{Rel: "alternate", Href: "http://codereview.appspot.com/126085"},
140			},
141			Updated: ParseTime("2009-10-04T01:35:58+00:00"),
142			Author: Person{
143				Name:     "email-address-removed",
144				InnerXML: "<name>email-address-removed</name>",
145			},
146			ID: "urn:md5:134d9179c41f806be79b3a5f7877d19a",
147			Summary: Text{
148				Type: "html",
149				Body: `
150  An attempt at adding pubsubhubbub support to Rietveld.
151http://code.google.com/p/pubsubhubbub
152http://code.google.com/p/rietveld/issues/detail?id=155
153
154The server side of the protocol is trivial:
155  1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
156     feeds that will be pubsubhubbubbed.
157  2. every time one of those feeds changes, tell the hub
158     with a simple POST request.
159
160I have tested this by adding debug prints to a local hub
161server and checking that the server got the right publish
162requests.
163
164I can&#39;t quite get the server to work, but I think the bug
165is not in my code.  I think that the server expects to be
166able to grab the feed and see the feed&#39;s actual URL in
167the link rel=&quot;self&quot;, but the default value for that drops
168the :port from the URL, and I cannot for the life of me
169figure out how to get the Atom generator deep inside
170django not to do that, or even where it is doing that,
171or even what code is running to generate the Atom feed.
172(I thought I knew but I added some assert False statements
173and it kept running!)
174
175Ignoring that particular problem, I would appreciate
176feedback on the right way to get the two values at
177the top of feeds.py marked NOTE(rsc).
178
179
180`,
181			},
182		},
183		{
184			Title: "rietveld: correct tab handling\n",
185			Link: []Link{
186				{Rel: "alternate", Href: "http://codereview.appspot.com/124106"},
187			},
188			Updated: ParseTime("2009-10-03T23:02:17+00:00"),
189			Author: Person{
190				Name:     "email-address-removed",
191				InnerXML: "<name>email-address-removed</name>",
192			},
193			ID: "urn:md5:0a2a4f19bb815101f0ba2904aed7c35a",
194			Summary: Text{
195				Type: "html",
196				Body: `
197  This fixes the buggy tab rendering that can be seen at
198http://codereview.appspot.com/116075/diff/1/2
199
200The fundamental problem was that the tab code was
201not being told what column the text began in, so it
202didn&#39;t know where to put the tab stops.  Another problem
203was that some of the code assumed that string byte
204offsets were the same as column offsets, which is only
205true if there are no tabs.
206
207In the process of fixing this, I cleaned up the arguments
208to Fold and ExpandTabs and renamed them Break and
209_ExpandTabs so that I could be sure that I found all the
210call sites.  I also wanted to verify that ExpandTabs was
211not being used from outside intra_region_diff.py.
212
213
214`,
215			},
216		},
217	},
218}
219
220const pathTestString = `
221<Result>
222    <Before>1</Before>
223    <Items>
224        <Item1>
225            <Value>A</Value>
226        </Item1>
227        <Item2>
228            <Value>B</Value>
229        </Item2>
230        <Item1>
231            <Value>C</Value>
232            <Value>D</Value>
233        </Item1>
234        <_>
235            <Value>E</Value>
236        </_>
237    </Items>
238    <After>2</After>
239</Result>
240`
241
242type PathTestItem struct {
243	Value string
244}
245
246type PathTestA struct {
247	Items         []PathTestItem `xml:">Item1"`
248	Before, After string
249}
250
251type PathTestB struct {
252	Other         []PathTestItem `xml:"Items>Item1"`
253	Before, After string
254}
255
256type PathTestC struct {
257	Values1       []string `xml:"Items>Item1>Value"`
258	Values2       []string `xml:"Items>Item2>Value"`
259	Before, After string
260}
261
262type PathTestSet struct {
263	Item1 []PathTestItem
264}
265
266type PathTestD struct {
267	Other         PathTestSet `xml:"Items"`
268	Before, After string
269}
270
271type PathTestE struct {
272	Underline     string `xml:"Items>_>Value"`
273	Before, After string
274}
275
276var pathTests = []any{
277	&PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
278	&PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
279	&PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"},
280	&PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
281	&PathTestE{Underline: "E", Before: "1", After: "2"},
282}
283
284func TestUnmarshalPaths(t *testing.T) {
285	for _, pt := range pathTests {
286		v := reflect.New(reflect.TypeOf(pt).Elem()).Interface()
287		if err := Unmarshal([]byte(pathTestString), v); err != nil {
288			t.Fatalf("Unmarshal: %s", err)
289		}
290		if !reflect.DeepEqual(v, pt) {
291			t.Fatalf("have %#v\nwant %#v", v, pt)
292		}
293	}
294}
295
296type BadPathTestA struct {
297	First  string `xml:"items>item1"`
298	Other  string `xml:"items>item2"`
299	Second string `xml:"items"`
300}
301
302type BadPathTestB struct {
303	Other  string `xml:"items>item2>value"`
304	First  string `xml:"items>item1"`
305	Second string `xml:"items>item1>value"`
306}
307
308type BadPathTestC struct {
309	First  string
310	Second string `xml:"First"`
311}
312
313type BadPathTestD struct {
314	BadPathEmbeddedA
315	BadPathEmbeddedB
316}
317
318type BadPathEmbeddedA struct {
319	First string
320}
321
322type BadPathEmbeddedB struct {
323	Second string `xml:"First"`
324}
325
326var badPathTests = []struct {
327	v, e any
328}{
329	{&BadPathTestA{}, &TagPathError{reflect.TypeFor[BadPathTestA](), "First", "items>item1", "Second", "items"}},
330	{&BadPathTestB{}, &TagPathError{reflect.TypeFor[BadPathTestB](), "First", "items>item1", "Second", "items>item1>value"}},
331	{&BadPathTestC{}, &TagPathError{reflect.TypeFor[BadPathTestC](), "First", "", "Second", "First"}},
332	{&BadPathTestD{}, &TagPathError{reflect.TypeFor[BadPathTestD](), "First", "", "Second", "First"}},
333}
334
335func TestUnmarshalBadPaths(t *testing.T) {
336	for _, tt := range badPathTests {
337		err := Unmarshal([]byte(pathTestString), tt.v)
338		if !reflect.DeepEqual(err, tt.e) {
339			t.Fatalf("Unmarshal with %#v didn't fail properly:\nhave %#v,\nwant %#v", tt.v, err, tt.e)
340		}
341	}
342}
343
344const OK = "OK"
345const withoutNameTypeData = `
346<?xml version="1.0" charset="utf-8"?>
347<Test3 Attr="OK" />`
348
349type TestThree struct {
350	XMLName Name   `xml:"Test3"`
351	Attr    string `xml:",attr"`
352}
353
354func TestUnmarshalWithoutNameType(t *testing.T) {
355	var x TestThree
356	if err := Unmarshal([]byte(withoutNameTypeData), &x); err != nil {
357		t.Fatalf("Unmarshal: %s", err)
358	}
359	if x.Attr != OK {
360		t.Fatalf("have %v\nwant %v", x.Attr, OK)
361	}
362}
363
364func TestUnmarshalAttr(t *testing.T) {
365	type ParamVal struct {
366		Int int `xml:"int,attr"`
367	}
368
369	type ParamPtr struct {
370		Int *int `xml:"int,attr"`
371	}
372
373	type ParamStringPtr struct {
374		Int *string `xml:"int,attr"`
375	}
376
377	x := []byte(`<Param int="1" />`)
378
379	p1 := &ParamPtr{}
380	if err := Unmarshal(x, p1); err != nil {
381		t.Fatalf("Unmarshal: %s", err)
382	}
383	if p1.Int == nil {
384		t.Fatalf("Unmarshal failed in to *int field")
385	} else if *p1.Int != 1 {
386		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p1.Int, 1)
387	}
388
389	p2 := &ParamVal{}
390	if err := Unmarshal(x, p2); err != nil {
391		t.Fatalf("Unmarshal: %s", err)
392	}
393	if p2.Int != 1 {
394		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p2.Int, 1)
395	}
396
397	p3 := &ParamStringPtr{}
398	if err := Unmarshal(x, p3); err != nil {
399		t.Fatalf("Unmarshal: %s", err)
400	}
401	if p3.Int == nil {
402		t.Fatalf("Unmarshal failed in to *string field")
403	} else if *p3.Int != "1" {
404		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p3.Int, 1)
405	}
406}
407
408type Tables struct {
409	HTable string `xml:"http://www.w3.org/TR/html4/ table"`
410	FTable string `xml:"http://www.w3schools.com/furniture table"`
411}
412
413var tables = []struct {
414	xml string
415	tab Tables
416	ns  string
417}{
418	{
419		xml: `<Tables>` +
420			`<table xmlns="http://www.w3.org/TR/html4/">hello</table>` +
421			`<table xmlns="http://www.w3schools.com/furniture">world</table>` +
422			`</Tables>`,
423		tab: Tables{"hello", "world"},
424	},
425	{
426		xml: `<Tables>` +
427			`<table xmlns="http://www.w3schools.com/furniture">world</table>` +
428			`<table xmlns="http://www.w3.org/TR/html4/">hello</table>` +
429			`</Tables>`,
430		tab: Tables{"hello", "world"},
431	},
432	{
433		xml: `<Tables xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/">` +
434			`<f:table>world</f:table>` +
435			`<h:table>hello</h:table>` +
436			`</Tables>`,
437		tab: Tables{"hello", "world"},
438	},
439	{
440		xml: `<Tables>` +
441			`<table>bogus</table>` +
442			`</Tables>`,
443		tab: Tables{},
444	},
445	{
446		xml: `<Tables>` +
447			`<table>only</table>` +
448			`</Tables>`,
449		tab: Tables{HTable: "only"},
450		ns:  "http://www.w3.org/TR/html4/",
451	},
452	{
453		xml: `<Tables>` +
454			`<table>only</table>` +
455			`</Tables>`,
456		tab: Tables{FTable: "only"},
457		ns:  "http://www.w3schools.com/furniture",
458	},
459	{
460		xml: `<Tables>` +
461			`<table>only</table>` +
462			`</Tables>`,
463		tab: Tables{},
464		ns:  "something else entirely",
465	},
466}
467
468func TestUnmarshalNS(t *testing.T) {
469	for i, tt := range tables {
470		var dst Tables
471		var err error
472		if tt.ns != "" {
473			d := NewDecoder(strings.NewReader(tt.xml))
474			d.DefaultSpace = tt.ns
475			err = d.Decode(&dst)
476		} else {
477			err = Unmarshal([]byte(tt.xml), &dst)
478		}
479		if err != nil {
480			t.Errorf("#%d: Unmarshal: %v", i, err)
481			continue
482		}
483		want := tt.tab
484		if dst != want {
485			t.Errorf("#%d: dst=%+v, want %+v", i, dst, want)
486		}
487	}
488}
489
490func TestMarshalNS(t *testing.T) {
491	dst := Tables{"hello", "world"}
492	data, err := Marshal(&dst)
493	if err != nil {
494		t.Fatalf("Marshal: %v", err)
495	}
496	want := `<Tables><table xmlns="http://www.w3.org/TR/html4/">hello</table><table xmlns="http://www.w3schools.com/furniture">world</table></Tables>`
497	str := string(data)
498	if str != want {
499		t.Errorf("have: %q\nwant: %q\n", str, want)
500	}
501}
502
503type TableAttrs struct {
504	TAttr TAttr
505}
506
507type TAttr struct {
508	HTable string `xml:"http://www.w3.org/TR/html4/ table,attr"`
509	FTable string `xml:"http://www.w3schools.com/furniture table,attr"`
510	Lang   string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
511	Other1 string `xml:"http://golang.org/xml/ other,attr,omitempty"`
512	Other2 string `xml:"http://golang.org/xmlfoo/ other,attr,omitempty"`
513	Other3 string `xml:"http://golang.org/json/ other,attr,omitempty"`
514	Other4 string `xml:"http://golang.org/2/json/ other,attr,omitempty"`
515}
516
517var tableAttrs = []struct {
518	xml string
519	tab TableAttrs
520	ns  string
521}{
522	{
523		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
524			`h:table="hello" f:table="world" ` +
525			`/></TableAttrs>`,
526		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
527	},
528	{
529		xml: `<TableAttrs><TAttr xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
530			`h:table="hello" f:table="world" ` +
531			`/></TableAttrs>`,
532		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
533	},
534	{
535		xml: `<TableAttrs><TAttr ` +
536			`h:table="hello" f:table="world" xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
537			`/></TableAttrs>`,
538		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
539	},
540	{
541		// Default space does not apply to attribute names.
542		xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
543			`h:table="hello" table="world" ` +
544			`/></TableAttrs>`,
545		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
546	},
547	{
548		// Default space does not apply to attribute names.
549		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` +
550			`table="hello" f:table="world" ` +
551			`/></TableAttrs>`,
552		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
553	},
554	{
555		xml: `<TableAttrs><TAttr ` +
556			`table="bogus" ` +
557			`/></TableAttrs>`,
558		tab: TableAttrs{},
559	},
560	{
561		// Default space does not apply to attribute names.
562		xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
563			`h:table="hello" table="world" ` +
564			`/></TableAttrs>`,
565		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
566		ns:  "http://www.w3schools.com/furniture",
567	},
568	{
569		// Default space does not apply to attribute names.
570		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` +
571			`table="hello" f:table="world" ` +
572			`/></TableAttrs>`,
573		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
574		ns:  "http://www.w3.org/TR/html4/",
575	},
576	{
577		xml: `<TableAttrs><TAttr ` +
578			`table="bogus" ` +
579			`/></TableAttrs>`,
580		tab: TableAttrs{},
581		ns:  "something else entirely",
582	},
583}
584
585func TestUnmarshalNSAttr(t *testing.T) {
586	for i, tt := range tableAttrs {
587		var dst TableAttrs
588		var err error
589		if tt.ns != "" {
590			d := NewDecoder(strings.NewReader(tt.xml))
591			d.DefaultSpace = tt.ns
592			err = d.Decode(&dst)
593		} else {
594			err = Unmarshal([]byte(tt.xml), &dst)
595		}
596		if err != nil {
597			t.Errorf("#%d: Unmarshal: %v", i, err)
598			continue
599		}
600		want := tt.tab
601		if dst != want {
602			t.Errorf("#%d: dst=%+v, want %+v", i, dst, want)
603		}
604	}
605}
606
607func TestMarshalNSAttr(t *testing.T) {
608	src := TableAttrs{TAttr{"hello", "world", "en_US", "other1", "other2", "other3", "other4"}}
609	data, err := Marshal(&src)
610	if err != nil {
611		t.Fatalf("Marshal: %v", err)
612	}
613	want := `<TableAttrs><TAttr xmlns:html4="http://www.w3.org/TR/html4/" html4:table="hello" xmlns:furniture="http://www.w3schools.com/furniture" furniture:table="world" xml:lang="en_US" xmlns:_xml="http://golang.org/xml/" _xml:other="other1" xmlns:_xmlfoo="http://golang.org/xmlfoo/" _xmlfoo:other="other2" xmlns:json="http://golang.org/json/" json:other="other3" xmlns:json_1="http://golang.org/2/json/" json_1:other="other4"></TAttr></TableAttrs>`
614	str := string(data)
615	if str != want {
616		t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want)
617	}
618
619	var dst TableAttrs
620	if err := Unmarshal(data, &dst); err != nil {
621		t.Errorf("Unmarshal: %v", err)
622	}
623
624	if dst != src {
625		t.Errorf("Unmarshal = %q, want %q", dst, src)
626	}
627}
628
629type MyCharData struct {
630	body string
631}
632
633func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error {
634	for {
635		t, err := d.Token()
636		if err == io.EOF { // found end of element
637			break
638		}
639		if err != nil {
640			return err
641		}
642		if char, ok := t.(CharData); ok {
643			m.body += string(char)
644		}
645	}
646	return nil
647}
648
649var _ Unmarshaler = (*MyCharData)(nil)
650
651func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error {
652	panic("must not call")
653}
654
655type MyAttr struct {
656	attr string
657}
658
659func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
660	m.attr = attr.Value
661	return nil
662}
663
664var _ UnmarshalerAttr = (*MyAttr)(nil)
665
666type MyStruct struct {
667	Data *MyCharData
668	Attr *MyAttr `xml:",attr"`
669
670	Data2 MyCharData
671	Attr2 MyAttr `xml:",attr"`
672}
673
674func TestUnmarshaler(t *testing.T) {
675	xml := `<?xml version="1.0" encoding="utf-8"?>
676		<MyStruct Attr="attr1" Attr2="attr2">
677		<Data>hello <!-- comment -->world</Data>
678		<Data2>howdy <!-- comment -->world</Data2>
679		</MyStruct>
680	`
681
682	var m MyStruct
683	if err := Unmarshal([]byte(xml), &m); err != nil {
684		t.Fatal(err)
685	}
686
687	if m.Data == nil || m.Attr == nil || m.Data.body != "hello world" || m.Attr.attr != "attr1" || m.Data2.body != "howdy world" || m.Attr2.attr != "attr2" {
688		t.Errorf("m=%#+v\n", m)
689	}
690}
691
692type Pea struct {
693	Cotelydon string
694}
695
696type Pod struct {
697	Pea any `xml:"Pea"`
698}
699
700// https://golang.org/issue/6836
701func TestUnmarshalIntoInterface(t *testing.T) {
702	pod := new(Pod)
703	pod.Pea = new(Pea)
704	xml := `<Pod><Pea><Cotelydon>Green stuff</Cotelydon></Pea></Pod>`
705	err := Unmarshal([]byte(xml), pod)
706	if err != nil {
707		t.Fatalf("failed to unmarshal %q: %v", xml, err)
708	}
709	pea, ok := pod.Pea.(*Pea)
710	if !ok {
711		t.Fatalf("unmarshaled into wrong type: have %T want *Pea", pod.Pea)
712	}
713	have, want := pea.Cotelydon, "Green stuff"
714	if have != want {
715		t.Errorf("failed to unmarshal into interface, have %q want %q", have, want)
716	}
717}
718
719type X struct {
720	D string `xml:",comment"`
721}
722
723// Issue 11112. Unmarshal must reject invalid comments.
724func TestMalformedComment(t *testing.T) {
725	testData := []string{
726		"<X><!-- a---></X>",
727		"<X><!-- -- --></X>",
728		"<X><!-- a--b --></X>",
729		"<X><!------></X>",
730	}
731	for i, test := range testData {
732		data := []byte(test)
733		v := new(X)
734		if err := Unmarshal(data, v); err == nil {
735			t.Errorf("%d: unmarshal should reject invalid comments", i)
736		}
737	}
738}
739
740type IXField struct {
741	Five        int      `xml:"five"`
742	NotInnerXML []string `xml:",innerxml"`
743}
744
745// Issue 15600. ",innerxml" on a field that can't hold it.
746func TestInvalidInnerXMLType(t *testing.T) {
747	v := new(IXField)
748	if err := Unmarshal([]byte(`<tag><five>5</five><innertag/></tag>`), v); err != nil {
749		t.Errorf("Unmarshal failed: got %v", err)
750	}
751	if v.Five != 5 {
752		t.Errorf("Five = %v, want 5", v.Five)
753	}
754	if v.NotInnerXML != nil {
755		t.Errorf("NotInnerXML = %v, want nil", v.NotInnerXML)
756	}
757}
758
759type Child struct {
760	G struct {
761		I int
762	}
763}
764
765type ChildToEmbed struct {
766	X bool
767}
768
769type Parent struct {
770	I        int
771	IPtr     *int
772	Is       []int
773	IPtrs    []*int
774	F        float32
775	FPtr     *float32
776	Fs       []float32
777	FPtrs    []*float32
778	B        bool
779	BPtr     *bool
780	Bs       []bool
781	BPtrs    []*bool
782	Bytes    []byte
783	BytesPtr *[]byte
784	S        string
785	SPtr     *string
786	Ss       []string
787	SPtrs    []*string
788	MyI      MyInt
789	Child    Child
790	Children []Child
791	ChildPtr *Child
792	ChildToEmbed
793}
794
795const (
796	emptyXML = `
797<Parent>
798    <I></I>
799    <IPtr></IPtr>
800    <Is></Is>
801    <IPtrs></IPtrs>
802    <F></F>
803    <FPtr></FPtr>
804    <Fs></Fs>
805    <FPtrs></FPtrs>
806    <B></B>
807    <BPtr></BPtr>
808    <Bs></Bs>
809    <BPtrs></BPtrs>
810    <Bytes></Bytes>
811    <BytesPtr></BytesPtr>
812    <S></S>
813    <SPtr></SPtr>
814    <Ss></Ss>
815    <SPtrs></SPtrs>
816    <MyI></MyI>
817    <Child></Child>
818    <Children></Children>
819    <ChildPtr></ChildPtr>
820    <X></X>
821</Parent>
822`
823)
824
825// golang.org/issues/13417
826func TestUnmarshalEmptyValues(t *testing.T) {
827	// Test first with a zero-valued dst.
828	v := new(Parent)
829	if err := Unmarshal([]byte(emptyXML), v); err != nil {
830		t.Fatalf("zero: Unmarshal failed: got %v", err)
831	}
832
833	zBytes, zInt, zStr, zFloat, zBool := []byte{}, 0, "", float32(0), false
834	want := &Parent{
835		IPtr:         &zInt,
836		Is:           []int{zInt},
837		IPtrs:        []*int{&zInt},
838		FPtr:         &zFloat,
839		Fs:           []float32{zFloat},
840		FPtrs:        []*float32{&zFloat},
841		BPtr:         &zBool,
842		Bs:           []bool{zBool},
843		BPtrs:        []*bool{&zBool},
844		Bytes:        []byte{},
845		BytesPtr:     &zBytes,
846		SPtr:         &zStr,
847		Ss:           []string{zStr},
848		SPtrs:        []*string{&zStr},
849		Children:     []Child{{}},
850		ChildPtr:     new(Child),
851		ChildToEmbed: ChildToEmbed{},
852	}
853	if !reflect.DeepEqual(v, want) {
854		t.Fatalf("zero: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
855	}
856
857	// Test with a pre-populated dst.
858	// Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
859	vBytes0, vInt0, vStr0, vFloat0, vBool0 := []byte("x"), 1, "x", float32(1), true
860	vBytes1, vInt1, vStr1, vFloat1, vBool1 := []byte("x"), 1, "x", float32(1), true
861	vInt2, vStr2, vFloat2, vBool2 := 1, "x", float32(1), true
862	v = &Parent{
863		I:            vInt0,
864		IPtr:         &vInt1,
865		Is:           []int{vInt0},
866		IPtrs:        []*int{&vInt2},
867		F:            vFloat0,
868		FPtr:         &vFloat1,
869		Fs:           []float32{vFloat0},
870		FPtrs:        []*float32{&vFloat2},
871		B:            vBool0,
872		BPtr:         &vBool1,
873		Bs:           []bool{vBool0},
874		BPtrs:        []*bool{&vBool2},
875		Bytes:        vBytes0,
876		BytesPtr:     &vBytes1,
877		S:            vStr0,
878		SPtr:         &vStr1,
879		Ss:           []string{vStr0},
880		SPtrs:        []*string{&vStr2},
881		MyI:          MyInt(vInt0),
882		Child:        Child{G: struct{ I int }{I: vInt0}},
883		Children:     []Child{{G: struct{ I int }{I: vInt0}}},
884		ChildPtr:     &Child{G: struct{ I int }{I: vInt0}},
885		ChildToEmbed: ChildToEmbed{X: vBool0},
886	}
887	if err := Unmarshal([]byte(emptyXML), v); err != nil {
888		t.Fatalf("populated: Unmarshal failed: got %v", err)
889	}
890
891	want = &Parent{
892		IPtr:     &zInt,
893		Is:       []int{vInt0, zInt},
894		IPtrs:    []*int{&vInt0, &zInt},
895		FPtr:     &zFloat,
896		Fs:       []float32{vFloat0, zFloat},
897		FPtrs:    []*float32{&vFloat0, &zFloat},
898		BPtr:     &zBool,
899		Bs:       []bool{vBool0, zBool},
900		BPtrs:    []*bool{&vBool0, &zBool},
901		Bytes:    []byte{},
902		BytesPtr: &zBytes,
903		SPtr:     &zStr,
904		Ss:       []string{vStr0, zStr},
905		SPtrs:    []*string{&vStr0, &zStr},
906		Child:    Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
907		Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
908		ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
909	}
910	if !reflect.DeepEqual(v, want) {
911		t.Fatalf("populated: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
912	}
913}
914
915type WhitespaceValuesParent struct {
916	BFalse bool
917	BTrue  bool
918	I      int
919	INeg   int
920	I8     int8
921	I8Neg  int8
922	I16    int16
923	I16Neg int16
924	I32    int32
925	I32Neg int32
926	I64    int64
927	I64Neg int64
928	UI     uint
929	UI8    uint8
930	UI16   uint16
931	UI32   uint32
932	UI64   uint64
933	F32    float32
934	F32Neg float32
935	F64    float64
936	F64Neg float64
937}
938
939const whitespaceValuesXML = `
940<WhitespaceValuesParent>
941    <BFalse>   false   </BFalse>
942    <BTrue>   true   </BTrue>
943    <I>   266703   </I>
944    <INeg>   -266703   </INeg>
945    <I8>  112  </I8>
946    <I8Neg>  -112  </I8Neg>
947    <I16>  6703  </I16>
948    <I16Neg>  -6703  </I16Neg>
949    <I32>  266703  </I32>
950    <I32Neg>  -266703  </I32Neg>
951    <I64>  266703  </I64>
952    <I64Neg>  -266703  </I64Neg>
953    <UI>   266703   </UI>
954    <UI8>  112  </UI8>
955    <UI16>  6703  </UI16>
956    <UI32>  266703  </UI32>
957    <UI64>  266703  </UI64>
958    <F32>  266.703  </F32>
959    <F32Neg>  -266.703  </F32Neg>
960    <F64>  266.703  </F64>
961    <F64Neg>  -266.703  </F64Neg>
962</WhitespaceValuesParent>
963`
964
965// golang.org/issues/22146
966func TestUnmarshalWhitespaceValues(t *testing.T) {
967	v := WhitespaceValuesParent{}
968	if err := Unmarshal([]byte(whitespaceValuesXML), &v); err != nil {
969		t.Fatalf("whitespace values: Unmarshal failed: got %v", err)
970	}
971
972	want := WhitespaceValuesParent{
973		BFalse: false,
974		BTrue:  true,
975		I:      266703,
976		INeg:   -266703,
977		I8:     112,
978		I8Neg:  -112,
979		I16:    6703,
980		I16Neg: -6703,
981		I32:    266703,
982		I32Neg: -266703,
983		I64:    266703,
984		I64Neg: -266703,
985		UI:     266703,
986		UI8:    112,
987		UI16:   6703,
988		UI32:   266703,
989		UI64:   266703,
990		F32:    266.703,
991		F32Neg: -266.703,
992		F64:    266.703,
993		F64Neg: -266.703,
994	}
995	if v != want {
996		t.Fatalf("whitespace values: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
997	}
998}
999
1000type WhitespaceAttrsParent struct {
1001	BFalse bool    `xml:",attr"`
1002	BTrue  bool    `xml:",attr"`
1003	I      int     `xml:",attr"`
1004	INeg   int     `xml:",attr"`
1005	I8     int8    `xml:",attr"`
1006	I8Neg  int8    `xml:",attr"`
1007	I16    int16   `xml:",attr"`
1008	I16Neg int16   `xml:",attr"`
1009	I32    int32   `xml:",attr"`
1010	I32Neg int32   `xml:",attr"`
1011	I64    int64   `xml:",attr"`
1012	I64Neg int64   `xml:",attr"`
1013	UI     uint    `xml:",attr"`
1014	UI8    uint8   `xml:",attr"`
1015	UI16   uint16  `xml:",attr"`
1016	UI32   uint32  `xml:",attr"`
1017	UI64   uint64  `xml:",attr"`
1018	F32    float32 `xml:",attr"`
1019	F32Neg float32 `xml:",attr"`
1020	F64    float64 `xml:",attr"`
1021	F64Neg float64 `xml:",attr"`
1022}
1023
1024const whitespaceAttrsXML = `
1025<WhitespaceAttrsParent
1026    BFalse="  false  "
1027    BTrue="  true  "
1028    I="  266703  "
1029    INeg="  -266703  "
1030    I8="  112  "
1031    I8Neg="  -112  "
1032    I16="  6703  "
1033    I16Neg="  -6703  "
1034    I32="  266703  "
1035    I32Neg="  -266703  "
1036    I64="  266703  "
1037    I64Neg="  -266703  "
1038    UI="  266703  "
1039    UI8="  112  "
1040    UI16="  6703  "
1041    UI32="  266703  "
1042    UI64="  266703  "
1043    F32="  266.703  "
1044    F32Neg="  -266.703  "
1045    F64="  266.703  "
1046    F64Neg="  -266.703  "
1047>
1048</WhitespaceAttrsParent>
1049`
1050
1051// golang.org/issues/22146
1052func TestUnmarshalWhitespaceAttrs(t *testing.T) {
1053	v := WhitespaceAttrsParent{}
1054	if err := Unmarshal([]byte(whitespaceAttrsXML), &v); err != nil {
1055		t.Fatalf("whitespace attrs: Unmarshal failed: got %v", err)
1056	}
1057
1058	want := WhitespaceAttrsParent{
1059		BFalse: false,
1060		BTrue:  true,
1061		I:      266703,
1062		INeg:   -266703,
1063		I8:     112,
1064		I8Neg:  -112,
1065		I16:    6703,
1066		I16Neg: -6703,
1067		I32:    266703,
1068		I32Neg: -266703,
1069		I64:    266703,
1070		I64Neg: -266703,
1071		UI:     266703,
1072		UI8:    112,
1073		UI16:   6703,
1074		UI32:   266703,
1075		UI64:   266703,
1076		F32:    266.703,
1077		F32Neg: -266.703,
1078		F64:    266.703,
1079		F64Neg: -266.703,
1080	}
1081	if v != want {
1082		t.Fatalf("whitespace attrs: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
1083	}
1084}
1085
1086// golang.org/issues/53350
1087func TestUnmarshalIntoNil(t *testing.T) {
1088	type T struct {
1089		A int `xml:"A"`
1090	}
1091
1092	var nilPointer *T
1093	err := Unmarshal([]byte("<T><A>1</A></T>"), nilPointer)
1094
1095	if err == nil {
1096		t.Fatalf("no error in unmarshaling")
1097	}
1098
1099}
1100
1101func TestCVE202228131(t *testing.T) {
1102	type nested struct {
1103		Parent *nested `xml:",any"`
1104	}
1105	var n nested
1106	err := Unmarshal(bytes.Repeat([]byte("<a>"), maxUnmarshalDepth+1), &n)
1107	if err == nil {
1108		t.Fatal("Unmarshal did not fail")
1109	} else if !errors.Is(err, errUnmarshalDepth) {
1110		t.Fatalf("Unmarshal unexpected error: got %q, want %q", err, errUnmarshalDepth)
1111	}
1112}
1113
1114func TestCVE202230633(t *testing.T) {
1115	if testing.Short() || runtime.GOARCH == "wasm" {
1116		t.Skip("test requires significant memory")
1117	}
1118	defer func() {
1119		p := recover()
1120		if p != nil {
1121			t.Fatal("Unmarshal panicked")
1122		}
1123	}()
1124	var example struct {
1125		Things []string
1126	}
1127	Unmarshal(bytes.Repeat([]byte("<a>"), 17_000_000), &example)
1128}
1129