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