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