Home | History | Annotate | Download | only in 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\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 }
    322