Source file src/encoding/xml/read_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     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  // Stripped down Atom feed data structures.
    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  // hget http://codereview.appspot.com/rss/mine/rsc
    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&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
    34  </title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
    35    An attempt at adding pubsubhubbub support to Rietveld.
    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 &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
    41       feeds that will be pubsubhubbubbed.
    42    2. every time one of those feeds changes, tell the hub
    43       with a simple POST request.
    44  
    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&amp;#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&amp;#39;s actual URL in
    52  the link rel=&amp;quot;self&amp;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&amp;#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&lt;&gt;</name>",
   134  	},
   135  	Entry: []Entry{
   136  		{
   137  			Title: "rietveld: an attempt at pubsubhubbub\n",
   138  			Link: []Link{
   139  				{Rel: "alternate", Href: "http://codereview.appspot.com/126085"},
   140  			},
   141  			Updated: ParseTime("2009-10-04T01:35:58+00:00"),
   142  			Author: Person{
   143  				Name:     "email-address-removed",
   144  				InnerXML: "<name>email-address-removed</name>",
   145  			},
   146  			ID: "urn:md5:134d9179c41f806be79b3a5f7877d19a",
   147  			Summary: Text{
   148  				Type: "html",
   149  				Body: `
   150    An attempt at adding pubsubhubbub support to Rietveld.
   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 &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
   156       feeds that will be pubsubhubbubbed.
   157    2. every time one of those feeds changes, tell the hub
   158       with a simple POST request.
   159  
   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&#39;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&#39;s actual URL in
   167  the link rel=&quot;self&quot;, 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&#39;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 = []any{
   277  	&PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
   278  	&PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
   279  	&PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"},
   280  	&PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
   281  	&PathTestE{Underline: "E", Before: "1", After: "2"},
   282  }
   283  
   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 any
   328  }{
   329  	{&BadPathTestA{}, &TagPathError{reflect.TypeFor[BadPathTestA](), "First", "items>item1", "Second", "items"}},
   330  	{&BadPathTestB{}, &TagPathError{reflect.TypeFor[BadPathTestB](), "First", "items>item1", "Second", "items>item1>value"}},
   331  	{&BadPathTestC{}, &TagPathError{reflect.TypeFor[BadPathTestC](), "First", "", "Second", "First"}},
   332  	{&BadPathTestD{}, &TagPathError{reflect.TypeFor[BadPathTestD](), "First", "", "Second", "First"}},
   333  }
   334  
   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  		// Default space does not apply to attribute names.
   542  		xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
   543  			`h:table="hello" table="world" ` +
   544  			`/></TableAttrs>`,
   545  		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
   546  	},
   547  	{
   548  		// Default space does not apply to attribute names.
   549  		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` +
   550  			`table="hello" f:table="world" ` +
   551  			`/></TableAttrs>`,
   552  		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
   553  	},
   554  	{
   555  		xml: `<TableAttrs><TAttr ` +
   556  			`table="bogus" ` +
   557  			`/></TableAttrs>`,
   558  		tab: TableAttrs{},
   559  	},
   560  	{
   561  		// Default space does not apply to attribute names.
   562  		xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
   563  			`h:table="hello" table="world" ` +
   564  			`/></TableAttrs>`,
   565  		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
   566  		ns:  "http://www.w3schools.com/furniture",
   567  	},
   568  	{
   569  		// Default space does not apply to attribute names.
   570  		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` +
   571  			`table="hello" f:table="world" ` +
   572  			`/></TableAttrs>`,
   573  		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
   574  		ns:  "http://www.w3.org/TR/html4/",
   575  	},
   576  	{
   577  		xml: `<TableAttrs><TAttr ` +
   578  			`table="bogus" ` +
   579  			`/></TableAttrs>`,
   580  		tab: TableAttrs{},
   581  		ns:  "something else entirely",
   582  	},
   583  }
   584  
   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 { // found end of element
   637  			break
   638  		}
   639  		if err != nil {
   640  			return err
   641  		}
   642  		if char, ok := t.(CharData); ok {
   643  			m.body += string(char)
   644  		}
   645  	}
   646  	return nil
   647  }
   648  
   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 any `xml:"Pea"`
   698  }
   699  
   700  // https://golang.org/issue/6836
   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  // Issue 11112. Unmarshal must reject invalid comments.
   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  // Issue 15600. ",innerxml" on a field that can't hold it.
   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  // golang.org/issues/13417
   826  func TestUnmarshalEmptyValues(t *testing.T) {
   827  	// Test first with a zero-valued dst.
   828  	v := new(Parent)
   829  	if err := Unmarshal([]byte(emptyXML), v); err != nil {
   830  		t.Fatalf("zero: Unmarshal failed: got %v", err)
   831  	}
   832  
   833  	zBytes, zInt, zStr, zFloat, zBool := []byte{}, 0, "", float32(0), false
   834  	want := &Parent{
   835  		IPtr:         &zInt,
   836  		Is:           []int{zInt},
   837  		IPtrs:        []*int{&zInt},
   838  		FPtr:         &zFloat,
   839  		Fs:           []float32{zFloat},
   840  		FPtrs:        []*float32{&zFloat},
   841  		BPtr:         &zBool,
   842  		Bs:           []bool{zBool},
   843  		BPtrs:        []*bool{&zBool},
   844  		Bytes:        []byte{},
   845  		BytesPtr:     &zBytes,
   846  		SPtr:         &zStr,
   847  		Ss:           []string{zStr},
   848  		SPtrs:        []*string{&zStr},
   849  		Children:     []Child{{}},
   850  		ChildPtr:     new(Child),
   851  		ChildToEmbed: ChildToEmbed{},
   852  	}
   853  	if !reflect.DeepEqual(v, want) {
   854  		t.Fatalf("zero: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
   855  	}
   856  
   857  	// Test with a pre-populated dst.
   858  	// Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
   859  	vBytes0, vInt0, vStr0, vFloat0, vBool0 := []byte("x"), 1, "x", float32(1), true
   860  	vBytes1, vInt1, vStr1, vFloat1, vBool1 := []byte("x"), 1, "x", float32(1), true
   861  	vInt2, vStr2, vFloat2, vBool2 := 1, "x", float32(1), true
   862  	v = &Parent{
   863  		I:            vInt0,
   864  		IPtr:         &vInt1,
   865  		Is:           []int{vInt0},
   866  		IPtrs:        []*int{&vInt2},
   867  		F:            vFloat0,
   868  		FPtr:         &vFloat1,
   869  		Fs:           []float32{vFloat0},
   870  		FPtrs:        []*float32{&vFloat2},
   871  		B:            vBool0,
   872  		BPtr:         &vBool1,
   873  		Bs:           []bool{vBool0},
   874  		BPtrs:        []*bool{&vBool2},
   875  		Bytes:        vBytes0,
   876  		BytesPtr:     &vBytes1,
   877  		S:            vStr0,
   878  		SPtr:         &vStr1,
   879  		Ss:           []string{vStr0},
   880  		SPtrs:        []*string{&vStr2},
   881  		MyI:          MyInt(vInt0),
   882  		Child:        Child{G: struct{ I int }{I: vInt0}},
   883  		Children:     []Child{{G: struct{ I int }{I: vInt0}}},
   884  		ChildPtr:     &Child{G: struct{ I int }{I: vInt0}},
   885  		ChildToEmbed: ChildToEmbed{X: vBool0},
   886  	}
   887  	if err := Unmarshal([]byte(emptyXML), v); err != nil {
   888  		t.Fatalf("populated: Unmarshal failed: got %v", err)
   889  	}
   890  
   891  	want = &Parent{
   892  		IPtr:     &zInt,
   893  		Is:       []int{vInt0, zInt},
   894  		IPtrs:    []*int{&vInt0, &zInt},
   895  		FPtr:     &zFloat,
   896  		Fs:       []float32{vFloat0, zFloat},
   897  		FPtrs:    []*float32{&vFloat0, &zFloat},
   898  		BPtr:     &zBool,
   899  		Bs:       []bool{vBool0, zBool},
   900  		BPtrs:    []*bool{&vBool0, &zBool},
   901  		Bytes:    []byte{},
   902  		BytesPtr: &zBytes,
   903  		SPtr:     &zStr,
   904  		Ss:       []string{vStr0, zStr},
   905  		SPtrs:    []*string{&vStr0, &zStr},
   906  		Child:    Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
   907  		Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
   908  		ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
   909  	}
   910  	if !reflect.DeepEqual(v, want) {
   911  		t.Fatalf("populated: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
   912  	}
   913  }
   914  
   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  // golang.org/issues/22146
   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  // golang.org/issues/22146
  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  // golang.org/issues/53350
  1087  func TestUnmarshalIntoNil(t *testing.T) {
  1088  	type T struct {
  1089  		A int `xml:"A"`
  1090  	}
  1091  
  1092  	var nilPointer *T
  1093  	err := Unmarshal([]byte("<T><A>1</A></T>"), nilPointer)
  1094  
  1095  	if err == nil {
  1096  		t.Fatalf("no error in unmarshalling")
  1097  	}
  1098  
  1099  }
  1100  
  1101  func TestCVE202228131(t *testing.T) {
  1102  	type nested struct {
  1103  		Parent *nested `xml:",any"`
  1104  	}
  1105  	var n nested
  1106  	err := Unmarshal(bytes.Repeat([]byte("<a>"), maxUnmarshalDepth+1), &n)
  1107  	if err == nil {
  1108  		t.Fatal("Unmarshal did not fail")
  1109  	} else if !errors.Is(err, errUnmarshalDepth) {
  1110  		t.Fatalf("Unmarshal unexpected error: got %q, want %q", err, errUnmarshalDepth)
  1111  	}
  1112  }
  1113  
  1114  func TestCVE202230633(t *testing.T) {
  1115  	if testing.Short() || runtime.GOARCH == "wasm" {
  1116  		t.Skip("test requires significant memory")
  1117  	}
  1118  	defer func() {
  1119  		p := recover()
  1120  		if p != nil {
  1121  			t.Fatal("Unmarshal panicked")
  1122  		}
  1123  	}()
  1124  	var example struct {
  1125  		Things []string
  1126  	}
  1127  	Unmarshal(bytes.Repeat([]byte("<a>"), 17_000_000), &example)
  1128  }
  1129  

View as plain text