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  	"strings"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  // Stripped down Atom feed data structures.
    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  // hget http://codereview.appspot.com/rss/mine/rsc
    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&lt;&gt;</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 &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;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&amp;#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&amp;#39;s actual URL in
    51  the link rel=&amp;quot;self&amp;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&amp;#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&lt;&gt;</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 &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; 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&#39;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&#39;s actual URL in
   166  the link rel=&quot;self&quot;, 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&#39;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  		// Default space does not apply to attribute names.
   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  		// Default space does not apply to attribute names.
   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  		// Default space does not apply to attribute names.
   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  		// Default space does not apply to attribute names.
   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 { // found end of element
   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  // https://golang.org/issue/6836
   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  // Issue 11112. Unmarshal must reject invalid comments.
   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  // Issue 15600. ",innerxml" on a field that can't hold it.
   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  // golang.org/issues/13417
   825  func TestUnmarshalEmptyValues(t *testing.T) {
   826  	// Test first with a zero-valued dst.
   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  	// Test with a pre-populated dst.
   857  	// Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
   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}}, // I should == zInt0? (zero value)
   906  		Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
   907  		ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
   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  // golang.org/issues/22146
   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  // golang.org/issues/22146
  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  // golang.org/issues/53350
  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