github.com/sean-/go@v0.0.0-20151219100004-97f854cd7bb6/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  			`<button onclick='alert("{{.}}")'>`,
   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  			`<a href="?q={{.}}">`,
   179  			[]string{
   180  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   181  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   182  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   183  				`%20dir%3d%22ltr%22`,
   184  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   185  				`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
   186  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
   187  				`greeting=H%69&amp;addressee=%28World%29`,
   188  			},
   189  		},
   190  		{
   191  			`<style>body { background: url('?img={{.}}') }</style>`,
   192  			[]string{
   193  				`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
   194  				`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
   195  				`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
   196  				`%20dir%3d%22ltr%22`,
   197  				`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
   198  				`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
   199  				// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
   200  				`greeting=H%69&addressee=%28World%29`,
   201  			},
   202  		},
   203  	}
   204  
   205  	for _, test := range tests {
   206  		tmpl := Must(New("x").Parse(test.input))
   207  		pre := strings.Index(test.input, "{{.}}")
   208  		post := len(test.input) - (pre + 5)
   209  		var b bytes.Buffer
   210  		for i, x := range data {
   211  			b.Reset()
   212  			if err := tmpl.Execute(&b, x); err != nil {
   213  				t.Errorf("%q with %v: %s", test.input, x, err)
   214  				continue
   215  			}
   216  			if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
   217  				t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
   218  				continue
   219  			}
   220  		}
   221  	}
   222  }
   223  
   224  // Test that we print using the String method. Was issue 3073.
   225  type stringer struct {
   226  	v int
   227  }
   228  
   229  func (s *stringer) String() string {
   230  	return fmt.Sprintf("string=%d", s.v)
   231  }
   232  
   233  type errorer struct {
   234  	v int
   235  }
   236  
   237  func (s *errorer) Error() string {
   238  	return fmt.Sprintf("error=%d", s.v)
   239  }
   240  
   241  func TestStringer(t *testing.T) {
   242  	s := &stringer{3}
   243  	b := new(bytes.Buffer)
   244  	tmpl := Must(New("x").Parse("{{.}}"))
   245  	if err := tmpl.Execute(b, s); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	var expect = "string=3"
   249  	if b.String() != expect {
   250  		t.Errorf("expected %q got %q", expect, b.String())
   251  	}
   252  	e := &errorer{7}
   253  	b.Reset()
   254  	if err := tmpl.Execute(b, e); err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	expect = "error=7"
   258  	if b.String() != expect {
   259  		t.Errorf("expected %q got %q", expect, b.String())
   260  	}
   261  }
   262  
   263  // https://golang.org/issue/5982
   264  func TestEscapingNilNonemptyInterfaces(t *testing.T) {
   265  	tmpl := Must(New("x").Parse("{{.E}}"))
   266  
   267  	got := new(bytes.Buffer)
   268  	testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
   269  	tmpl.Execute(got, testData)
   270  
   271  	// Use this data instead of just hard-coding "&lt;nil&gt;" to avoid
   272  	// dependencies on the html escaper and the behavior of fmt w.r.t. nil.
   273  	want := new(bytes.Buffer)
   274  	data := struct{ E string }{E: fmt.Sprint(nil)}
   275  	tmpl.Execute(want, data)
   276  
   277  	if !bytes.Equal(want.Bytes(), got.Bytes()) {
   278  		t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
   279  	}
   280  }