1
2
3
4
5 package xml
6
7 import (
8 "bytes"
9 "errors"
10 "io"
11 "reflect"
12 "runtime"
13 "strings"
14 "testing"
15 "time"
16 )
17
18
19
20 func 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
31 const 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<></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.
36 http://code.google.com/p/pubsubhubbub
37 http://code.google.com/p/rietveld/issues/detail?id=155
38
39 The server side of the protocol is trivial:
40 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&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
45 I have tested this by adding debug prints to a local hub
46 server and checking that the server got the right publish
47 requests.
48
49 I can&#39;t quite get the server to work, but I think the bug
50 is not in my code. I think that the server expects to be
51 able to grab the feed and see the feed&#39;s actual URL in
52 the link rel=&quot;self&quot;, but the default value for that drops
53 the :port from the URL, and I cannot for the life of me
54 figure out how to get the Atom generator deep inside
55 django not to do that, or even where it is doing that,
56 or even what code is running to generate the Atom feed.
57 (I thought I knew but I added some assert False statements
58 and it kept running!)
59
60 Ignoring that particular problem, I would appreciate
61 feedback on the right way to get the two values at
62 the 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
68 http://codereview.appspot.com/116075/diff/1/2
69
70 The fundamental problem was that the tab code was
71 not being told what column the text began in, so it
72 didn&#39;t know where to put the tab stops. Another problem
73 was that some of the code assumed that string byte
74 offsets were the same as column offsets, which is only
75 true if there are no tabs.
76
77 In the process of fixing this, I cleaned up the arguments
78 to Fold and ExpandTabs and renamed them Break and
79 _ExpandTabs so that I could be sure that I found all the
80 call sites. I also wanted to verify that ExpandTabs was
81 not being used from outside intra_region_diff.py.
82
83
84 </summary></entry></feed> `
85
86 type 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
96 type 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
105 type Link struct {
106 Rel string `xml:"rel,attr,omitempty"`
107 Href string `xml:"href,attr"`
108 }
109
110 type Person struct {
111 Name string `xml:"name"`
112 URI string `xml:"uri"`
113 Email string `xml:"email"`
114 InnerXML string `xml:",innerxml"`
115 }
116
117 type Text struct {
118 Type string `xml:"type,attr,omitempty"`
119 Body string `xml:",chardata"`
120 }
121
122 var 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<></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.
151 http://code.google.com/p/pubsubhubbub
152 http://code.google.com/p/rietveld/issues/detail?id=155
153
154 The server side of the protocol is trivial:
155 1. add a <link rel="hub" href="hub-server"> 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
160 I have tested this by adding debug prints to a local hub
161 server and checking that the server got the right publish
162 requests.
163
164 I can't quite get the server to work, but I think the bug
165 is not in my code. I think that the server expects to be
166 able to grab the feed and see the feed's actual URL in
167 the link rel="self", but the default value for that drops
168 the :port from the URL, and I cannot for the life of me
169 figure out how to get the Atom generator deep inside
170 django not to do that, or even where it is doing that,
171 or even what code is running to generate the Atom feed.
172 (I thought I knew but I added some assert False statements
173 and it kept running!)
174
175 Ignoring that particular problem, I would appreciate
176 feedback on the right way to get the two values at
177 the 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
198 http://codereview.appspot.com/116075/diff/1/2
199
200 The fundamental problem was that the tab code was
201 not being told what column the text began in, so it
202 didn't know where to put the tab stops. Another problem
203 was that some of the code assumed that string byte
204 offsets were the same as column offsets, which is only
205 true if there are no tabs.
206
207 In the process of fixing this, I cleaned up the arguments
208 to Fold and ExpandTabs and renamed them Break and
209 _ExpandTabs so that I could be sure that I found all the
210 call sites. I also wanted to verify that ExpandTabs was
211 not being used from outside intra_region_diff.py.
212
213
214 `,
215 },
216 },
217 },
218 }
219
220 const 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
242 type PathTestItem struct {
243 Value string
244 }
245
246 type PathTestA struct {
247 Items []PathTestItem `xml:">Item1"`
248 Before, After string
249 }
250
251 type PathTestB struct {
252 Other []PathTestItem `xml:"Items>Item1"`
253 Before, After string
254 }
255
256 type PathTestC struct {
257 Values1 []string `xml:"Items>Item1>Value"`
258 Values2 []string `xml:"Items>Item2>Value"`
259 Before, After string
260 }
261
262 type PathTestSet struct {
263 Item1 []PathTestItem
264 }
265
266 type PathTestD struct {
267 Other PathTestSet `xml:"Items"`
268 Before, After string
269 }
270
271 type PathTestE struct {
272 Underline string `xml:"Items>_>Value"`
273 Before, After string
274 }
275
276 var pathTests = []interface{}{
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
284 func 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
296 type BadPathTestA struct {
297 First string `xml:"items>item1"`
298 Other string `xml:"items>item2"`
299 Second string `xml:"items"`
300 }
301
302 type BadPathTestB struct {
303 Other string `xml:"items>item2>value"`
304 First string `xml:"items>item1"`
305 Second string `xml:"items>item1>value"`
306 }
307
308 type BadPathTestC struct {
309 First string
310 Second string `xml:"First"`
311 }
312
313 type BadPathTestD struct {
314 BadPathEmbeddedA
315 BadPathEmbeddedB
316 }
317
318 type BadPathEmbeddedA struct {
319 First string
320 }
321
322 type BadPathEmbeddedB struct {
323 Second string `xml:"First"`
324 }
325
326 var badPathTests = []struct {
327 v, e interface{}
328 }{
329 {&BadPathTestA{}, &TagPathError{reflect.TypeOf(BadPathTestA{}), "First", "items>item1", "Second", "items"}},
330 {&BadPathTestB{}, &TagPathError{reflect.TypeOf(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}},
331 {&BadPathTestC{}, &TagPathError{reflect.TypeOf(BadPathTestC{}), "First", "", "Second", "First"}},
332 {&BadPathTestD{}, &TagPathError{reflect.TypeOf(BadPathTestD{}), "First", "", "Second", "First"}},
333 }
334
335 func 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
344 const OK = "OK"
345 const withoutNameTypeData = `
346 <?xml version="1.0" charset="utf-8"?>
347 <Test3 Attr="OK" />`
348
349 type TestThree struct {
350 XMLName Name `xml:"Test3"`
351 Attr string `xml:",attr"`
352 }
353
354 func 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
364 func 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
408 type 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
413 var 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
468 func 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
490 func 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
503 type TableAttrs struct {
504 TAttr TAttr
505 }
506
507 type 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
517 var 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
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
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
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
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
585 func 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
607 func 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
629 type MyCharData struct {
630 body string
631 }
632
633 func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error {
634 for {
635 t, err := d.Token()
636 if err == io.EOF {
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
649 var _ Unmarshaler = (*MyCharData)(nil)
650
651 func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error {
652 panic("must not call")
653 }
654
655 type MyAttr struct {
656 attr string
657 }
658
659 func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
660 m.attr = attr.Value
661 return nil
662 }
663
664 var _ UnmarshalerAttr = (*MyAttr)(nil)
665
666 type MyStruct struct {
667 Data *MyCharData
668 Attr *MyAttr `xml:",attr"`
669
670 Data2 MyCharData
671 Attr2 MyAttr `xml:",attr"`
672 }
673
674 func 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
692 type Pea struct {
693 Cotelydon string
694 }
695
696 type Pod struct {
697 Pea interface{} `xml:"Pea"`
698 }
699
700
701 func 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
719 type X struct {
720 D string `xml:",comment"`
721 }
722
723
724 func 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
740 type IXField struct {
741 Five int `xml:"five"`
742 NotInnerXML []string `xml:",innerxml"`
743 }
744
745
746 func 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
759 type Child struct {
760 G struct {
761 I int
762 }
763 }
764
765 type ChildToEmbed struct {
766 X bool
767 }
768
769 type 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
795 const (
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
826 func TestUnmarshalEmptyValues(t *testing.T) {
827
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
858
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}},
907 Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
908 ChildPtr: &Child{G: struct{ I int }{I: vInt0}},
909 }
910 if !reflect.DeepEqual(v, want) {
911 t.Fatalf("populated: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
912 }
913 }
914
915 type 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
939 const 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
966 func 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
1000 type 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
1024 const 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
1052 func 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 func TestCVE202230633(t *testing.T) {
1087 if runtime.GOARCH == "wasm" {
1088 t.Skip("causes memory exhaustion on js/wasm")
1089 }
1090 defer func() {
1091 p := recover()
1092 if p != nil {
1093 t.Fatal("Unmarshal panicked")
1094 }
1095 }()
1096 var example struct {
1097 Things []string
1098 }
1099 Unmarshal(bytes.Repeat([]byte("<a>"), 17_000_000), &example)
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, errExeceededMaxUnmarshalDepth) {
1111 t.Fatalf("Unmarshal unexpected error: got %q, want %q", err, errExeceededMaxUnmarshalDepth)
1112 }
1113 }
1114
View as plain text