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