github.com/filosottile/go@v0.0.0-20170906193555-dbed9972d994/src/html/template/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  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\x21`),
    22  		URL(`greeting=H%69&addressee=(World)`),
    23  	}
    24  
    25  	// For each content sensitive escaper, see how it does on
    26  	// each of the typed strings above.
    27  	tests := []struct {
    28  		// A template containing a single {{.}}.
    29  		input string
    30  		want  []string
    31  	}{
    32  		{
    33  			`<style>{{.}} { color: blue }</style>`,
    34  			[]string{
    35  				`ZgotmplZ`,
    36  				// Allowed but not escaped.
    37  				`a[href =~ "//example.com"]#foo`,
    38  				`ZgotmplZ`,
    39  				`ZgotmplZ`,
    40  				`ZgotmplZ`,
    41  				`ZgotmplZ`,
    42  				`ZgotmplZ`,
    43  			},
    44  		},
    45  		{
    46  			`<div style="{{.}}">`,
    47  			[]string{
    48  				`ZgotmplZ`,
    49  				// Allowed and HTML escaped.
    50  				`a[href =~ &#34;//example.com&#34;]#foo`,
    51  				`ZgotmplZ`,
    52  				`ZgotmplZ`,
    53  				`ZgotmplZ`,
    54  				`ZgotmplZ`,
    55  				`ZgotmplZ`,
    56  			},
    57  		},
    58  		{
    59  			`{{.}}`,
    60  			[]string{
    61  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
    62  				`a[href =~ &#34;//example.com&#34;]#foo`,
    63  				// Not escaped.
    64  				`Hello, <b>World</b> &amp;tc!`,
    65  				` dir=&#34;ltr&#34;`,
    66  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
    67  				`Hello, World &amp; O&#39;Reilly\x21`,
    68  				`greeting=H%69&amp;addressee=(World)`,
    69  			},
    70  		},
    71  		{
    72  			`<a{{.}}>`,
    73  			[]string{
    74  				`ZgotmplZ`,
    75  				`ZgotmplZ`,
    76  				`ZgotmplZ`,
    77  				// Allowed and HTML escaped.
    78  				` dir="ltr"`,
    79  				`ZgotmplZ`,
    80  				`ZgotmplZ`,
    81  				`ZgotmplZ`,
    82  			},
    83  		},
    84  		{
    85  			`<a title={{.}}>`,
    86  			[]string{
    87  				`&lt;b&gt;&#32;&#34;foo%&#34;&#32;O&#39;Reilly&#32;&amp;bar;`,
    88  				`a[href&#32;&#61;~&#32;&#34;//example.com&#34;]#foo`,
    89  				// Tags stripped, spaces escaped, entity not re-escaped.
    90  				`Hello,&#32;World&#32;&amp;tc!`,
    91  				`&#32;dir&#61;&#34;ltr&#34;`,
    92  				`c&#32;&amp;&amp;&#32;alert(&#34;Hello,&#32;World!&#34;);`,
    93  				`Hello,&#32;World&#32;&amp;&#32;O&#39;Reilly\x21`,
    94  				`greeting&#61;H%69&amp;addressee&#61;(World)`,
    95  			},
    96  		},
    97  		{
    98  			`<a title='{{.}}'>`,
    99  			[]string{
   100  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   101  				`a[href =~ &#34;//example.com&#34;]#foo`,
   102  				// Tags stripped, entity not re-escaped.
   103  				`Hello, World &amp;tc!`,
   104  				` dir=&#34;ltr&#34;`,
   105  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   106  				`Hello, World &amp; O&#39;Reilly\x21`,
   107  				`greeting=H%69&amp;addressee=(World)`,
   108  			},
   109  		},
   110  		{
   111  			`<textarea>{{.}}</textarea>`,
   112  			[]string{
   113  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   114  				`a[href =~ &#34;//example.com&#34;]#foo`,
   115  				// Angle brackets escaped to prevent injection of close tags, entity not re-escaped.
   116  				`Hello, &lt;b&gt;World&lt;/b&gt; &amp;tc!`,
   117  				` dir=&#34;ltr&#34;`,
   118  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   119  				`Hello, World &amp; O&#39;Reilly\x21`,
   120  				`greeting=H%69&amp;addressee=(World)`,
   121  			},
   122  		},
   123  		{
   124  			`<script>alert({{.}})</script>`,
   125  			[]string{
   126  				`"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
   127  				`"a[href =~ \"//example.com\"]#foo"`,
   128  				`"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
   129  				`" dir=\"ltr\""`,
   130  				// Not escaped.
   131  				`c && alert("Hello, World!");`,
   132  				// Escape sequence not over-escaped.
   133  				`"Hello, World & O'Reilly\x21"`,
   134  				`"greeting=H%69\u0026addressee=(World)"`,
   135  			},
   136  		},
   137  		{
   138  			`<button onclick="alert({{.}})">`,
   139  			[]string{
   140  				`&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly \u0026bar;&#34;`,
   141  				`&#34;a[href =~ \&#34;//example.com\&#34;]#foo&#34;`,
   142  				`&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!&#34;`,
   143  				`&#34; dir=\&#34;ltr\&#34;&#34;`,
   144  				// Not JS escaped but HTML escaped.
   145  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   146  				// Escape sequence not over-escaped.
   147  				`&#34;Hello, World &amp; O&#39;Reilly\x21&#34;`,
   148  				`&#34;greeting=H%69\u0026addressee=(World)&#34;`,
   149  			},
   150  		},
   151  		{
   152  			`<script>alert("{{.}}")</script>`,
   153  			[]string{
   154  				`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
   155  				`a[href =~ \x22\/\/example.com\x22]#foo`,
   156  				`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
   157  				` dir=\x22ltr\x22`,
   158  				`c \x26\x26 alert(\x22Hello, World!\x22);`,
   159  				// Escape sequence not over-escaped.
   160  				`Hello, World \x26 O\x27Reilly\x21`,
   161  				`greeting=H%69\x26addressee=(World)`,
   162  			},
   163  		},
   164  		{
   165  			`<script type="text/javascript">alert("{{.}}")</script>`,
   166  			[]string{
   167  				`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
   168  				`a[href =~ \x22\/\/example.com\x22]#foo`,
   169  				`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
   170  				` dir=\x22ltr\x22`,
   171  				`c \x26\x26 alert(\x22Hello, World!\x22);`,
   172  				// Escape sequence not over-escaped.
   173  				`Hello, World \x26 O\x27Reilly\x21`,
   174  				`greeting=H%69\x26addressee=(World)`,
   175  			},
   176  		},
   177  		{
   178  			`<script type="text/javascript">alert({{.}})</script>`,
   179  			[]string{
   180  				`"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
   181  				`"a[href =~ \"//example.com\"]#foo"`,
   182  				`"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
   183  				`" dir=\"ltr\""`,
   184  				// Not escaped.
   185  				`c && alert("Hello, World!");`,
   186  				// Escape sequence not over-escaped.
   187  				`"Hello, World & O'Reilly\x21"`,
   188  				`"greeting=H%69\u0026addressee=(World)"`,
   189  			},
   190  		},
   191  		{
   192  			// Not treated as JS. The output is same as for <div>{{.}}</div>
   193  			`<script type="text/template">{{.}}</script>`,
   194  			[]string{
   195  				`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
   196  				`a[href =~ &#34;//example.com&#34;]#foo`,
   197  				// Not escaped.
   198  				`Hello, <b>World</b> &amp;tc!`,
   199  				` dir=&#34;ltr&#34;`,
   200  				`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
   201  				`Hello, World &amp; O&#39;Reilly\x21`,
   202  				`greeting=H%69&amp;addressee=(World)`,
   203  			},
   204  		},
   205  		{
   206  			`<button onclick='alert("{{.}}")'>`,
   207  			[]string{
   208  				`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
   209  				`a[href =~ \x22\/\/example.com\x22]#foo`,
   210  				`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
   211  				` dir=\x22ltr\x22`,
   212  				`c \x26\x26 alert(\x22Hello, World!\x22);`,
   213  				// Escape sequence not over-escaped.
   214  				`Hello, World \x26 O\x27Reilly\x21`,
   215  				`greeting=H%69\x26addressee=(World)`,
   216  			},
   217  		},
   218  		{
   219  			`<a href="?q={{.}}">`,
   220  			[]string{
   221  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   222  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   223  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   224  				`%20dir%3d%22ltr%22`,
   225  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   226  				`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
   227  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
   228  				`greeting=H%69&amp;addressee=%28World%29`,
   229  			},
   230  		},
   231  		{
   232  			`<style>body { background: url('?img={{.}}') }</style>`,
   233  			[]string{
   234  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   235  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   236  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   237  				`%20dir%3d%22ltr%22`,
   238  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   239  				`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
   240  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
   241  				`greeting=H%69&addressee=%28World%29`,
   242  			},
   243  		},
   244  	}
   245  
   246  	for _, test := range tests {
   247  		tmpl := Must(New("x").Parse(test.input))
   248  		pre := strings.Index(test.input, "{{.}}")
   249  		post := len(test.input) - (pre + 5)
   250  		var b bytes.Buffer
   251  		for i, x := range data {
   252  			b.Reset()
   253  			if err := tmpl.Execute(&b, x); err != nil {
   254  				t.Errorf("%q with %v: %s", test.input, x, err)
   255  				continue
   256  			}
   257  			if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
   258  				t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
   259  				continue
   260  			}
   261  		}
   262  	}
   263  }
   264  
   265  // Test that we print using the String method. Was issue 3073.
   266  type stringer struct {
   267  	v int
   268  }
   269  
   270  func (s *stringer) String() string {
   271  	return fmt.Sprintf("string=%d", s.v)
   272  }
   273  
   274  type errorer struct {
   275  	v int
   276  }
   277  
   278  func (s *errorer) Error() string {
   279  	return fmt.Sprintf("error=%d", s.v)
   280  }
   281  
   282  func TestStringer(t *testing.T) {
   283  	s := &stringer{3}
   284  	b := new(bytes.Buffer)
   285  	tmpl := Must(New("x").Parse("{{.}}"))
   286  	if err := tmpl.Execute(b, s); err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	var expect = "string=3"
   290  	if b.String() != expect {
   291  		t.Errorf("expected %q got %q", expect, b.String())
   292  	}
   293  	e := &errorer{7}
   294  	b.Reset()
   295  	if err := tmpl.Execute(b, e); err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	expect = "error=7"
   299  	if b.String() != expect {
   300  		t.Errorf("expected %q got %q", expect, b.String())
   301  	}
   302  }
   303  
   304  // https://golang.org/issue/5982
   305  func TestEscapingNilNonemptyInterfaces(t *testing.T) {
   306  	tmpl := Must(New("x").Parse("{{.E}}"))
   307  
   308  	got := new(bytes.Buffer)
   309  	testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
   310  	tmpl.Execute(got, testData)
   311  
   312  	// Use this data instead of just hard-coding "&lt;nil&gt;" to avoid
   313  	// dependencies on the html escaper and the behavior of fmt w.r.t. nil.
   314  	want := new(bytes.Buffer)
   315  	data := struct{ E string }{E: fmt.Sprint(nil)}
   316  	tmpl.Execute(want, data)
   317  
   318  	if !bytes.Equal(want.Bytes(), got.Bytes()) {
   319  		t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
   320  	}
   321  }