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