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> &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 =~ "//example.com"]#foo`, 51 `ZgotmplZ`, 52 `ZgotmplZ`, 53 `ZgotmplZ`, 54 `ZgotmplZ`, 55 `ZgotmplZ`, 56 }, 57 }, 58 { 59 `{{.}}`, 60 []string{ 61 `<b> "foo%" O'Reilly &bar;`, 62 `a[href =~ "//example.com"]#foo`, 63 // Not escaped. 64 `Hello, <b>World</b> &tc!`, 65 ` dir="ltr"`, 66 `c && alert("Hello, World!");`, 67 `Hello, World & O'Reilly\x21`, 68 `greeting=H%69&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 `<b> "foo%" O'Reilly &bar;`, 88 `a[href =~ "//example.com"]#foo`, 89 // Tags stripped, spaces escaped, entity not re-escaped. 90 `Hello, World &tc!`, 91 ` dir="ltr"`, 92 `c && alert("Hello, World!");`, 93 `Hello, World & O'Reilly\x21`, 94 `greeting=H%69&addressee=(World)`, 95 }, 96 }, 97 { 98 `<a title='{{.}}'>`, 99 []string{ 100 `<b> "foo%" O'Reilly &bar;`, 101 `a[href =~ "//example.com"]#foo`, 102 // Tags stripped, entity not re-escaped. 103 `Hello, World &tc!`, 104 ` dir="ltr"`, 105 `c && alert("Hello, World!");`, 106 `Hello, World & O'Reilly\x21`, 107 `greeting=H%69&addressee=(World)`, 108 }, 109 }, 110 { 111 `<textarea>{{.}}</textarea>`, 112 []string{ 113 `<b> "foo%" O'Reilly &bar;`, 114 `a[href =~ "//example.com"]#foo`, 115 // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. 116 `Hello, <b>World</b> &tc!`, 117 ` dir="ltr"`, 118 `c && alert("Hello, World!");`, 119 `Hello, World & O'Reilly\x21`, 120 `greeting=H%69&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 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, 141 `"a[href =~ \"//example.com\"]#foo"`, 142 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, 143 `" dir=\"ltr\""`, 144 // Not JS escaped but HTML escaped. 145 `c && alert("Hello, World!");`, 146 // Escape sequence not over-escaped. 147 `"Hello, World & O'Reilly\x21"`, 148 `"greeting=H%69\u0026addressee=(World)"`, 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&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 "<nil>" 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 } 281