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