Black Lives Matter. Support the Equal Justice Initiative.

Source file src/html/template/content_test.go

Documentation: html/template

     1  // Copyright 2011 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 template
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  func TestTypedContent(t *testing.T) {
    15  	data := []interface{}{
    16  		`<b> "foo%" O'Reilly &bar;`,
    17  		CSS(`a[href =~ "//example.com"]#foo`),
    18  		HTML(`Hello, <b>World</b> &amp;tc!`),
    19  		HTMLAttr(` dir="ltr"`),
    20  		JS(`c && alert("Hello, World!");`),
    21  		JSStr(`Hello, World & O'Reilly\u0021`),
    22  		URL(`greeting=H%69,&addressee=(World)`),
    23  		Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
    24  		URL(`,foo/,`),
    25  	}
    26  
    27  	// For each content sensitive escaper, see how it does on
    28  	// each of the typed strings above.
    29  	tests := []struct {
    30  		// A template containing a single {{.}}.
    31  		input string
    32  		want  []string
    33  	}{
    34  		{
    35  			`<style>{{.}} { color: blue }</style>`,
    36  			[]string{
    37  				`ZgotmplZ`,
    38  				// Allowed but not escaped.
    39  				`a[href =~ "//example.com"]#foo`,
    40  				`ZgotmplZ`,
    41  				`ZgotmplZ`,
    42  				`ZgotmplZ`,
    43  				`ZgotmplZ`,
    44  				`ZgotmplZ`,
    45  				`ZgotmplZ`,
    46  				`ZgotmplZ`,
    47  			},
    48  		},
    49  		{
    50  			`<div style="{{.}}">`,
    51  			[]string{
    52  				`ZgotmplZ`,
    53  				// Allowed and HTML escaped.
    54  				`a[href =~ &#34;//example.com&#34;]#foo`,
    55  				`ZgotmplZ`,
    56  				`ZgotmplZ`,
    57  				`ZgotmplZ`,
    58  				`ZgotmplZ`,
    59  				`ZgotmplZ`,
    60  				`ZgotmplZ`,
    61  				`ZgotmplZ`,
    62  			},
    63  		},
    64  		{
    65  			`{{.}}`,
    66  			[]string{
    67  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
    68  				`a[href =~ &#34;//example.com&#34;]#foo`,
    69  				// Not escaped.
    70  				`Hello, <b>World</b> &amp;tc!`,
    71  				` dir=&#34;ltr&#34;`,
    72  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
    73  				`Hello, World &amp; O&#39;Reilly\u0021`,
    74  				`greeting=H%69,&amp;addressee=(World)`,
    75  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
    76  				`,foo/,`,
    77  			},
    78  		},
    79  		{
    80  			`<a{{.}}>`,
    81  			[]string{
    82  				`ZgotmplZ`,
    83  				`ZgotmplZ`,
    84  				`ZgotmplZ`,
    85  				// Allowed and HTML escaped.
    86  				` dir="ltr"`,
    87  				`ZgotmplZ`,
    88  				`ZgotmplZ`,
    89  				`ZgotmplZ`,
    90  				`ZgotmplZ`,
    91  				`ZgotmplZ`,
    92  			},
    93  		},
    94  		{
    95  			`<a title={{.}}>`,
    96  			[]string{
    97  				`&lt;b&gt;&#32;&#34;foo%&#34;&#32;O&#39;Reilly&#32;&amp;bar;`,
    98  				`a[href&#32;&#61;~&#32;&#34;//example.com&#34;]#foo`,
    99  				// Tags stripped, spaces escaped, entity not re-escaped.
   100  				`Hello,&#32;World&#32;&amp;tc!`,
   101  				`&#32;dir&#61;&#34;ltr&#34;`,
   102  				`c&#32;&amp;&amp;&#32;alert(&#34;Hello,&#32;World!&#34;);`,
   103  				`Hello,&#32;World&#32;&amp;&#32;O&#39;Reilly\u0021`,
   104  				`greeting&#61;H%69,&amp;addressee&#61;(World)`,
   105  				`greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
   106  				`,foo/,`,
   107  			},
   108  		},
   109  		{
   110  			`<a title='{{.}}'>`,
   111  			[]string{
   112  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   113  				`a[href =~ &#34;//example.com&#34;]#foo`,
   114  				// Tags stripped, entity not re-escaped.
   115  				`Hello, World &amp;tc!`,
   116  				` dir=&#34;ltr&#34;`,
   117  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   118  				`Hello, World &amp; O&#39;Reilly\u0021`,
   119  				`greeting=H%69,&amp;addressee=(World)`,
   120  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   121  				`,foo/,`,
   122  			},
   123  		},
   124  		{
   125  			`<textarea>{{.}}</textarea>`,
   126  			[]string{
   127  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   128  				`a[href =~ &#34;//example.com&#34;]#foo`,
   129  				// Angle brackets escaped to prevent injection of close tags, entity not re-escaped.
   130  				`Hello, &lt;b&gt;World&lt;/b&gt; &amp;tc!`,
   131  				` dir=&#34;ltr&#34;`,
   132  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   133  				`Hello, World &amp; O&#39;Reilly\u0021`,
   134  				`greeting=H%69,&amp;addressee=(World)`,
   135  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   136  				`,foo/,`,
   137  			},
   138  		},
   139  		{
   140  			`<script>alert({{.}})</script>`,
   141  			[]string{
   142  				`"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
   143  				`"a[href =~ \"//example.com\"]#foo"`,
   144  				`"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
   145  				`" dir=\"ltr\""`,
   146  				// Not escaped.
   147  				`c && alert("Hello, World!");`,
   148  				// Escape sequence not over-escaped.
   149  				`"Hello, World & O'Reilly\u0021"`,
   150  				`"greeting=H%69,\u0026addressee=(World)"`,
   151  				`"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
   152  				`",foo/,"`,
   153  			},
   154  		},
   155  		{
   156  			`<button onclick="alert({{.}})">`,
   157  			[]string{
   158  				`&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly \u0026bar;&#34;`,
   159  				`&#34;a[href =~ \&#34;//example.com\&#34;]#foo&#34;`,
   160  				`&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!&#34;`,
   161  				`&#34; dir=\&#34;ltr\&#34;&#34;`,
   162  				// Not JS escaped but HTML escaped.
   163  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   164  				// Escape sequence not over-escaped.
   165  				`&#34;Hello, World &amp; O&#39;Reilly\u0021&#34;`,
   166  				`&#34;greeting=H%69,\u0026addressee=(World)&#34;`,
   167  				`&#34;greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w&#34;`,
   168  				`&#34;,foo/,&#34;`,
   169  			},
   170  		},
   171  		{
   172  			`<script>alert("{{.}}")</script>`,
   173  			[]string{
   174  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
   175  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
   176  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
   177  				` dir=\u0022ltr\u0022`,
   178  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
   179  				// Escape sequence not over-escaped.
   180  				`Hello, World \u0026 O\u0027Reilly\u0021`,
   181  				`greeting=H%69,\u0026addressee=(World)`,
   182  				`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
   183  				`,foo\/,`,
   184  			},
   185  		},
   186  		{
   187  			`<script type="text/javascript">alert("{{.}}")</script>`,
   188  			[]string{
   189  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
   190  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
   191  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
   192  				` dir=\u0022ltr\u0022`,
   193  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
   194  				// Escape sequence not over-escaped.
   195  				`Hello, World \u0026 O\u0027Reilly\u0021`,
   196  				`greeting=H%69,\u0026addressee=(World)`,
   197  				`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
   198  				`,foo\/,`,
   199  			},
   200  		},
   201  		{
   202  			`<script type="text/javascript">alert({{.}})</script>`,
   203  			[]string{
   204  				`"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
   205  				`"a[href =~ \"//example.com\"]#foo"`,
   206  				`"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
   207  				`" dir=\"ltr\""`,
   208  				// Not escaped.
   209  				`c && alert("Hello, World!");`,
   210  				// Escape sequence not over-escaped.
   211  				`"Hello, World & O'Reilly\u0021"`,
   212  				`"greeting=H%69,\u0026addressee=(World)"`,
   213  				`"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
   214  				`",foo/,"`,
   215  			},
   216  		},
   217  		{
   218  			// Not treated as JS. The output is same as for <div>{{.}}</div>
   219  			`<script type="text/template">{{.}}</script>`,
   220  			[]string{
   221  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   222  				`a[href =~ &#34;//example.com&#34;]#foo`,
   223  				// Not escaped.
   224  				`Hello, <b>World</b> &amp;tc!`,
   225  				` dir=&#34;ltr&#34;`,
   226  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   227  				`Hello, World &amp; O&#39;Reilly\u0021`,
   228  				`greeting=H%69,&amp;addressee=(World)`,
   229  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   230  				`,foo/,`,
   231  			},
   232  		},
   233  		{
   234  			`<button onclick='alert("{{.}}")'>`,
   235  			[]string{
   236  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
   237  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
   238  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
   239  				` dir=\u0022ltr\u0022`,
   240  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
   241  				// Escape sequence not over-escaped.
   242  				`Hello, World \u0026 O\u0027Reilly\u0021`,
   243  				`greeting=H%69,\u0026addressee=(World)`,
   244  				`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
   245  				`,foo\/,`,
   246  			},
   247  		},
   248  		{
   249  			`<a href="?q={{.}}">`,
   250  			[]string{
   251  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   252  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   253  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   254  				`%20dir%3d%22ltr%22`,
   255  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   256  				`Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
   257  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
   258  				`greeting=H%69,&amp;addressee=%28World%29`,
   259  				`greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
   260  				`,foo/,`,
   261  			},
   262  		},
   263  		{
   264  			`<style>body { background: url('?img={{.}}') }</style>`,
   265  			[]string{
   266  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   267  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   268  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   269  				`%20dir%3d%22ltr%22`,
   270  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   271  				`Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
   272  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
   273  				`greeting=H%69,&addressee=%28World%29`,
   274  				`greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
   275  				`,foo/,`,
   276  			},
   277  		},
   278  		{
   279  			`<img srcset="{{.}}">`,
   280  			[]string{
   281  				`#ZgotmplZ`,
   282  				`#ZgotmplZ`,
   283  				// Commas are not esacped
   284  				`Hello,#ZgotmplZ`,
   285  				// Leading spaces are not percent escapes.
   286  				` dir=%22ltr%22`,
   287  				// Spaces after commas are not percent escaped.
   288  				`#ZgotmplZ, World!%22%29;`,
   289  				`Hello,#ZgotmplZ`,
   290  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   291  				// Metadata is not escaped.
   292  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   293  				`%2cfoo/%2c`,
   294  			},
   295  		},
   296  		{
   297  			`<img srcset={{.}}>`,
   298  			[]string{
   299  				`#ZgotmplZ`,
   300  				`#ZgotmplZ`,
   301  				`Hello,#ZgotmplZ`,
   302  				// Spaces are HTML escaped not %-escaped
   303  				`&#32;dir&#61;%22ltr%22`,
   304  				`#ZgotmplZ,&#32;World!%22%29;`,
   305  				`Hello,#ZgotmplZ`,
   306  				`greeting&#61;H%69%2c&amp;addressee&#61;%28World%29`,
   307  				`greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
   308  				// Commas are escaped.
   309  				`%2cfoo/%2c`,
   310  			},
   311  		},
   312  		{
   313  			`<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`,
   314  			[]string{
   315  				`#ZgotmplZ`,
   316  				`#ZgotmplZ`,
   317  				`Hello,#ZgotmplZ`,
   318  				` dir=%22ltr%22`,
   319  				`#ZgotmplZ, World!%22%29;`,
   320  				`Hello,#ZgotmplZ`,
   321  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   322  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   323  				`%2cfoo/%2c`,
   324  			},
   325  		},
   326  		{
   327  			`<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`,
   328  			[]string{
   329  				`#ZgotmplZ`,
   330  				`#ZgotmplZ`,
   331  				`Hello,#ZgotmplZ`,
   332  				` dir=%22ltr%22`,
   333  				`#ZgotmplZ, World!%22%29;`,
   334  				`Hello,#ZgotmplZ`,
   335  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   336  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   337  				`%2cfoo/%2c`,
   338  			},
   339  		},
   340  		{
   341  			`<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`,
   342  			[]string{
   343  				`#ZgotmplZ`,
   344  				`#ZgotmplZ`,
   345  				`Hello,#ZgotmplZ`,
   346  				` dir=%22ltr%22`,
   347  				`#ZgotmplZ, World!%22%29;`,
   348  				`Hello,#ZgotmplZ`,
   349  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   350  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   351  				`%2cfoo/%2c`,
   352  			},
   353  		},
   354  		{
   355  			`<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`,
   356  			[]string{
   357  				`#ZgotmplZ`,
   358  				`#ZgotmplZ`,
   359  				`Hello,#ZgotmplZ`,
   360  				` dir=%22ltr%22`,
   361  				`#ZgotmplZ, World!%22%29;`,
   362  				`Hello,#ZgotmplZ`,
   363  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   364  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   365  				`%2cfoo/%2c`,
   366  			},
   367  		},
   368  		{
   369  			`<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`,
   370  			[]string{
   371  				`#ZgotmplZ`,
   372  				`#ZgotmplZ`,
   373  				`Hello,#ZgotmplZ`,
   374  				` dir=%22ltr%22`,
   375  				`#ZgotmplZ, World!%22%29;`,
   376  				`Hello,#ZgotmplZ`,
   377  				`greeting=H%69%2c&amp;addressee=%28World%29`,
   378  				`greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
   379  				`%2cfoo/%2c`,
   380  			},
   381  		},
   382  	}
   383  
   384  	for _, test := range tests {
   385  		tmpl := Must(New("x").Parse(test.input))
   386  		pre := strings.Index(test.input, "{{.}}")
   387  		post := len(test.input) - (pre + 5)
   388  		var b bytes.Buffer
   389  		for i, x := range data {
   390  			b.Reset()
   391  			if err := tmpl.Execute(&b, x); err != nil {
   392  				t.Errorf("%q with %v: %s", test.input, x, err)
   393  				continue
   394  			}
   395  			if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
   396  				t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
   397  				continue
   398  			}
   399  		}
   400  	}
   401  }
   402  
   403  // Test that we print using the String method. Was issue 3073.
   404  type myStringer struct {
   405  	v int
   406  }
   407  
   408  func (s *myStringer) String() string {
   409  	return fmt.Sprintf("string=%d", s.v)
   410  }
   411  
   412  type errorer struct {
   413  	v int
   414  }
   415  
   416  func (s *errorer) Error() string {
   417  	return fmt.Sprintf("error=%d", s.v)
   418  }
   419  
   420  func TestStringer(t *testing.T) {
   421  	s := &myStringer{3}
   422  	b := new(bytes.Buffer)
   423  	tmpl := Must(New("x").Parse("{{.}}"))
   424  	if err := tmpl.Execute(b, s); err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	var expect = "string=3"
   428  	if b.String() != expect {
   429  		t.Errorf("expected %q got %q", expect, b.String())
   430  	}
   431  	e := &errorer{7}
   432  	b.Reset()
   433  	if err := tmpl.Execute(b, e); err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	expect = "error=7"
   437  	if b.String() != expect {
   438  		t.Errorf("expected %q got %q", expect, b.String())
   439  	}
   440  }
   441  
   442  // https://golang.org/issue/5982
   443  func TestEscapingNilNonemptyInterfaces(t *testing.T) {
   444  	tmpl := Must(New("x").Parse("{{.E}}"))
   445  
   446  	got := new(bytes.Buffer)
   447  	testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
   448  	tmpl.Execute(got, testData)
   449  
   450  	// A non-empty interface should print like an empty interface.
   451  	want := new(bytes.Buffer)
   452  	data := struct{ E interface{} }{}
   453  	tmpl.Execute(want, data)
   454  
   455  	if !bytes.Equal(want.Bytes(), got.Bytes()) {
   456  		t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
   457  	}
   458  }
   459  

View as plain text