| 295 |
|
|
``,
| 296 |
|
|
``,
| 297 |
|
|
},
|
| 298 |
|
|
{
|
| 299 |
|
|
"styleColorPassed",
|
| 300 |
|
|
``,
|
| 301 |
|
|
``,
|
| 302 |
|
|
},
|
| 303 |
|
|
{
|
| 304 |
|
|
"styleObfuscatedExpressionBlocked",
|
| 305 |
|
|
``,
|
| 306 |
|
|
``,
|
| 307 |
|
|
},
|
| 308 |
|
|
{
|
| 309 |
|
|
"styleMozBindingBlocked",
|
| 310 |
|
|
``,
|
| 311 |
|
|
``,
|
| 312 |
|
|
},
|
| 313 |
|
|
{
|
| 314 |
|
|
"styleObfuscatedMozBindingBlocked",
|
| 315 |
|
|
``,
|
| 316 |
|
|
``,
|
| 317 |
|
|
},
|
| 318 |
|
|
{
|
| 319 |
|
|
"styleFontNameString",
|
| 320 |
|
|
``,
|
| 321 |
|
|
``,
|
| 322 |
|
|
},
|
| 323 |
|
|
{
|
| 324 |
|
|
"styleFontNameString",
|
| 325 |
|
|
``,
|
| 326 |
|
|
``,
|
| 327 |
|
|
},
|
| 328 |
|
|
{
|
| 329 |
|
|
"styleFontNameUnquoted",
|
| 330 |
|
|
``,
|
| 331 |
|
|
``,
|
| 332 |
|
|
},
|
| 333 |
|
|
{
|
| 334 |
|
|
"styleURLQueryEncoded",
|
| 335 |
|
|
`.png"}})">`,
|
| 336 |
|
|
``,
|
| 337 |
|
|
},
|
| 338 |
|
|
{
|
| 339 |
|
|
"styleQuotedURLQueryEncoded",
|
| 340 |
|
|
`.png"}}')">`,
|
| 341 |
|
|
``,
|
| 342 |
|
|
},
|
| 343 |
|
|
{
|
| 344 |
|
|
"styleStrQueryEncoded",
|
| 345 |
|
|
`.png"}}'">`,
|
| 346 |
|
|
``,
|
| 347 |
|
|
},
|
| 348 |
|
|
{
|
| 349 |
|
|
"styleURLBadProtocolBlocked",
|
| 350 |
|
|
``,
|
| 351 |
|
|
``,
|
| 352 |
|
|
},
|
| 353 |
|
|
{
|
| 354 |
|
|
"styleStrBadProtocolBlocked",
|
| 355 |
|
|
``,
|
| 356 |
|
|
``,
|
| 357 |
|
|
},
|
| 358 |
|
|
{
|
| 359 |
|
|
"styleStrEncodedProtocolEncoded",
|
| 360 |
|
|
``,
|
| 361 |
|
|
// The CSS string 'javascript\\3a alert(1337)' does not contains a colon.
|
| 362 |
|
|
``,
|
| 363 |
|
|
},
|
| 364 |
|
|
{
|
| 365 |
|
|
"styleURLGoodProtocolPassed",
|
| 366 |
|
|
`;{}.html"}}')">`,
|
| 367 |
|
|
``,
|
| 368 |
|
|
},
|
| 369 |
|
|
{
|
| 370 |
|
|
"styleStrGoodProtocolPassed",
|
| 371 |
|
|
`;{}.html"}}'">`,
|
| 372 |
|
|
``,
|
| 373 |
|
|
},
|
| 374 |
|
|
{
|
| 375 |
|
|
"styleURLEncodedForHTMLInAttr",
|
| 376 |
|
|
``,
|
| 377 |
|
|
``,
|
| 378 |
|
|
},
|
| 379 |
|
|
{
|
| 380 |
|
|
"styleURLNotEncodedForHTMLInCdata",
|
| 381 |
|
|
``,
|
| 382 |
|
|
``,
|
| 383 |
|
|
},
|
| 384 |
|
|
{
|
| 385 |
|
|
"styleURLMixedCase",
|
| 386 |
|
|
``,
|
| 387 |
|
|
``,
|
| 388 |
|
|
},
|
| 389 |
|
|
{
|
| 390 |
|
|
"stylePropertyPairPassed",
|
| 391 |
|
|
``,
|
| 392 |
|
|
``,
|
| 393 |
|
|
},
|
| 394 |
|
|
{
|
| 395 |
|
|
"styleStrSpecialsEncoded",
|
| 396 |
|
|
``,
|
| 397 |
|
|
``,
|
| 398 |
|
|
},
|
| 399 |
|
|
{
|
| 400 |
|
|
"styleURLSpecialsEncoded",
|
| 401 |
|
|
``,
|
| 402 |
|
|
``,
|
| 403 |
|
|
},
|
| 404 |
|
|
{
|
| 405 |
|
|
"HTML comment",
|
| 406 |
|
|
"Hello, {{.C}}",
|
| 407 |
|
|
"Hello, <Cincinatti>",
|
| 408 |
|
|
},
|
| 409 |
|
|
{
|
| 410 |
|
|
"HTML comment not first < in text node.",
|
| 411 |
|
|
"<!--",
|
| 412 |
|
|
"<!--",
|
| 413 |
|
|
},
|
| 414 |
|
|
{
|
| 415 |
|
|
"HTML normalization 1",
|
| 416 |
|
|
"a < b",
|
| 417 |
|
|
"a < b",
|
| 418 |
|
|
},
|
| 419 |
|
|
{
|
| 420 |
|
|
"HTML normalization 2",
|
| 421 |
|
|
"a << b",
|
| 422 |
|
|
"a << b",
|
| 423 |
|
|
},
|
| 424 |
|
|
{
|
| 425 |
|
|
"HTML normalization 3",
|
| 426 |
|
|
"a<b",
|
| 427 |
|
|
"a<b",
|
| 428 |
|
|
},
|
| 429 |
|
|
{
|
| 430 |
|
|
"HTML doctype not normalized",
|
| 431 |
|
|
"Hello, World!",
|
| 432 |
|
|
"Hello, World!",
|
| 433 |
|
|
},
|
| 434 |
|
|
{
|
| 435 |
|
|
"No doctype injection",
|
| 436 |
|
|
`
|
| 437 |
|
|
"<!DOCTYPE",
|
| 438 |
|
|
},
|
| 439 |
|
|
{
|
| 440 |
|
|
"Split HTML comment",
|
| 441 |
|
|
"Hello, {{.C}}{{else}}world -->{{.W}}{{end}}",
|
| 442 |
|
|
"Hello, <Cincinatti>",
|
| 443 |
|
|
},
|
| 444 |
|
|
{
|
| 445 |
|
|
"JS line comment",
|
| 446 |
|
|
"",
|
| 448 |
|
|
"",
|
| 450 |
|
|
},
|
| 451 |
|
|
{
|
| 452 |
|
|
"JS multiline block comment",
|
| 453 |
|
|
"",
|
| 455 |
|
|
// Newline separates break from call. If newline
|
| 456 |
|
|
// removed, then break will consume label leaving
|
| 457 |
|
|
// code invalid.
|
| 458 |
|
|
"",
|
| 460 |
|
|
},
|
| 461 |
|
|
{
|
| 462 |
|
|
"JS single-line block comment",
|
| 463 |
|
|
"",
|
| 466 |
|
|
// Newline separates break from call. If newline
|
| 467 |
|
|
// removed, then break will consume label leaving
|
| 468 |
|
|
// code invalid.
|
| 469 |
|
|
"",
|
| 472 |
|
|
},
|
| 473 |
|
|
{
|
| 474 |
|
|
"JS block comment flush with mathematical division",
|
| 475 |
|
|
"",
|
| 476 |
|
|
"",
|
| 477 |
|
|
},
|
| 478 |
|
|
{
|
| 479 |
|
|
"JS mixed comments",
|
| 480 |
|
|
"",
|
| 481 |
|
|
"",
|
| 482 |
|
|
},
|
| 483 |
|
|
{
|
| 484 |
|
|
"CSS comments",
|
| 485 |
|
|
"`,
|
| 487 |
|
|
"",
|
| 489 |
|
|
},
|
| 490 |
|
|
{
|
| 491 |
|
|
"JS attr block comment",
|
| 492 |
|
|
``,
|
| 493 |
|
|
// Attribute comment tests should pass if the comments
|
| 494 |
|
|
// are successfully elided.
|
| 495 |
|
|
``,
|
| 496 |
|
|
},
|
| 497 |
|
|
{
|
| 498 |
|
|
"JS attr line comment",
|
| 499 |
|
|
``,
|
| 500 |
|
|
``,
|
| 501 |
|
|
},
|
| 502 |
|
|
{
|
| 503 |
|
|
"CSS attr block comment",
|
| 504 |
|
|
``,
|
| 505 |
|
|
``,
|
| 506 |
|
|
},
|
| 507 |
|
|
{
|
| 508 |
|
|
"CSS attr line comment",
|
| 509 |
|
|
``,
|
| 510 |
|
|
``,
|
| 511 |
|
|
},
|
| 512 |
|
|
{
|
| 513 |
|
|
"HTML substitution commented out",
|
| 514 |
|
|
"",
|
| 515 |
|
|
"",
|
| 516 |
|
|
},
|
| 517 |
|
|
{
|
| 518 |
|
|
"Comment ends flush with start",
|
| 519 |
|
|
"",
|
| 520 |
|
|
"",
|
| 521 |
|
|
},
|
| 522 |
|
|
{
|
| 523 |
|
|
"typed HTML in text",
|
| 524 |
|
|
`{{.W}}`,
|
| 525 |
|
|
`¡Hello, !`,
|
| 526 |
|
|
},
|
| 527 |
|
|
{
|
| 528 |
|
|
"typed HTML in attribute",
|
| 529 |
|
|
``,
|
| 530 |
|
|
``,
|
| 531 |
|
|
},
|
| 532 |
|
|
{
|
| 533 |
|
|
"typed HTML in script",
|
| 534 |
|
|
`
|
| 535 |
|
|
`
|
| 536 |
|
|
},
|
| 537 |
|
|
{
|
| 538 |
|
|
"typed HTML in RCDATA",
|
| 539 |
|
|
``,
|
| 540 |
|
|
``,
|
| 541 |
|
|
},
|
| 542 |
|
|
{
|
| 543 |
|
|
"range in textarea",
|
| 544 |
|
|
"",
|
| 545 |
|
|
"",
|
| 546 |
|
|
},
|
| 547 |
|
|
{
|
| 548 |
|
|
"auditable exemption from escaping",
|
| 549 |
|
|
"{{range .A}}{{. | noescape}}{{end}}",
|
| 550 |
|
|
"",
|
| 551 |
|
|
},
|
| 552 |
|
|
{
|
| 553 |
|
|
"No tag injection",
|
| 554 |
|
|
`{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`,
|
| 555 |
|
|
`10$<script src,evil.org/pwnd.js...`,
|
| 556 |
|
|
},
|
| 557 |
|
|
{
|
| 558 |
|
|
"No comment injection",
|
| 559 |
|
|
`<{{"!--"}}`,
|
| 560 |
|
|
`<!--`,
|
| 561 |
|
|
},
|
| 562 |
|
|
{
|
| 563 |
|
|
"No RCDATA end tag injection",
|
| 564 |
|
|
``,
|
| 565 |
|
|
``,
|
| 566 |
|
|
},
|
| 567 |
|
|
{
|
| 568 |
|
|
"optional attrs",
|
| 569 |
|
|
`![]()
|
| 570 |
|
|
`{{if .T}} id="{{""}}"{{end}}` +
|
| 571 |
|
|
// Double quotes inside if/else.
|
| 572 |
|
|
` src=` +
|
| 573 |
|
|
`{{if .T}}"?{{""}}"` +
|
| 574 |
|
|
`{{else}}"images/cleardot.gif"{{end}}` +
|
| 575 |
|
|
// Missing space before title, but it is not a
|
| 576 |
|
|
// part of the src attribute.
|
| 577 |
|
|
`{{if .T}}title="{{""}}"{{end}}` +
|
| 578 |
|
|
// Quotes outside if/else.
|
| 579 |
|
|
` alt="` +
|
| 580 |
|
|
`{{if .T}}{{""}}` +
|
| 581 |
|
|
`{{else}}{{if .F}}{{""}}{{end}}` +
|
| 582 |
|
|
`{{end}}"` +
|
| 583 |
|
|
`>`,
|
| 584 |
|
|
` `,
|
| 585 |
|
|
},
|
| 586 |
|
|
{
|
| 587 |
|
|
"conditional valueless attr name",
|
| 588 |
|
|
``,
|
| 589 |
|
|
``,
|
| 590 |
|
|
},
|
| 591 |
|
|
{
|
| 592 |
|
|
"conditional dynamic valueless attr name 1",
|
| 593 |
|
|
``,
|
| 594 |
|
|
``,
|
| 595 |
|
|
},
|
| 596 |
|
|
{
|
| 597 |
|
|
"conditional dynamic valueless attr name 2",
|
| 598 |
|
|
``,
|
| 599 |
|
|
``,
|
| 600 |
|
|
},
|
| 601 |
|
|
{
|
| 602 |
|
|
"dynamic attribute name",
|
| 603 |
|
|
` `,
|
| 604 |
|
|
// Treated as JS since quotes are inserted.
|
| 605 |
|
|
` `,
|
| 606 |
|
|
},
|
| 607 |
|
|
{
|
| 608 |
|
|
"bad dynamic attribute name 1",
|
| 609 |
|
|
// Allow checked, selected, disabled, but not JS or
|
| 610 |
|
|
// CSS attributes.
|
| 611 |
|
|
``,
|
| 612 |
|
|
``,
|
| 613 |
|
|
},
|
| 614 |
|
|
{
|
| 615 |
|
|
"bad dynamic attribute name 2",
|
| 616 |
|
|
``,
|
| 617 |
|
|
``,
|
| 618 |
|
|
},
|
| 619 |
|
|
{
|
| 620 |
|
|
"bad dynamic attribute name 3",
|
| 621 |
|
|
// Allow title or alt, but not a URL.
|
| 622 |
|
|
` `,
|
| 623 |
|
|
` `,
|
| 624 |
|
|
},
|
| 625 |
|
|
{
|
| 626 |
|
|
"bad dynamic attribute name 4",
|
| 627 |
|
|
// Structure preservation requires values to associate
|
| 628 |
|
|
// with a consistent attribute.
|
| 629 |
|
|
``,
|
| 630 |
|
|
``,
|
| 631 |
|
|
},
|
| 632 |
|
|
{
|
| 633 |
|
|
"dynamic element name",
|
| 634 |
|
|
`...`,
| 635 |
|
|
`...`,
| 636 |
|
|
},
|
| 637 |
|
|
{
|
| 638 |
|
|
"bad dynamic element name",
|
| 639 |
|
|
// Dynamic element names are typically used to switch
|
| 640 |
|
|
// between (thead, tfoot, tbody), (ul, ol), (th, td),
|
| 641 |
|
|
// and other replaceable sets.
|
| 642 |
|
|
// We do not currently easily support (ul, ol).
|
| 643 |
|
|
// If we do change to support that, this test should
|
| 644 |
|
|
// catch failures to filter out special tag names which
|
| 645 |
|
|
// would violate the structure preservation property --
|
| 646 |
|
|
// if any special tag name could be substituted, then
|
| 647 |
|
|
// the content could be raw text/RCDATA for some inputs
|
| 648 |
|
|
// and regular HTML content for others.
|
| 649 |
|
|
`<{{"script"}}>{{"doEvil()"}}{{"script"}}>`,
|
| 650 |
|
|
`<script>doEvil()</script>`,
|
| 651 |
|
|
},
|
| 652 |
|
|
}
|
| 653 |
|
|
|
| 654 |
|
|
for _, test := range tests {
|
| 655 |
|
|
tmpl := New(test.name)
|
| 656 |
|
|
// TODO: Move noescape into template/func.go
|
| 657 |
|
|
tmpl.Funcs(FuncMap{
|
| 658 |
|
|
"noescape": func(a ...interface{}) string {
|
| 659 |
|
|
return fmt.Sprint(a...)
|
| 660 |
|
|
},
|
| 661 |
|
|
})
|
| 662 |
|
|
tmpl = Must(tmpl.Parse(test.input))
|
| 663 |
|
|
b := new(bytes.Buffer)
|
| 664 |
|
|
if err := tmpl.Execute(b, data); err != nil {
|
| 665 |
|
|
t.Errorf("%s: template execution failed: %s", test.name, err)
|
| 666 |
|
|
continue
|
| 667 |
|
|
}
|
| 668 |
|
|
if w, g := test.output, b.String(); w != g {
|
| 669 |
|
|
t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
|
| 670 |
|
|
continue
|
| 671 |
|
|
}
|
| 672 |
|
|
b.Reset()
|
| 673 |
|
|
if err := tmpl.Execute(b, pdata); err != nil {
|
| 674 |
|
|
t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
|
| 675 |
|
|
continue
|
| 676 |
|
|
}
|
| 677 |
|
|
if w, g := test.output, b.String(); w != g {
|
| 678 |
|
|
t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
|
| 679 |
|
|
continue
|
| 680 |
|
|
}
|
| 681 |
|
|
}
|
| 682 |
|
|
}
|
| 683 |
|
|
|
| 684 |
|
|
func TestEscapeSet(t *testing.T) {
|
| 685 |
|
|
type dataItem struct {
|
| 686 |
|
|
Children []*dataItem
|
| 687 |
|
|
X string
|
| 688 |
|
|
}
|
| 689 |
|
|
|
| 690 |
|
|
data := dataItem{
|
| 691 |
|
|
Children: []*dataItem{
|
| 692 |
|
|
{X: "foo"},
|
| 693 |
|
|
{X: ""},
|
| 694 |
|
|
{
|
| 695 |
|
|
Children: []*dataItem{
|
| 696 |
|
|
{X: "baz"},
|
| 697 |
|
|
},
|
| 698 |
|
|
},
|
| 699 |
|
|
},
|
| 700 |
|
|
}
|
| 701 |
|
|
|
| 702 |
|
|
tests := []struct {
|
| 703 |
|
|
inputs map[string]string
|
| 704 |
|
|
want string
|
| 705 |
|
|
}{
|
| 706 |
|
|
// The trivial set.
|
| 707 |
|
|
{
|
| 708 |
|
|
map[string]string{
|
| 709 |
|
|
"main": ``,
|
| 710 |
|
|
},
|
| 711 |
|
|
``,
|
| 712 |
|
|
},
|
| 713 |
|
|
// A template called in the start context.
|
| 714 |
|
|
{
|
| 715 |
|
|
map[string]string{
|
| 716 |
|
|
"main": `Hello, {{template "helper"}}!`,
|
| 717 |
|
|
// Not a valid top level HTML template.
|
| 718 |
|
|
// "
|
| 719 |
|
|
"helper": `{{""}}`,
|
| 720 |
|
|
},
|
| 721 |
|
|
`Hello, <World>!`,
|
| 722 |
|
|
},
|
| 723 |
|
|
// A template called in a context other than the start.
|
| 724 |
|
|
{
|
| 725 |
|
|
map[string]string{
|
| 726 |
|
|
"main": ``,
|
| 727 |
|
|
// Not a valid top level HTML template.
|
| 728 |
|
|
// "
|
| 729 |
|
|
"helper": `{{""}}
|
| 730 |
|
|
},
|
| 731 |
|
|
``,
|
| 732 |
|
|
},
|
| 733 |
|
|
// A recursive template that ends in its start context.
|
| 734 |
|
|
{
|
| 735 |
|
|
map[string]string{
|
| 736 |
|
|
"main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`,
|
| 737 |
|
|
},
|
| 738 |
|
|
`foo <bar> baz `,
|
| 739 |
|
|
},
|
| 740 |
|
|
// A recursive helper template that ends in its start context.
|
| 741 |
|
|
{
|
| 742 |
|
|
map[string]string{
|
| 743 |
|
|
"main": `{{template "helper" .}}`,
|
| 744 |
|
|
"helper": `{{if .Children}}{{range .Children}}- {{template "main" .}}
{{end}} {{else}}{{.X}}{{end}}`,
|
| 745 |
|
|
},
|
| 746 |
|
|
``,
|
| 747 |
|
|
},
|
| 748 |
|
|
// Co-recursive templates that end in its start context.
|
| 749 |
|
|
{
|
| 750 |
|
|
map[string]string{
|
| 751 |
|
|
"main": `{{range .Children}}{{template "helper" .}}{{end}} `,
|
| 752 |
|
|
"helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`,
|
| 753 |
|
|
},
|
| 754 |
|
|
`foo <bar>
baz
`,
|
| 755 |
|
|
},
|
| 756 |
|
|
// A template that is called in two different contexts.
|
| 757 |
|
|
{
|
| 758 |
|
|
map[string]string{
|
| 759 |
|
|
"main": ``,
|
| 760 |
|
|
"helper": `{{11}} of {{"<100>"}}`,
|
| 761 |
|
|
},
|
| 762 |
|
|
``,
|
| 763 |
|
|
},
|
| 764 |
|
|
// A non-recursive template that ends in a different context.
|
| 765 |
|
|
// helper starts in jsCtxRegexp and ends in jsCtxDivOp.
|
| 766 |
|
|
{
|
| 767 |
|
|
map[string]string{
|
| 768 |
|
|
"main": ``,
|
| 769 |
|
|
"helper": "{{126}}",
|
| 770 |
|
|
},
|
| 771 |
|
|
``,
|
| 772 |
|
|
},
|
| 773 |
|
|
// A recursive template that ends in a similar context.
|
| 774 |
|
|
{
|
| 775 |
|
|
map[string]string{
|
| 776 |
|
|
"main": ``,
|
| 777 |
|
|
"countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`,
|
| 778 |
|
|
},
|
| 779 |
|
|
``,
|
| 780 |
|
|
},
|
| 781 |
|
|
// A recursive template that ends in a different context.
|
| 782 |
|
|
/*
|
| 783 |
|
|
{
|
| 784 |
|
|
map[string]string{
|
| 785 |
|
|
"main": ``,
|
| 786 |
|
|
"helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`,
|
| 787 |
|
|
},
|
| 788 |
|
|
``,
|
| 789 |
|
|
},
|
| 790 |
|
|
*/
|
| 791 |
|
|
}
|
| 792 |
|
|
|
| 793 |
|
|
// pred is a template function that returns the predecessor of a
|
| 794 |
|
|
// natural number for testing recursive templates.
|
| 795 |
|
|
fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
|
| 796 |
|
|
if len(a) == 1 {
|
| 797 |
|
|
if i, _ := a[0].(int); i > 0 {
|
| 798 |
|
|
return i - 1, nil
|
| 799 |
|
|
}
|
| 800 |
|
|
}
|
| 801 |
|
|
return nil, fmt.Errorf("undefined pred(%v)", a)
|
| 802 |
|
|
}}
|
| 803 |
|
|
|
| 804 |
|
|
for _, test := range tests {
|
| 805 |
|
|
source := ""
|
| 806 |
|
|
for name, body := range test.inputs {
|
| 807 |
|
|
source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body)
|
| 808 |
|
|
}
|
| 809 |
|
|
tmpl, err := New("root").Funcs(fns).Parse(source)
|
| 810 |
|
|
if err != nil {
|
| 811 |
|
|
t.Errorf("error parsing %q: %v", source, err)
|
| 812 |
|
|
continue
|
| 813 |
|
|
}
|
| 814 |
|
|
var b bytes.Buffer
|
| 815 |
|
|
|
| 816 |
|
|
if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil {
|
| 817 |
|
|
t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main"))
|
| 818 |
|
|
continue
|
| 819 |
|
|
}
|
| 820 |
|
|
if got := b.String(); test.want != got {
|
| 821 |
|
|
t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
|
| 822 |
|
|
}
|
| 823 |
|
|
}
|
| 824 |
|
|
|
| 825 |
|
|
}
|
| 826 |
|
|
|
| 827 |
|
|
func TestErrors(t *testing.T) {
|
| 828 |
|
|
tests := []struct {
|
| 829 |
|
|
input string
|
| 830 |
|
|
err string
|
| 831 |
|
|
}{
|
| 832 |
|
|
// Non-error cases.
|
| 833 |
|
|
{
|
| 834 |
|
|
"{{if .Cond}}{{else}}{{end}}",
|
| 835 |
|
|
"",
|
| 836 |
|
|
},
|
| 837 |
|
|
{
|
| 838 |
|
|
"{{if .Cond}}{{end}}",
|
| 839 |
|
|
"",
|
| 840 |
|
|
},
|
| 841 |
|
|
{
|
| 842 |
|
|
"{{if .Cond}}{{else}}{{end}}",
|
| 843 |
|
|
"",
|
| 844 |
|
|
},
|
| 845 |
|
|
{
|
| 846 |
|
|
"{{with .Cond}}{{end}}",
|
| 847 |
|
|
"",
|
| 848 |
|
|
},
|
| 849 |
|
|
{
|
| 850 |
|
|
"{{range .Items}}{{end}}",
|
| 851 |
|
|
"",
|
| 852 |
|
|
},
|
| 853 |
|
|
{
|
| 854 |
|
|
"",
|
| 855 |
|
|
"",
|
| 856 |
|
|
},
|
| 857 |
|
|
// Error cases.
|
| 858 |
|
|
{
|
| 859 |
|
|
"{{if .Cond}}
|
| 860 |
|
|
"z:1: {{if}} branches",
|
| 861 |
|
|
},
|
| 862 |
|
|
{
|
| 863 |
|
|
"{{if .Cond}}\n{{else}}\n
|
| 864 |
|
|
"z:1: {{if}} branches",
|
| 865 |
|
|
},
|
| 866 |
|
|
{
|
| 867 |
|
|
// Missing quote in the else branch.
|
| 868 |
|
|
`{{if .Cond}}{{else}}
| 869 |
|
|
"z:1: {{if}} branches",
|
|
| 870 |
|
|
},
|
| 871 |
|
|
{
|
| 872 |
|
|
// Different kind of attribute: href implies a URL.
|
| 873 |
|
|
"",
|
| 874 |
|
|
"z:1: {{if}} branches",
|
| 875 |
|
|
},
|
| 876 |
|
|
{
|
| 877 |
|
|
"\n{{with .X}}
|
| 878 |
|
|
"z:2: {{with}} branches",
|
| 879 |
|
|
},
|
| 880 |
|
|
{
|
| 881 |
|
|
"\n{{with .X}}{{else}}
|
| 882 |
|
|
"z:2: {{with}} branches",
|
| 883 |
|
|
},
|
| 884 |
|
|
{
|
| 885 |
|
|
"{{range .Items}}
|
| 886 |
|
|
`z:1: on range loop re-entry: "<" in attribute name: "
|
| 887 |
|
|
},
|
| 888 |
|
|
{
|
| 889 |
|
|
"\n{{range .Items}} x='
|
| 890 |
|
|
"z:2: on range loop re-entry: {{range}} branches",
|
| 891 |
|
|
},
|
| 892 |
|
|
{
|
| 893 |
|
|
"
|
| 894 |
|
|
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
|
| 895 |
|
|
},
|
| 896 |
|
|
{
|
| 897 |
|
|
"`,
|
| 927 |
|
|
`'/' could start a division or regexp: "/-"`,
|
| 928 |
|
|
},
|
| 929 |
|
|
{
|
| 930 |
|
|
`{{template "foo"}}`,
|
| 931 |
|
|
"z:1: no such template \"foo\"",
|
| 932 |
|
|
},
|
| 933 |
|
|
{
|
| 934 |
|
|
`` +
|
| 935 |
|
|
// Illegal starting in stateTag but not in stateText.
|
| 936 |
|
|
`{{define "y"}} foo
|
| 937 |
|
|
`"<" in attribute name: " foo
|
| 938 |
|
|
},
|
| 939 |
|
|
{
|
| 940 |
|
|
`` +
|
| 941 |
|
|
// Missing " after recursive call.
|
| 942 |
|
|
`{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`,
|
| 943 |
|
|
`: cannot compute output context for template t$htmltemplate_stateJS_elementScript`,
|
| 944 |
|
|
},
|
| 945 |
|
|
{
|
| 946 |
|
|
``,
|
| 947 |
|
|
`html/template:z: "=" in unquoted attr: "onclick="`,
|
| 948 |
|
|
},
|
| 949 |
|
|
{
|
| 950 |
|
|
``,
|
| 951 |
|
|
`html/template:z: "=" in unquoted attr: "onclick="`,
|
| 952 |
|
|
},
|
| 953 |
|
|
{
|
| 954 |
|
|
``,
|
| 955 |
|
|
`html/template:z: "=" in unquoted attr: "1+1=2"`,
|
| 956 |
|
|
},
|
| 957 |
|
|
{
|
| 958 |
|
|
"",
|
| 959 |
|
|
"html/template:z: \"`\" in unquoted attr: \"`foo\"",
|
| 960 |
|
|
},
|
| 961 |
|
|
{
|
| 962 |
|
|
``,
|
| 963 |
|
|
`html/template:z: "'" in unquoted attr: "font:'Arial'"`,
|
| 964 |
|
|
},
|
| 965 |
|
|
{
|
| 966 |
|
|
``,
|
| 967 |
|
|
`: expected space, attr name, or end of tag, but got "=foo>"`,
|
| 968 |
|
|
},
|
| 969 |
|
|
}
|
| 970 |
|
|
|
| 971 |
|
|
for _, test := range tests {
|
| 972 |
|
|
buf := new(bytes.Buffer)
|
| 973 |
|
|
tmpl, err := New("z").Parse(test.input)
|
| 974 |
|
|
if err != nil {
|
| 975 |
|
|
t.Errorf("input=%q: unexpected parse error %s\n", test.input, err)
|
| 976 |
|
|
continue
|
| 977 |
|
|
}
|
| 978 |
|
|
err = tmpl.Execute(buf, nil)
|
| 979 |
|
|
var got string
|
| 980 |
|
|
if err != nil {
|
| 981 |
|
|
got = err.Error()
|
| 982 |
|
|
}
|
| 983 |
|
|
if test.err == "" {
|
| 984 |
|
|
if got != "" {
|
| 985 |
|
|
t.Errorf("input=%q: unexpected error %q", test.input, got)
|
| 986 |
|
|
}
|
| 987 |
|
|
continue
|
| 988 |
|
|
}
|
| 989 |
|
|
if strings.Index(got, test.err) == -1 {
|
| 990 |
|
|
t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err)
|
| 991 |
|
|
continue
|
| 992 |
|
|
}
|
| 993 |
|
|
}
|
| 994 |
|
|
}
|
| 995 |
|
|
|
| 996 |
|
|
func TestEscapeText(t *testing.T) {
|
| 997 |
|
|
tests := []struct {
|
| 998 |
|
|
input string
|
| 999 |
|
|
output context
|
| 1000 |
|
|
}{
|
| 1001 |
|
|
{
|
| 1002 |
|
|
``,
|
| 1003 |
|
|
context{},
|
| 1004 |
|
|
},
|
| 1005 |
|
|
{
|
| 1006 |
|
|
`Hello, World!`,
|
| 1007 |
|
|
context{},
|
| 1008 |
|
|
},
|
| 1009 |
|
|
{
|
| 1010 |
|
|
// An orphaned "<" is OK.
|
| 1011 |
|
|
`I <3 Ponies!`,
|
| 1012 |
|
|
context{},
|
| 1013 |
|
|
},
|
| 1014 |
|
|
{
|
| 1015 |
|
|
`
|
| 1016 |
|
|
context{state: stateTag},
|
| 1017 |
|
|
},
|
| 1018 |
|
|
{
|
| 1019 |
|
|
`
|
| 1020 |
|
|
context{state: stateTag},
|
| 1021 |
|
|
},
|
| 1022 |
|
|
{
|
| 1023 |
|
|
``,
|
| 1024 |
|
|
context{state: stateText},
|
| 1025 |
|
|
},
|
| 1026 |
|
|
{
|
| 1027 |
|
|
`
|
| 1028 |
|
|
context{state: stateAttrName, attr: attrURL},
|
| 1029 |
|
|
},
|
| 1030 |
|
|
{
|
| 1031 |
|
|
`
|
| 1032 |
|
|
context{state: stateAttrName, attr: attrScript},
|
| 1033 |
|
|
},
|
| 1034 |
|
|
{
|
| 1035 |
|
|
`
|
| 1036 |
|
|
context{state: stateAfterName, attr: attrURL},
|
| 1037 |
|
|
},
|
| 1038 |
|
|
{
|
| 1039 |
|
|
`
|
| 1040 |
|
|
context{state: stateBeforeValue, attr: attrStyle},
|
| 1041 |
|
|
},
|
| 1042 |
|
|
{
|
| 1043 |
|
|
`
|
| 1044 |
|
|
context{state: stateBeforeValue, attr: attrURL},
|
| 1045 |
|
|
},
|
| 1046 |
|
|
{
|
| 1047 |
|
|
`
|
| 1048 |
|
|
context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery},
|
| 1049 |
|
|
},
|
| 1050 |
|
|
{
|
| 1051 |
|
|
`
|
| 1052 |
|
|
context{state: stateTag},
|
| 1053 |
|
|
},
|
| 1054 |
|
|
{
|
| 1055 |
|
|
``,
|
| 1056 |
|
|
context{state: stateText},
|
| 1057 |
|
|
},
|
| 1058 |
|
|
{
|
| 1059 |
|
|
``,
|
| 1060 |
|
|
context{state: stateText},
|
| 1061 |
|
|
},
|
| 1062 |
|
|
{
|
| 1063 |
|
|
`
|
| 1068 |
|
|
context{state: stateTag},
|
| 1069 |
|
|
},
|
| 1070 |
|
|
{
|
| 1071 |
|
|
`
| 1072 |
|
|
context{state: stateURL, delim: delimDoubleQuote},
|
|
| 1073 |
|
|
},
|
| 1074 |
|
|
{
|
| 1075 |
|
|
`
|
| 1076 |
|
|
context{state: stateTag},
|
| 1077 |
|
|
},
|
| 1078 |
|
|
{
|
| 1079 |
|
|
`
| 1080 |
|
|
context{state: stateAttr, delim: delimDoubleQuote},
|
|
| 1081 |
|
|
},
|
| 1082 |
|
|
{
|
| 1083 |
|
|
`
|
| 1088 |
|
|
context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery},
|
| 1089 |
|
|
},
|
| 1090 |
|
|
{
|
| 1091 |
|
|
`
|
| 1096 |
|
|
context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
| 1097 |
|
|
},
|
| 1098 |
|
|
{
|
| 1099 |
|
|
`
|
| 1140 |
|
|
context{state: stateJS, delim: delimSingleQuote},
|
| 1141 |
|
|
},
|
| 1142 |
|
|
{
|
| 1143 |
|
|
"
|
| 1148 |
|
|
context{state: stateJS, delim: delimSingleQuote},
|
| 1149 |
|
|
},
|
| 1150 |
|
|
{
|
| 1151 |
|
|
`
| 1152 |
|
|
context{state: stateJSBlockCmt, delim: delimDoubleQuote},
|
|
| 1153 |
|
|
},
|
| 1154 |
|
|
{
|
| 1155 |
|
|
`
| 1156 |
|
|
context{state: stateJSBlockCmt, delim: delimDoubleQuote},
|
|
| 1157 |
|
|
},
|
| 1158 |
|
|
{
|
| 1159 |
|
|
`
| 1160 |
|
|
context{state: stateJS, delim: delimDoubleQuote},
|
|
| 1161 |
|
|
},
|
| 1162 |
|
|
{
|
| 1163 |
|
|
`
| 1164 |
|
|
context{state: stateJSDqStr, delim: delimDoubleQuote},
|
|
| 1165 |
|
|
},
|
| 1166 |
|
|
{
|
| 1167 |
|
|
`
|
| 1180 |
|
|
context{state: stateJSDqStr, delim: delimDoubleQuote},
|
| 1181 |
|
|
},
|
| 1182 |
|
|
{
|
| 1183 |
|
|
`
| 1184 |
|
|
context{state: stateJSSqStr, delim: delimDoubleQuote},
|
|
| 1185 |
|
|
},
|
| 1186 |
|
|
{
|
| 1187 |
|
|
`
| 1188 |
|
|
context{state: stateJSSqStr, delim: delimDoubleQuote},
|
|
| 1189 |
|
|
},
|
| 1190 |
|
|
{
|
| 1191 |
|
|
`
| 1192 |
|
|
context{state: stateJSRegexp, delim: delimDoubleQuote},
|
|
| 1193 |
|
|
},
|
| 1194 |
|
|
{
|
| 1195 |
|
|
`
| 1196 |
|
|
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
|
|
| 1197 |
|
|
},
|
| 1198 |
|
|
{
|
| 1199 |
|
|
`
| 1200 |
|
|
context{state: stateJSSqStr, delim: delimDoubleQuote},
|
|
| 1201 |
|
|
},
|
| 1202 |
|
|
{
|
| 1203 |
|
|
`
| 1204 |
|
|
context{state: stateJSSqStr, delim: delimDoubleQuote},
|
|
| 1205 |
|
|
},
|
| 1206 |
|
|
{
|
| 1207 |
|
|
`
| 1208 |
|
|
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
|
|
| 1209 |
|
|
},
|
| 1210 |
|
|
{
|
| 1211 |
|
|
``,
|
| 1244 |
|
|
context{state: stateCSSLineCmt, delim: delimDoubleQuote},
|
| 1245 |
|
|
},
|
| 1246 |
|
|
{
|
| 1247 |
|
|
"
|
| 1252 |
|
|
context{state: stateCSS, delim: delimSingleQuote},
|
| 1253 |
|
|
},
|
| 1254 |
|
|
{
|
| 1255 |
|
|
`
| 1256 |
|
|
context{state: stateCSSBlockCmt, delim: delimDoubleQuote},
|
|
| 1257 |
|
|
},
|
| 1258 |
|
|
{
|
| 1259 |
|
|
`
| 1260 |
|
|
context{state: stateCSSBlockCmt, delim: delimDoubleQuote},
|
|
| 1261 |
|
|
},
|
| 1262 |
|
|
{
|
| 1263 |
|
|
`
| 1264 |
|
|
context{state: stateCSS, delim: delimDoubleQuote},
|
|
| 1265 |
|
|
},
|
| 1266 |
|
|
{
|
| 1267 |
|
|
`
| 1268 |
|
|
context{state: stateCSSSqStr, delim: delimDoubleQuote},
|
|
| 1269 |
|
|
},
|
| 1270 |
|
|
{
|
| 1271 |
|
|
`
| 1272 |
|
|
context{state: stateCSSDqStr, delim: delimDoubleQuote},
|
|
| 1273 |
|
|
},
|
| 1274 |
|
|
{
|
| 1275 |
|
|
`
| 1276 |
|
|
context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag},
|
|
| 1277 |
|
|
},
|
| 1278 |
|
|
{
|
| 1279 |
|
|
`
| 1280 |
|
|
context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1281 |
|
|
},
|
| 1282 |
|
|
{
|
| 1283 |
|
|
`
| 1284 |
|
|
context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1285 |
|
|
},
|
| 1286 |
|
|
{
|
| 1287 |
|
|
`
| 1288 |
|
|
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1289 |
|
|
},
|
| 1290 |
|
|
{
|
| 1291 |
|
|
`
| 1292 |
|
|
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1293 |
|
|
},
|
| 1294 |
|
|
{
|
| 1295 |
|
|
`
| 1296 |
|
|
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1297 |
|
|
},
|
| 1298 |
|
|
{
|
| 1299 |
|
|
`
| 1300 |
|
|
context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
|
|
| 1301 |
|
|
},
|
| 1302 |
|
|
{
|
| 1303 |
|
|
`
| 1304 |
|
|
context{state: stateCSSURL, delim: delimDoubleQuote},
|
|
| 1305 |
|
|
},
|
| 1306 |
|
|
{
|
| 1307 |
|
|
`
| 1308 |
|
|
context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag},
|
|
| 1309 |
|
|
},
|
| 1310 |
|
|
{
|
| 1311 |
|
|
`
| 1312 |
|
|
context{state: stateCSS, delim: delimDoubleQuote},
|
|
| 1313 |
|
|
},
|
| 1314 |
|
|
{
|
| 1315 |
|
|
`
| 1316 |
|
|
context{state: stateCSS, delim: delimDoubleQuote},
|
|
| 1317 |
|
|
},
|
| 1318 |
|
|
{
|
| 1319 |
|
|
`
| 1320 |
|
|
context{state: stateCSS, delim: delimDoubleQuote},
|
|
| 1321 |
|
|
},
|
| 1322 |
|
|
{
|
| 1323 |
|
|
``,
|
| 1328 |
|
|
context{state: stateHTMLCmt},
|
| 1329 |
|
|
},
|
| 1330 |
|
|
{
|
| 1331 |
|
|
``,
|
| 1332 |
|
|
context{state: stateHTMLCmt},
|
| 1333 |
|
|
},
|
| 1334 |
|
|
{
|
| 1335 |
|
|
``,
|
| 1336 |
|
|
context{state: stateText},
|
| 1337 |
|
|
},
|
| 1338 |
|
|
{
|
| 1339 |
|
|
`
|
| 1340 |
|
|
context{state: stateTag, element: elementScript},
|
| 1341 |
|
|
},
|
| 1342 |
|
|
{
|
| 1343 |
|
|
`
|
| 1344 |
|
|
context{state: stateTag, element: elementScript},
|
| 1345 |
|
|
},
|
| 1346 |
|
|
{
|
| 1347 |
|
|
`
|
| 1348 |
|
|
context{state: stateTag, element: elementScript},
|
| 1349 |
|
|
},
|
| 1350 |
|
|
{
|
| 1351 |
|
|
`
|
| 1352 |
|
|
context{state: stateTag, element: elementScript},
|
| 1353 |
|
|
},
|
| 1354 |
|
|
{
|
| 1355 |
|
|
`
|
| 1356 |
|
|
context{state: stateTag, element: elementScript},
|
| 1357 |
|
|
},
|
| 1358 |
|
|
{
|
| 1359 |
|
|
``,
|
| 1364 |
|
|
context{state: stateText},
|
| 1365 |
|
|
},
|
| 1366 |
|
|
{
|
| 1367 |
|
|
`
|
|
|
|
|