github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/tpl/internal/go_templates/htmltemplate/content_test.go (about)

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