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