├── v0 ├── testdata │ ├── file1.tmpl │ ├── file2.tmpl │ ├── tmpl1.tmpl │ └── tmpl2.tmpl ├── doc.go ├── examplefunc_test.go ├── example_test.go ├── block_test.go ├── escape │ ├── html_test.go │ ├── url_test.go │ ├── pipeline_test.go │ ├── url.go │ ├── pipeline.go │ ├── funcs.go │ ├── content.go │ ├── attr.go │ ├── error.go │ ├── css.go │ ├── css_test.go │ ├── html.go │ ├── context.go │ ├── js.go │ ├── escape_test.go │ └── js_test.go ├── inline.go ├── examplefiles_test.go ├── funcs.go ├── multi_test.go ├── content_test.go ├── template.go └── parse │ ├── lex_test.go │ ├── parse_test.go │ └── lex.go ├── .gitignore └── README.md /v0/testdata/file1.tmpl: -------------------------------------------------------------------------------- 1 | {{define "x"}}TEXT{{end}} 2 | {{define "dotV"}}{{.V}}{{end}} 3 | -------------------------------------------------------------------------------- /v0/testdata/file2.tmpl: -------------------------------------------------------------------------------- 1 | {{define "dot"}}{{.}}{{end}} 2 | {{define "nested"}}{{template "dot" .}}{{end}} 3 | -------------------------------------------------------------------------------- /v0/testdata/tmpl1.tmpl: -------------------------------------------------------------------------------- 1 | {{define "template1"}} 2 | template1 3 | {{template "y"}} 4 | {{end}} 5 | {{define "x"}}x{{end}} 6 | -------------------------------------------------------------------------------- /v0/testdata/tmpl2.tmpl: -------------------------------------------------------------------------------- 1 | {{define "template2"}} 2 | template2 3 | {{template "x"}} 4 | {{end}} 5 | {{define "y"}}y{{end}} 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /v0/doc.go: -------------------------------------------------------------------------------- 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 | /* 6 | Package gorilla/template is a template engine to generate textual or HTML 7 | output. 8 | 9 | It is based on the standard text/template and html/template packages. 10 | 11 | Here's the simplest example to render a template: 12 | 13 | set, err := new(template.Set).Parse(`{{define "hello"}}Hello, World.{{end}}`) 14 | if err != nil { 15 | // do something with the parsing error... 16 | } 17 | err = set.Execute(os.Stderr, "hello", nil) 18 | if err != nil { 19 | // do something with the execution error... 20 | } 21 | 22 | First we create a Set, which stores a collection of templates. Then we call 23 | Parse() to parse a string and add the templates defined there to the set. 24 | Finally we call Execute() to render the template named "hello" using the given 25 | data (in this case, nil), and write the output to an io.Writer (in this case, 26 | os.Stderr). 27 | 28 | Parse() can be called multiple times to fill the set with as many template 29 | definitions as needed. There are also ParseFiles() and ParseGlob() methods 30 | to read and parse the contents from files. 31 | 32 | Template names must be unique within a set. 33 | */ 34 | package template 35 | -------------------------------------------------------------------------------- /v0/examplefunc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 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_test 6 | 7 | import ( 8 | "log" 9 | "os" 10 | "strings" 11 | "text/template" 12 | ) 13 | 14 | // This example demonstrates a custom function to process template text. 15 | // It installs the strings.Title function and uses it to 16 | // Make Title Text Look Good In Our Template's Output. 17 | func ExampleTemplate_func() { 18 | // First we create a FuncMap with which to register the function. 19 | funcMap := template.FuncMap{ 20 | // The name "title" is what the function will be called in the template text. 21 | "title": strings.Title, 22 | } 23 | 24 | // A simple template definition to test our function. 25 | // We print the input text several ways: 26 | // - the original 27 | // - title-cased 28 | // - title-cased and then printed with %q 29 | // - printed with %q and then title-cased. 30 | const templateText = ` 31 | Input: {{printf "%q" .}} 32 | Output 0: {{title .}} 33 | Output 1: {{title . | printf "%q"}} 34 | Output 2: {{printf "%q" . | title}} 35 | ` 36 | 37 | // Create a template, add the function map, and parse the text. 38 | tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText) 39 | if err != nil { 40 | log.Fatalf("parsing: %s", err) 41 | } 42 | 43 | // Run the template to verify the output. 44 | err = tmpl.Execute(os.Stdout, "the go programming language") 45 | if err != nil { 46 | log.Fatalf("execution: %s", err) 47 | } 48 | 49 | // Output: 50 | // Input: "the go programming language" 51 | // Output 0: The Go Programming Language 52 | // Output 1: "The Go Programming Language" 53 | // Output 2: "The Go Programming Language" 54 | } 55 | -------------------------------------------------------------------------------- /v0/example_test.go: -------------------------------------------------------------------------------- 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_test 6 | 7 | import ( 8 | "log" 9 | "os" 10 | 11 | "github.com/gorilla/template/v0" 12 | ) 13 | 14 | func ExampleTemplate() { 15 | // Define a template. 16 | const letter = `{{define "letter"}} 17 | Dear {{.Name}}, 18 | {{if .Attended}} 19 | It was a pleasure to see you at the wedding.{{else}} 20 | It is a shame you couldn't make it to the wedding.{{end}} 21 | {{with .Gift}}Thank you for the lovely {{.}}. 22 | {{end}} 23 | Best wishes, 24 | Josie 25 | {{end}}` 26 | 27 | // Prepare some data to insert into the template. 28 | type Recipient struct { 29 | Name, Gift string 30 | Attended bool 31 | } 32 | var recipients = []Recipient{ 33 | {"Aunt Mildred", "bone china tea set", true}, 34 | {"Uncle John", "moleskin pants", false}, 35 | {"Cousin Rodney", "", false}, 36 | } 37 | 38 | // Create a new template and parse the letter into it. 39 | t := template.Must(new(template.Set).Parse(letter)) 40 | 41 | // Execute the template for each recipient. 42 | for _, r := range recipients { 43 | err := t.Execute(os.Stdout, "letter", r) 44 | if err != nil { 45 | log.Println("executing template:", err) 46 | } 47 | } 48 | 49 | // Output: 50 | // Dear Aunt Mildred, 51 | // 52 | // It was a pleasure to see you at the wedding. 53 | // Thank you for the lovely bone china tea set. 54 | // 55 | // Best wishes, 56 | // Josie 57 | // 58 | // Dear Uncle John, 59 | // 60 | // It is a shame you couldn't make it to the wedding. 61 | // Thank you for the lovely moleskin pants. 62 | // 63 | // Best wishes, 64 | // Josie 65 | // 66 | // Dear Cousin Rodney, 67 | // 68 | // It is a shame you couldn't make it to the wedding. 69 | // 70 | // Best wishes, 71 | // Josie 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gorilla/template 2 | ================ 3 | 4 | *Warning:* This is a work in progress, and the v0 API is subject of 5 | changes. 6 | 7 | Go templates are a great foundation but suffer from some design 8 | flaws, and this is a pity. They could be really awesome. 9 | gorilla/template is yet another attempt to fix them. 10 | 11 | Goals 12 | ----- 13 | - Better composition options. 14 | - Improved HTML support. 15 | - Client-side support. 16 | - Auto-documentation. 17 | - Simpler spec and API. 18 | 19 | These goals will be achieved through several phases. 20 | 21 | ### Phase 1 - Dec.2012 22 | The initial work makes templates self-contained, removing root 23 | templates. This greatly simplifies internals, spec and API without 24 | losing any functionality. Highlights: 25 | 26 | - Removed New("name") API: templates are self-contained and must 27 | be named using {{define}}. 28 | - Templates are always executed passing the template name, so there's 29 | no need for two Execute methods. 30 | - The Set type replaces the Template type: a Set is a group 31 | of parsed templates. 32 | - Parse tree: added DefineNode and Tree types. 33 | - Node.String() methods result in templates that reproduce the 34 | original template exactly. 35 | 36 | ### Phase 2 - Dec.2012 37 | In this step the contextual escaping mechanism from html/template 38 | becomes part of the gorilla/template package, effectively making it 39 | a combination of text/template and html/template. Highlights: 40 | 41 | - Same functionality of text/template and html/template but: 42 | - 1119 less lines of code. 43 | - 33 less types, functions and methods. 44 | - HTML contextual escaping is set explicitly calling Escape(). 45 | - Types to encapsulate safe strings are placed in the template/escape 46 | package: CSS, JS, JSStr, HTML, HTMLAttr. 47 | 48 | ### Phase 3 - Jan.2013 49 | We finally add template inheritance and introduce two new actions: 50 | {{slot}} and {{fill}}. Highlights: 51 | 52 | - A {{slot}} defines placeholders in a base template, and a {{fill}} 53 | fills a placeholder in a parent template. 54 | - An inherited template is defined passing the parent template name 55 | in the {{define}} action, as in {{define "child" "parent"}}. 56 | - New templates can't be added to a Set after execution: the set 57 | is "compiled" and locked, as in html/template. 58 | - Inheritance and the new actions are inlined, so contextual escaping 59 | and execution didn't require any changes. 60 | -------------------------------------------------------------------------------- /v0/block_test.go: -------------------------------------------------------------------------------- 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 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestSlot(t *testing.T) { 14 | // Some deep inheritance. 15 | tpl1 := ` 16 | {{define "tpl1"}} 17 | A 18 | {{slot "header"}} 19 | -h1- 20 | {{end}} 21 | B 22 | {{slot "footer"}} 23 | -f1- 24 | {{end}} 25 | C 26 | {{end}} 27 | 28 | {{define "tpl2" "tpl1"}} 29 | xxx 30 | {{end}} 31 | 32 | {{define "tpl3" "tpl2"}} 33 | xxx 34 | {{fill "header"}} 35 | -h3- 36 | {{end}} 37 | xxx 38 | {{end}} 39 | 40 | {{define "tpl4" "tpl3"}} 41 | xxx 42 | {{fill "header"}} 43 | -h4- 44 | {{end}} 45 | xxx 46 | {{fill "footer"}} 47 | -f4- 48 | {{end}} 49 | xxx 50 | {{end}} 51 | 52 | {{define "tpl5" "tpl4"}} 53 | xxx 54 | {{fill "footer"}} 55 | -f5- 56 | {{end}} 57 | xxx 58 | {{end}}` 59 | // Recursive inheritance. 60 | tpl2 := ` 61 | {{define "tpl1" "tpl2"}} 62 | {{fill "header"}} 63 | -h1- 64 | {{end}} 65 | {{end}} 66 | 67 | {{define "tpl2" "tpl3"}} 68 | {{fill "header"}} 69 | -h2- 70 | {{end}} 71 | {{end}} 72 | 73 | {{define "tpl3" "tpl1"}} 74 | {{fill "header"}} 75 | -h3- 76 | {{end}} 77 | {{end}}` 78 | 79 | tests := []execTest{ 80 | // the base template itself 81 | {"tpl1", tpl1, "A-h1-B-f1-C", nil, true}, 82 | // default slot value 83 | {"tpl2", tpl1, "A-h1-B-f1-C", nil, true}, 84 | // override only one slot 85 | {"tpl3", tpl1, "A-h3-B-f1-C", nil, true}, 86 | // override both slots 87 | {"tpl4", tpl1, "A-h4-B-f4-C", nil, true}, 88 | // override only one slot, higher level override both 89 | {"tpl5", tpl1, "A-h4-B-f5-C", nil, true}, 90 | // impossible recursion 91 | {"tpl1", tpl2, "impossible recursion", nil, false}, 92 | } 93 | for _, test := range tests { 94 | set, err := new(Set).Parse(test.input) 95 | if err != nil { 96 | t.Errorf("%s: unexpected parse error: %s", test.name, err) 97 | continue 98 | } 99 | b := new(bytes.Buffer) 100 | err = set.Execute(b, test.name, test.data) 101 | if test.ok { 102 | if err != nil { 103 | t.Errorf("%s: unexpected exec error: %s", test.name, err) 104 | continue 105 | } 106 | output := b.String() 107 | output = strings.Replace(output, " ", "", -1) 108 | output = strings.Replace(output, "\n", "", -1) 109 | output = strings.Replace(output, "\t", "", -1) 110 | if test.output != output { 111 | t.Errorf("%s: expected %q, got %q", test.name, test.output, output) 112 | } 113 | } else { 114 | if err == nil { 115 | t.Errorf("%s: expected exec error", test.name) 116 | continue 117 | } 118 | if !strings.Contains(err.Error(), test.output) { 119 | t.Errorf("%s: expected exec error %q, got %q", test.name, test.output, err.Error()) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /v0/escape/html_test.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "html" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestHTMLNospaceEscaper(t *testing.T) { 14 | input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 15 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 16 | ` !"#$%&'()*+,-./` + 17 | `0123456789:;<=>?` + 18 | `@ABCDEFGHIJKLMNO` + 19 | `PQRSTUVWXYZ[\]^_` + 20 | "`abcdefghijklmno" + 21 | "pqrstuvwxyz{|}~\x7f" + 22 | "\u00A0\u0100\u2028\u2029\ufeff\ufdec\U0001D11E") 23 | 24 | want := ("�\x01\x02\x03\x04\x05\x06\x07" + 25 | "\x08 \x0E\x0F" + 26 | "\x10\x11\x12\x13\x14\x15\x16\x17" + 27 | "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 28 | ` !"#$%&'()*+,-./` + 29 | `0123456789:;<=>?` + 30 | `@ABCDEFGHIJKLMNO` + 31 | `PQRSTUVWXYZ[\]^_` + 32 | ``abcdefghijklmno` + 33 | `pqrstuvwxyz{|}~` + "\u007f" + 34 | "\u00A0\u0100\u2028\u2029\ufeff﷬\U0001D11E") 35 | 36 | got := htmlNospaceEscaper(input) 37 | if got != want { 38 | t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got) 39 | } 40 | 41 | got, want = html.UnescapeString(got), strings.Replace(input, "\x00", "\ufffd", 1) 42 | if want != got { 43 | t.Errorf("decode: want\n\t%q\nbut got\n\t%q", want, got) 44 | } 45 | } 46 | 47 | func TestStripTags(t *testing.T) { 48 | tests := []struct { 49 | input, want string 50 | }{ 51 | {"", ""}, 52 | {"Hello, World!", "Hello, World!"}, 53 | {"foo&bar", "foo&bar"}, 54 | {`Hello World!`, "Hello World!"}, 55 | {"Foo Baz", "Foo Bar Baz"}, 56 | {"Foo Baz", "Foo Baz"}, 57 | {"<", "<"}, 58 | {"foo < bar", "foo < bar"}, 59 | {`FooBar`, "FooBar"}, 60 | {`Foo
Bar`, "FooBar"}, 61 | {`I <3 Ponies!`, `I <3 Ponies!`}, 62 | {``, ``}, 63 | } 64 | 65 | for _, test := range tests { 66 | if got := stripTags(test.input); got != test.want { 67 | t.Errorf("%q: want %q, got %q", test.input, test.want, got) 68 | } 69 | } 70 | } 71 | 72 | func BenchmarkHTMLNospaceEscaper(b *testing.B) { 73 | for i := 0; i < b.N; i++ { 74 | htmlNospaceEscaper("The quick,\r\nbrown fox jumps\u2028over the dog") 75 | } 76 | } 77 | 78 | func BenchmarkHTMLNospaceEscaperNoSpecials(b *testing.B) { 79 | for i := 0; i < b.N; i++ { 80 | htmlNospaceEscaper("The_quick,_brown_fox_jumps_over_the_lazy_dog.") 81 | } 82 | } 83 | 84 | func BenchmarkStripTags(b *testing.B) { 85 | for i := 0; i < b.N; i++ { 86 | stripTags("The quick,\r\nbrown fox jumps\u2028over the dog") 87 | } 88 | } 89 | 90 | func BenchmarkStripTagsNoSpecials(b *testing.B) { 91 | for i := 0; i < b.N; i++ { 92 | stripTags("The quick, brown fox jumps over the lazy dog.") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /v0/escape/url_test.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestURLNormalizer(t *testing.T) { 12 | tests := []struct { 13 | url, want string 14 | }{ 15 | {"", ""}, 16 | { 17 | "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag", 18 | "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag", 19 | }, 20 | {" ", "%20"}, 21 | {"%7c", "%7c"}, 22 | {"%7C", "%7C"}, 23 | {"%2", "%252"}, 24 | {"%", "%25"}, 25 | {"%z", "%25z"}, 26 | {"/foo|bar/%5c\u1234", "/foo%7cbar/%5c%e1%88%b4"}, 27 | } 28 | for _, test := range tests { 29 | if got := urlNormalizer(test.url); test.want != got { 30 | t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.url, test.want, got) 31 | } 32 | if test.want != urlNormalizer(test.want) { 33 | t.Errorf("not idempotent: %q", test.want) 34 | } 35 | } 36 | } 37 | 38 | func TestURLFilters(t *testing.T) { 39 | input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 40 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 41 | ` !"#$%&'()*+,-./` + 42 | `0123456789:;<=>?` + 43 | `@ABCDEFGHIJKLMNO` + 44 | `PQRSTUVWXYZ[\]^_` + 45 | "`abcdefghijklmno" + 46 | "pqrstuvwxyz{|}~\x7f" + 47 | "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") 48 | 49 | tests := []struct { 50 | name string 51 | escaper func(...interface{}) string 52 | escaped string 53 | }{ 54 | { 55 | "urlEscaper", 56 | urlEscaper, 57 | "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" + 58 | "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" + 59 | "%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f" + 60 | "0123456789%3a%3b%3c%3d%3e%3f" + 61 | "%40ABCDEFGHIJKLMNO" + 62 | "PQRSTUVWXYZ%5b%5c%5d%5e_" + 63 | "%60abcdefghijklmno" + 64 | "pqrstuvwxyz%7b%7c%7d~%7f" + 65 | "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e", 66 | }, 67 | { 68 | "urlNormalizer", 69 | urlNormalizer, 70 | "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" + 71 | "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" + 72 | "%20!%22#$%25&%27%28%29*+,-./" + 73 | "0123456789:;%3c=%3e?" + 74 | "@ABCDEFGHIJKLMNO" + 75 | "PQRSTUVWXYZ[%5c]%5e_" + 76 | "%60abcdefghijklmno" + 77 | "pqrstuvwxyz%7b%7c%7d~%7f" + 78 | "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e", 79 | }, 80 | } 81 | 82 | for _, test := range tests { 83 | if s := test.escaper(input); s != test.escaped { 84 | t.Errorf("%s: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) 85 | continue 86 | } 87 | } 88 | } 89 | 90 | func BenchmarkURLEscaper(b *testing.B) { 91 | for i := 0; i < b.N; i++ { 92 | urlEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag") 93 | } 94 | } 95 | 96 | func BenchmarkURLEscaperNoSpecials(b *testing.B) { 97 | for i := 0; i < b.N; i++ { 98 | urlEscaper("TheQuickBrownFoxJumpsOverTheLazyDog.") 99 | } 100 | } 101 | 102 | func BenchmarkURLNormalizer(b *testing.B) { 103 | for i := 0; i < b.N; i++ { 104 | urlNormalizer("The quick brown fox jumps over the lazy dog.\n") 105 | } 106 | } 107 | 108 | func BenchmarkURLNormalizerNoSpecials(b *testing.B) { 109 | for i := 0; i < b.N; i++ { 110 | urlNormalizer("http://example.com:80/foo?q=bar%20&baz=x+y#frag") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /v0/escape/pipeline_test.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/gorilla/template/v0/parse" 12 | ) 13 | 14 | func TestensurePipelineContains(t *testing.T) { 15 | tests := []struct { 16 | input, output string 17 | ids []string 18 | }{ 19 | { 20 | "{{.X}}", 21 | ".X", 22 | []string{}, 23 | }, 24 | { 25 | "{{.X | html}}", 26 | ".X | html", 27 | []string{}, 28 | }, 29 | { 30 | "{{.X}}", 31 | ".X | html", 32 | []string{"html"}, 33 | }, 34 | { 35 | "{{.X | html}}", 36 | ".X | html | urlquery", 37 | []string{"urlquery"}, 38 | }, 39 | { 40 | "{{.X | html | urlquery}}", 41 | ".X | html | urlquery", 42 | []string{"urlquery"}, 43 | }, 44 | { 45 | "{{.X | html | urlquery}}", 46 | ".X | html | urlquery", 47 | []string{"html", "urlquery"}, 48 | }, 49 | { 50 | "{{.X | html | urlquery}}", 51 | ".X | html | urlquery", 52 | []string{"html"}, 53 | }, 54 | { 55 | "{{.X | urlquery}}", 56 | ".X | html | urlquery", 57 | []string{"html", "urlquery"}, 58 | }, 59 | { 60 | "{{.X | html | print}}", 61 | ".X | urlquery | html | print", 62 | []string{"urlquery", "html"}, 63 | }, 64 | { 65 | "{{($).X | html | print}}", 66 | "($).X | urlquery | html | print", 67 | []string{"urlquery", "html"}, 68 | }, 69 | } 70 | for i, test := range tests { 71 | text := fmt.Sprintf(`{{define "t"}}%s{{end}}`, test.input) 72 | // fake funcs just for the test. 73 | funcs := map[string]interface{}{ 74 | "html": true, 75 | "print": true, 76 | "urlquery": true, 77 | } 78 | tree, err := parse.Parse("", text, "", "", funcs, FuncMap) 79 | if err != nil { 80 | t.Errorf("#%d: parsing error: %v", i, err) 81 | continue 82 | } 83 | action, ok := (tree["t"].List.Nodes[0].(*parse.ActionNode)) 84 | if !ok { 85 | t.Errorf("#%d: First node is not an action: %s", i, text) 86 | continue 87 | } 88 | pipe := action.Pipe 89 | ensurePipelineContains(pipe, test.ids) 90 | got := pipe.String() 91 | if got != test.output { 92 | t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, text, test.ids, test.output, got) 93 | } 94 | } 95 | } 96 | 97 | func TestRedundantFuncs(t *testing.T) { 98 | inputs := []interface{}{ 99 | "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 100 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 101 | ` !"#$%&'()*+,-./` + 102 | `0123456789:;<=>?` + 103 | `@ABCDEFGHIJKLMNO` + 104 | `PQRSTUVWXYZ[\]^_` + 105 | "`abcdefghijklmno" + 106 | "pqrstuvwxyz{|}~\x7f" + 107 | "\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" + 108 | "&%22\\", 109 | CSS(`a[href =~ "//example.com"]#foo`), 110 | HTML(`Hello, World &tc!`), 111 | HTMLAttr(` dir="ltr"`), 112 | JS(`c && alert("Hello, World!");`), 113 | JSStr(`Hello, World & O'Reilly\x21`), 114 | URL(`greeting=H%69&addressee=(World)`), 115 | } 116 | 117 | for n0, m := range redundantFuncs { 118 | f0 := FuncMap[n0].(func(...interface{}) string) 119 | for n1 := range m { 120 | f1 := FuncMap[n1].(func(...interface{}) string) 121 | for _, input := range inputs { 122 | want := f0(input) 123 | if got := f1(want); want != got { 124 | t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /v0/escape/url.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // urlFilter returns its input unless it contains an unsafe protocol in which 14 | // case it defangs the entire URL. 15 | func urlFilter(args ...interface{}) string { 16 | s, t := stringify(args...) 17 | if t == contentTypeURL { 18 | return s 19 | } 20 | if i := strings.IndexRune(s, ':'); i >= 0 && strings.IndexRune(s[:i], '/') < 0 { 21 | protocol := strings.ToLower(s[:i]) 22 | if protocol != "http" && protocol != "https" && protocol != "mailto" { 23 | return "#" + filterFailsafe 24 | } 25 | } 26 | return s 27 | } 28 | 29 | // urlEscaper produces an output that can be embedded in a URL query. 30 | // The output can be embedded in an HTML attribute without further escaping. 31 | func urlEscaper(args ...interface{}) string { 32 | return urlProcessor(false, args...) 33 | } 34 | 35 | // urlEscaper normalizes URL content so it can be embedded in a quote-delimited 36 | // string or parenthesis delimited url(...). 37 | // The normalizer does not encode all HTML specials. Specifically, it does not 38 | // encode '&' so correct embedding in an HTML attribute requires escaping of 39 | // '&' to '&'. 40 | func urlNormalizer(args ...interface{}) string { 41 | return urlProcessor(true, args...) 42 | } 43 | 44 | // urlProcessor normalizes (when norm is true) or escapes its input to produce 45 | // a valid hierarchical or opaque URL part. 46 | func urlProcessor(norm bool, args ...interface{}) string { 47 | s, t := stringify(args...) 48 | if t == contentTypeURL { 49 | norm = true 50 | } 51 | var b bytes.Buffer 52 | written := 0 53 | // The byte loop below assumes that all URLs use UTF-8 as the 54 | // content-encoding. This is similar to the URI to IRI encoding scheme 55 | // defined in section 3.1 of RFC 3987, and behaves the same as the 56 | // EcmaScript builtin encodeURIComponent. 57 | // It should not cause any misencoding of URLs in pages with 58 | // Content-type: text/html;charset=UTF-8. 59 | for i, n := 0, len(s); i < n; i++ { 60 | c := s[i] 61 | switch c { 62 | // Single quote and parens are sub-delims in RFC 3986, but we 63 | // escape them so the output can be embedded in single 64 | // quoted attributes and unquoted CSS url(...) constructs. 65 | // Single quotes are reserved in URLs, but are only used in 66 | // the obsolete "mark" rule in an appendix in RFC 3986 67 | // so can be safely encoded. 68 | case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']': 69 | if norm { 70 | continue 71 | } 72 | // Unreserved according to RFC 3986 sec 2.3 73 | // "For consistency, percent-encoded octets in the ranges of 74 | // ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), 75 | // period (%2E), underscore (%5F), or tilde (%7E) should not be 76 | // created by URI producers 77 | case '-', '.', '_', '~': 78 | continue 79 | case '%': 80 | // When normalizing do not re-encode valid escapes. 81 | if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) { 82 | continue 83 | } 84 | default: 85 | // Unreserved according to RFC 3986 sec 2.3 86 | if 'a' <= c && c <= 'z' { 87 | continue 88 | } 89 | if 'A' <= c && c <= 'Z' { 90 | continue 91 | } 92 | if '0' <= c && c <= '9' { 93 | continue 94 | } 95 | } 96 | b.WriteString(s[written:i]) 97 | fmt.Fprintf(&b, "%%%02x", c) 98 | written = i + 1 99 | } 100 | if written == 0 { 101 | return s 102 | } 103 | b.WriteString(s[written:]) 104 | return b.String() 105 | } 106 | -------------------------------------------------------------------------------- /v0/escape/pipeline.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "github.com/gorilla/template/v0/parse" 9 | ) 10 | 11 | // redundantFuncs[a][b] implies that FuncMap[b](FuncMap[a](x)) == FuncMap[a](x) 12 | // for all x. 13 | var redundantFuncs = map[string]map[string]bool{ 14 | "html_template_commentescaper": { 15 | "html_template_attrescaper": true, 16 | "html_template_nospaceescaper": true, 17 | "html_template_htmlescaper": true, 18 | }, 19 | "html_template_cssescaper": { 20 | "html_template_attrescaper": true, 21 | }, 22 | "html_template_jsregexpescaper": { 23 | "html_template_attrescaper": true, 24 | }, 25 | "html_template_jsstrescaper": { 26 | "html_template_attrescaper": true, 27 | }, 28 | "html_template_urlescaper": { 29 | "html_template_urlnormalizer": true, 30 | }, 31 | } 32 | 33 | // equivEscapers matches contextual escapers to equivalent template builtins. 34 | var equivEscapers = map[string]string{ 35 | "html_template_attrescaper": "html", 36 | "html_template_htmlescaper": "html", 37 | "html_template_nospaceescaper": "html", 38 | "html_template_rcdataescaper": "html", 39 | "html_template_urlescaper": "urlquery", 40 | "html_template_urlnormalizer": "urlquery", 41 | } 42 | 43 | // ensurePipelineContains ensures that the pipeline has commands with 44 | // the identifiers in s in order. 45 | // If the pipeline already has some of the sanitizers, do not interfere. 46 | // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it 47 | // has one matching, "html", and one to insert, "escapeJSVal", to produce 48 | // (.X | escapeJSVal | html). 49 | func ensurePipelineContains(p *parse.PipeNode, s []string) { 50 | if len(s) == 0 { 51 | return 52 | } 53 | n := len(p.Cmds) 54 | // Find the identifiers at the end of the command chain. 55 | idents := p.Cmds 56 | for i := n - 1; i >= 0; i-- { 57 | if cmd := p.Cmds[i]; len(cmd.Args) != 0 { 58 | if id, ok := cmd.Args[0].(*parse.IdentifierNode); ok { 59 | if id.Ident == "noescape" { 60 | return 61 | } 62 | continue 63 | } 64 | } 65 | idents = p.Cmds[i+1:] 66 | } 67 | dups := 0 68 | for _, id := range idents { 69 | if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) { 70 | dups++ 71 | if dups == len(s) { 72 | return 73 | } 74 | } 75 | } 76 | newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups) 77 | copy(newCmds, p.Cmds) 78 | // Merge existing identifier commands with the sanitizers needed. 79 | for _, id := range idents { 80 | pos := id.Args[0].Position() 81 | i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq) 82 | if i != -1 { 83 | for _, name := range s[:i] { 84 | newCmds = appendCmd(newCmds, newIdentCmd(name, pos)) 85 | } 86 | s = s[i+1:] 87 | } 88 | newCmds = appendCmd(newCmds, id) 89 | } 90 | // Create any remaining sanitizers. 91 | for _, name := range s { 92 | newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position())) 93 | } 94 | p.Cmds = newCmds 95 | } 96 | 97 | // escFnsEq returns whether the two escaping functions are equivalent. 98 | func escFnsEq(a, b string) bool { 99 | if e := equivEscapers[a]; e != "" { 100 | a = e 101 | } 102 | if e := equivEscapers[b]; e != "" { 103 | b = e 104 | } 105 | return a == b 106 | } 107 | 108 | // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found. 109 | func indexOfStr(s string, strs []string, eq func(a, b string) bool) int { 110 | for i, t := range strs { 111 | if eq(s, t) { 112 | return i 113 | } 114 | } 115 | return -1 116 | } 117 | 118 | // appendCmd appends the given command to the end of the command pipeline 119 | // unless it is redundant with the last command. 120 | func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode { 121 | if n := len(cmds); n != 0 { 122 | last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode) 123 | next, _ := cmd.Args[0].(*parse.IdentifierNode) 124 | if ok && redundantFuncs[last.Ident][next.Ident] { 125 | return cmds 126 | } 127 | } 128 | return append(cmds, cmd) 129 | } 130 | 131 | // newIdentCmd produces a command containing a single identifier node. 132 | func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode { 133 | return &parse.CommandNode{ 134 | NodeType: parse.NodeCommand, 135 | Args: []parse.Node{parse.NewIdentifier(identifier).SetPos(pos)}, 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /v0/escape/funcs.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "net/url" 12 | "strings" 13 | "unicode" 14 | "unicode/utf8" 15 | ) 16 | 17 | // HTML escaping. 18 | 19 | var ( 20 | htmlQuot = []byte(""") // shorter than """ 21 | htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 22 | htmlAmp = []byte("&") 23 | htmlLt = []byte("<") 24 | htmlGt = []byte(">") 25 | ) 26 | 27 | // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. 28 | func HTMLEscape(w io.Writer, b []byte) { 29 | last := 0 30 | for i, c := range b { 31 | var html []byte 32 | switch c { 33 | case '"': 34 | html = htmlQuot 35 | case '\'': 36 | html = htmlApos 37 | case '&': 38 | html = htmlAmp 39 | case '<': 40 | html = htmlLt 41 | case '>': 42 | html = htmlGt 43 | default: 44 | continue 45 | } 46 | w.Write(b[last:i]) 47 | w.Write(html) 48 | last = i + 1 49 | } 50 | w.Write(b[last:]) 51 | } 52 | 53 | // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. 54 | func HTMLEscapeString(s string) string { 55 | // Avoid allocation if we can. 56 | if strings.IndexAny(s, `'"&<>`) < 0 { 57 | return s 58 | } 59 | var b bytes.Buffer 60 | HTMLEscape(&b, []byte(s)) 61 | return b.String() 62 | } 63 | 64 | // JavaScript escaping. 65 | 66 | var ( 67 | jsLowUni = []byte(`\u00`) 68 | hex = []byte("0123456789ABCDEF") 69 | 70 | jsBackslash = []byte(`\\`) 71 | jsApos = []byte(`\'`) 72 | jsQuot = []byte(`\"`) 73 | jsLt = []byte(`\x3C`) 74 | jsGt = []byte(`\x3E`) 75 | ) 76 | 77 | // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. 78 | func JSEscape(w io.Writer, b []byte) { 79 | last := 0 80 | for i := 0; i < len(b); i++ { 81 | c := b[i] 82 | 83 | if !jsIsSpecial(rune(c)) { 84 | // fast path: nothing to do 85 | continue 86 | } 87 | w.Write(b[last:i]) 88 | 89 | if c < utf8.RuneSelf { 90 | // Quotes, slashes and angle brackets get quoted. 91 | // Control characters get written as \u00XX. 92 | switch c { 93 | case '\\': 94 | w.Write(jsBackslash) 95 | case '\'': 96 | w.Write(jsApos) 97 | case '"': 98 | w.Write(jsQuot) 99 | case '<': 100 | w.Write(jsLt) 101 | case '>': 102 | w.Write(jsGt) 103 | default: 104 | w.Write(jsLowUni) 105 | t, b := c>>4, c&0x0f 106 | w.Write(hex[t : t+1]) 107 | w.Write(hex[b : b+1]) 108 | } 109 | } else { 110 | // Unicode rune. 111 | r, size := utf8.DecodeRune(b[i:]) 112 | if unicode.IsPrint(r) { 113 | w.Write(b[i : i+size]) 114 | } else { 115 | fmt.Fprintf(w, "\\u%04X", r) 116 | } 117 | i += size - 1 118 | } 119 | last = i + 1 120 | } 121 | w.Write(b[last:]) 122 | } 123 | 124 | // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. 125 | func JSEscapeString(s string) string { 126 | // Avoid allocation if we can. 127 | if strings.IndexFunc(s, jsIsSpecial) < 0 { 128 | return s 129 | } 130 | var b bytes.Buffer 131 | JSEscape(&b, []byte(s)) 132 | return b.String() 133 | } 134 | 135 | func jsIsSpecial(r rune) bool { 136 | switch r { 137 | case '\\', '\'', '"', '<', '>': 138 | return true 139 | } 140 | return r < ' ' || utf8.RuneSelf <= r 141 | } 142 | 143 | // HTMLEscaper returns the escaped HTML equivalent of the textual 144 | // representation of its arguments. 145 | func HTMLEscaper(args ...interface{}) string { 146 | ok := false 147 | var s string 148 | if len(args) == 1 { 149 | s, ok = args[0].(string) 150 | } 151 | if !ok { 152 | s = fmt.Sprint(args...) 153 | } 154 | return HTMLEscapeString(s) 155 | } 156 | 157 | // JSEscaper returns the escaped JavaScript equivalent of the textual 158 | // representation of its arguments. 159 | func JSEscaper(args ...interface{}) string { 160 | ok := false 161 | var s string 162 | if len(args) == 1 { 163 | s, ok = args[0].(string) 164 | } 165 | if !ok { 166 | s = fmt.Sprint(args...) 167 | } 168 | return JSEscapeString(s) 169 | } 170 | 171 | // URLQueryEscaper returns the escaped value of the textual representation of 172 | // its arguments in a form suitable for embedding in a URL query. 173 | func URLQueryEscaper(args ...interface{}) string { 174 | s, ok := "", false 175 | if len(args) == 1 { 176 | s, ok = args[0].(string) 177 | } 178 | if !ok { 179 | s = fmt.Sprint(args...) 180 | } 181 | return url.QueryEscape(s) 182 | } 183 | -------------------------------------------------------------------------------- /v0/escape/content.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | // Strings of content from a trusted source. 13 | type ( 14 | // CSS encapsulates known safe content that matches any of: 15 | // 1. The CSS3 stylesheet production, such as `p { color: purple }`. 16 | // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. 17 | // 3. CSS3 declaration productions, such as `color: red; margin: 2px`. 18 | // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. 19 | // See http://www.w3.org/TR/css3-syntax/#style 20 | CSS string 21 | 22 | // HTML encapsulates a known safe HTML document fragment. 23 | // It should not be used for HTML from a third-party, or HTML with 24 | // unclosed tags or comments. The outputs of a sound HTML sanitizer 25 | // and a template escaped by this package are fine for use with HTML. 26 | HTML string 27 | 28 | // HTMLAttr encapsulates an HTML attribute from a trusted source, 29 | // for example, ` dir="ltr"`. 30 | HTMLAttr string 31 | 32 | // JS encapsulates a known safe EcmaScript5 Expression, for example, 33 | // `(x + y * z())`. 34 | // Template authors are responsible for ensuring that typed expressions 35 | // do not break the intended precedence and that there is no 36 | // statement/expression ambiguity as when passing an expression like 37 | // "{ foo: bar() }\n['foo']()", which is both a valid Expression and a 38 | // valid Program with a very different meaning. 39 | JS string 40 | 41 | // JSStr encapsulates a sequence of characters meant to be embedded 42 | // between quotes in a JavaScript expression. 43 | // The string must match a series of StringCharacters: 44 | // StringCharacter :: SourceCharacter but not `\` or LineTerminator 45 | // | EscapeSequence 46 | // Note that LineContinuations are not allowed. 47 | // JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not. 48 | JSStr string 49 | 50 | // URL encapsulates a known safe URL or URL substring (see RFC 3986). 51 | // A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()` 52 | // from a trusted source should go in the page, but by default dynamic 53 | // `javascript:` URLs are filtered out since they are a frequently 54 | // exploited injection vector. 55 | URL string 56 | ) 57 | 58 | type contentType uint8 59 | 60 | const ( 61 | contentTypePlain contentType = iota 62 | contentTypeCSS 63 | contentTypeHTML 64 | contentTypeHTMLAttr 65 | contentTypeJS 66 | contentTypeJSStr 67 | contentTypeURL 68 | // contentTypeUnsafe is used in attr.go for values that affect how 69 | // embedded content and network messages are formed, vetted, 70 | // or interpreted; or which credentials network messages carry. 71 | contentTypeUnsafe 72 | ) 73 | 74 | // indirect returns the value, after dereferencing as many times 75 | // as necessary to reach the base type (or nil). 76 | func indirect(a interface{}) interface{} { 77 | if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { 78 | // Avoid creating a reflect.Value if it's not a pointer. 79 | return a 80 | } 81 | v := reflect.ValueOf(a) 82 | for v.Kind() == reflect.Ptr && !v.IsNil() { 83 | v = v.Elem() 84 | } 85 | return v.Interface() 86 | } 87 | 88 | var ( 89 | errorType = reflect.TypeOf((*error)(nil)).Elem() 90 | fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() 91 | ) 92 | 93 | // indirectToStringerOrError returns the value, after dereferencing as many times 94 | // as necessary to reach the base type (or nil) or an implementation of fmt.Stringer 95 | // or error, 96 | func indirectToStringerOrError(a interface{}) interface{} { 97 | v := reflect.ValueOf(a) 98 | for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { 99 | v = v.Elem() 100 | } 101 | return v.Interface() 102 | } 103 | 104 | // stringify converts its arguments to a string and the type of the content. 105 | // All pointers are dereferenced, as in the text/template package. 106 | func stringify(args ...interface{}) (string, contentType) { 107 | if len(args) == 1 { 108 | switch s := indirect(args[0]).(type) { 109 | case string: 110 | return s, contentTypePlain 111 | case CSS: 112 | return string(s), contentTypeCSS 113 | case HTML: 114 | return string(s), contentTypeHTML 115 | case HTMLAttr: 116 | return string(s), contentTypeHTMLAttr 117 | case JS: 118 | return string(s), contentTypeJS 119 | case JSStr: 120 | return string(s), contentTypeJSStr 121 | case URL: 122 | return string(s), contentTypeURL 123 | } 124 | } 125 | for i, arg := range args { 126 | args[i] = indirectToStringerOrError(arg) 127 | } 128 | return fmt.Sprint(args...), contentTypePlain 129 | } 130 | -------------------------------------------------------------------------------- /v0/inline.go: -------------------------------------------------------------------------------- 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 | "fmt" 9 | 10 | "github.com/gorilla/template/v0/parse" 11 | ) 12 | 13 | // parentList returns the list of parent templates for a given template name. 14 | // It returns an error if a template is not found or recursive dependency 15 | // is detected. 16 | func parentList(tree parse.Tree, name string) (deps []string, err error) { 17 | for { 18 | define := tree[name] 19 | if define == nil { 20 | return nil, fmt.Errorf("template: template not found: %q", name) 21 | } 22 | for _, v := range deps { 23 | if v == name { 24 | deps = append(deps, name) 25 | return nil, fmt.Errorf("template: impossible recursion: %#v", 26 | deps) 27 | } 28 | } 29 | deps = append(deps, name) 30 | name = define.Parent 31 | if name == "" { 32 | break 33 | } 34 | } 35 | return 36 | } 37 | 38 | // compilationOrder returns the order in which templates must be compiled in a 39 | // set. Parents are compiled only after all their dependents were compiled. 40 | func compilationOrder(tree parse.Tree) ([]string, error) { 41 | var deps [][]string 42 | for name, _ := range tree { 43 | p, err := parentList(tree, name) 44 | if err != nil { 45 | return nil, err 46 | } 47 | deps = append(deps, p) 48 | } 49 | order := make([]string, len(deps)) 50 | for len(deps) > 0 { 51 | i := 0 52 | for i < len(deps) { 53 | if len(deps[i]) == 1 { 54 | name := deps[i][0] 55 | order[len(deps)-1] = name 56 | deps = append(deps[:i], deps[i+1:]...) 57 | for k, v := range deps { 58 | var s []string 59 | for _, v2 := range v { 60 | if v2 != name { 61 | s = append(s, v2) 62 | } 63 | } 64 | deps[k] = s 65 | } 66 | } else { 67 | i++ 68 | } 69 | } 70 | } 71 | return order, nil 72 | } 73 | 74 | // inlineTree expands all {{define}} actions from a tree. 75 | func inlineTree(tree parse.Tree) error { 76 | order, err := compilationOrder(tree) 77 | if err != nil { 78 | return err 79 | } 80 | for _, name := range order { 81 | if err := inlineDefine(tree, name); err != nil { 82 | return err 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | // inlineDefine expands a simple or extended {{define}} action. 89 | func inlineDefine(tree parse.Tree, name string) error { 90 | define := tree[name] 91 | parent := tree[define.Parent] 92 | if define.Parent == "" { 93 | // Expand {{slot}}, remove {{fill}}. 94 | cleanupSlot(tree[name].List) 95 | return nil 96 | } else if parent == nil { 97 | return fmt.Errorf("template: define extends undefined parent %q", 98 | define.Parent) 99 | } 100 | // Get all FillNode's from current define. 101 | fillers := map[string]*parse.FillNode{} 102 | unused := map[string]bool{} 103 | for _, n := range define.List.Nodes { 104 | if f, ok := n.(*parse.FillNode); ok { 105 | fillers[f.Name] = f 106 | unused[f.Name] = true 107 | } 108 | } 109 | // Update nodes and parent. 110 | // TODO: must review debugging system because updating like this will 111 | // report wrong positions and context. 112 | define.List = parent.List.CopyList() 113 | define.Parent = parent.Parent 114 | // Replace FillNode's and SlotNode's from parent. 115 | applyFillers(define.List, fillers, unused) 116 | // Add extra fillers. 117 | for k, v := range unused { 118 | if v { 119 | define.List.Nodes = append(define.List.Nodes, fillers[k].CopyFill()) 120 | } 121 | } 122 | // Do it again until parent is empty. 123 | return inlineDefine(tree, name) 124 | } 125 | 126 | // applyFillers replaces slot and fill nodes by their filler counterparts. 127 | func applyFillers(n parse.Node, fillers map[string]*parse.FillNode, unused map[string]bool) { 128 | switch n := n.(type) { 129 | case *parse.IfNode: 130 | applyFillers(n.List, fillers, unused) 131 | applyFillers(n.ElseList, fillers, unused) 132 | case *parse.ListNode: 133 | if n == nil { 134 | return 135 | } 136 | for k, v := range n.Nodes { 137 | switch v := v.(type) { 138 | case *parse.SlotNode: 139 | // Replace the slot by the list of nodes from the filler. 140 | if filler := fillers[v.Name]; filler != nil { 141 | n.Nodes[k] = filler.List.CopyList() 142 | } 143 | case *parse.FillNode: 144 | // Replace the fill by the new filler. 145 | if filler := fillers[v.Name]; filler != nil { 146 | n.Nodes[k] = filler.CopyFill() 147 | unused[v.Name] = false 148 | } 149 | default: 150 | applyFillers(v, fillers, unused) 151 | } 152 | } 153 | case *parse.RangeNode: 154 | applyFillers(n.List, fillers, unused) 155 | applyFillers(n.ElseList, fillers, unused) 156 | case *parse.WithNode: 157 | applyFillers(n.List, fillers, unused) 158 | applyFillers(n.ElseList, fillers, unused) 159 | } 160 | } 161 | 162 | // cleanupSlot removes slot and fill nodes. 163 | // 164 | // May contain child actions: 165 | // SlotNode: n.List 166 | // DefineNode: n.List 167 | // FillNode: n.List 168 | // IfNode: n.List, n.ElseList 169 | // ListNode: n.Nodes 170 | // RangeNode: n.List, n.ElseList 171 | // WithNode: n.List, n.ElseList 172 | func cleanupSlot(n parse.Node) { 173 | switch n := n.(type) { 174 | case *parse.IfNode: 175 | cleanupSlot(n.List) 176 | cleanupSlot(n.ElseList) 177 | case *parse.ListNode: 178 | if n == nil { 179 | return 180 | } 181 | k := 0 182 | for k < len(n.Nodes) { 183 | v := n.Nodes[k] 184 | switch v := v.(type) { 185 | case *parse.SlotNode: 186 | // Replace the slot by its list of nodes. 187 | n.Nodes[k] = v.List 188 | continue 189 | case *parse.FillNode: 190 | // Remove the filler. 191 | n.Nodes = append(n.Nodes[:k], n.Nodes[k+1:]...) 192 | continue 193 | default: 194 | cleanupSlot(v) 195 | } 196 | k++ 197 | } 198 | case *parse.RangeNode: 199 | cleanupSlot(n.List) 200 | cleanupSlot(n.ElseList) 201 | case *parse.WithNode: 202 | cleanupSlot(n.List) 203 | cleanupSlot(n.ElseList) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /v0/examplefiles_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 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_test 6 | 7 | import ( 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "text/template" 14 | ) 15 | 16 | // templateFile defines the contents of a template to be stored in a file, for testing. 17 | type templateFile struct { 18 | name string 19 | contents string 20 | } 21 | 22 | func createTestDir(files []templateFile) string { 23 | dir, err := ioutil.TempDir("", "template") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | for _, file := range files { 28 | f, err := os.Create(filepath.Join(dir, file.name)) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | defer f.Close() 33 | _, err = io.WriteString(f, file.contents) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | } 38 | return dir 39 | } 40 | 41 | // Here we demonstrate loading a set of templates from a directory. 42 | func ExampleTemplate_glob() { 43 | // Here we create a temporary directory and populate it with our sample 44 | // template definition files; usually the template files would already 45 | // exist in some location known to the program. 46 | dir := createTestDir([]templateFile{ 47 | // T0.tmpl is a plain template file that just invokes T1. 48 | {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`}, 49 | // T1.tmpl defines a template, T1 that invokes T2. 50 | {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, 51 | // T2.tmpl defines a template T2. 52 | {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, 53 | }) 54 | // Clean up after the test; another quirk of running as an example. 55 | defer os.RemoveAll(dir) 56 | 57 | // pattern is the glob pattern used to find all the template files. 58 | pattern := filepath.Join(dir, "*.tmpl") 59 | 60 | // Here starts the example proper. 61 | // T0.tmpl is the first name matched, so it becomes the starting template, 62 | // the value returned by ParseGlob. 63 | tmpl := template.Must(template.ParseGlob(pattern)) 64 | 65 | err := tmpl.Execute(os.Stdout, nil) 66 | if err != nil { 67 | log.Fatalf("template execution: %s", err) 68 | } 69 | // Output: 70 | // T0 invokes T1: (T1 invokes T2: (This is T2)) 71 | } 72 | 73 | // This example demonstrates one way to share some templates 74 | // and use them in different contexts. In this variant we add multiple driver 75 | // templates by hand to an existing bundle of templates. 76 | func ExampleTemplate_helpers() { 77 | // Here we create a temporary directory and populate it with our sample 78 | // template definition files; usually the template files would already 79 | // exist in some location known to the program. 80 | dir := createTestDir([]templateFile{ 81 | // T1.tmpl defines a template, T1 that invokes T2. 82 | {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, 83 | // T2.tmpl defines a template T2. 84 | {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`}, 85 | }) 86 | // Clean up after the test; another quirk of running as an example. 87 | defer os.RemoveAll(dir) 88 | 89 | // pattern is the glob pattern used to find all the template files. 90 | pattern := filepath.Join(dir, "*.tmpl") 91 | 92 | // Here starts the example proper. 93 | // Load the helpers. 94 | templates := template.Must(template.ParseGlob(pattern)) 95 | // Add one driver template to the bunch; we do this with an explicit template definition. 96 | _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}") 97 | if err != nil { 98 | log.Fatal("parsing driver1: ", err) 99 | } 100 | // Add another driver template. 101 | _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}") 102 | if err != nil { 103 | log.Fatal("parsing driver2: ", err) 104 | } 105 | // We load all the templates before execution. This package does not require 106 | // that behavior but html/template's escaping does, so it's a good habit. 107 | err = templates.ExecuteTemplate(os.Stdout, "driver1", nil) 108 | if err != nil { 109 | log.Fatalf("driver1 execution: %s", err) 110 | } 111 | err = templates.ExecuteTemplate(os.Stdout, "driver2", nil) 112 | if err != nil { 113 | log.Fatalf("driver2 execution: %s", err) 114 | } 115 | // Output: 116 | // Driver 1 calls T1: (T1 invokes T2: (This is T2)) 117 | // Driver 2 calls T2: (This is T2) 118 | } 119 | 120 | // This example demonstrates how to use one group of driver 121 | // templates with distinct sets of helper templates. 122 | func ExampleTemplate_share() { 123 | // Here we create a temporary directory and populate it with our sample 124 | // template definition files; usually the template files would already 125 | // exist in some location known to the program. 126 | dir := createTestDir([]templateFile{ 127 | // T0.tmpl is a plain template file that just invokes T1. 128 | {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"}, 129 | // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined 130 | {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`}, 131 | }) 132 | // Clean up after the test; another quirk of running as an example. 133 | defer os.RemoveAll(dir) 134 | 135 | // pattern is the glob pattern used to find all the template files. 136 | pattern := filepath.Join(dir, "*.tmpl") 137 | 138 | // Here starts the example proper. 139 | // Load the drivers. 140 | drivers := template.Must(template.ParseGlob(pattern)) 141 | 142 | // We must define an implementation of the T2 template. First we clone 143 | // the drivers, then add a definition of T2 to the template name space. 144 | 145 | // 1. Clone the helper set to create a new name space from which to run them. 146 | first, err := drivers.Clone() 147 | if err != nil { 148 | log.Fatal("cloning helpers: ", err) 149 | } 150 | // 2. Define T2, version A, and parse it. 151 | _, err = first.Parse("{{define `T2`}}T2, version A{{end}}") 152 | if err != nil { 153 | log.Fatal("parsing T2: ", err) 154 | } 155 | 156 | // Now repeat the whole thing, using a different version of T2. 157 | // 1. Clone the drivers. 158 | second, err := drivers.Clone() 159 | if err != nil { 160 | log.Fatal("cloning drivers: ", err) 161 | } 162 | // 2. Define T2, version B, and parse it. 163 | _, err = second.Parse("{{define `T2`}}T2, version B{{end}}") 164 | if err != nil { 165 | log.Fatal("parsing T2: ", err) 166 | } 167 | 168 | // Execute the templates in the reverse order to verify the 169 | // first is unaffected by the second. 170 | err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second") 171 | if err != nil { 172 | log.Fatalf("second execution: %s", err) 173 | } 174 | err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first") 175 | if err != nil { 176 | log.Fatalf("first: execution: %s", err) 177 | } 178 | 179 | // Output: 180 | // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B)) 181 | // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A)) 182 | } 183 | -------------------------------------------------------------------------------- /v0/escape/attr.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // attrTypeMap[n] describes the value of the given attribute. 12 | // If an attribute affects (or can mask) the encoding or interpretation of 13 | // other content, or affects the contents, idempotency, or credentials of a 14 | // network message, then the value in this map is contentTypeUnsafe. 15 | // This map is derived from HTML5, specifically 16 | // http://www.w3.org/TR/html5/Overview.html#attributes-1 17 | // as well as "%URI"-typed attributes from 18 | // http://www.w3.org/TR/html4/index/attributes.html 19 | var attrTypeMap = map[string]contentType{ 20 | "accept": contentTypePlain, 21 | "accept-charset": contentTypeUnsafe, 22 | "action": contentTypeURL, 23 | "alt": contentTypePlain, 24 | "archive": contentTypeURL, 25 | "async": contentTypeUnsafe, 26 | "autocomplete": contentTypePlain, 27 | "autofocus": contentTypePlain, 28 | "autoplay": contentTypePlain, 29 | "background": contentTypeURL, 30 | "border": contentTypePlain, 31 | "checked": contentTypePlain, 32 | "cite": contentTypeURL, 33 | "challenge": contentTypeUnsafe, 34 | "charset": contentTypeUnsafe, 35 | "class": contentTypePlain, 36 | "classid": contentTypeURL, 37 | "codebase": contentTypeURL, 38 | "cols": contentTypePlain, 39 | "colspan": contentTypePlain, 40 | "content": contentTypeUnsafe, 41 | "contenteditable": contentTypePlain, 42 | "contextmenu": contentTypePlain, 43 | "controls": contentTypePlain, 44 | "coords": contentTypePlain, 45 | "crossorigin": contentTypeUnsafe, 46 | "data": contentTypeURL, 47 | "datetime": contentTypePlain, 48 | "default": contentTypePlain, 49 | "defer": contentTypeUnsafe, 50 | "dir": contentTypePlain, 51 | "dirname": contentTypePlain, 52 | "disabled": contentTypePlain, 53 | "draggable": contentTypePlain, 54 | "dropzone": contentTypePlain, 55 | "enctype": contentTypeUnsafe, 56 | "for": contentTypePlain, 57 | "form": contentTypeUnsafe, 58 | "formaction": contentTypeURL, 59 | "formenctype": contentTypeUnsafe, 60 | "formmethod": contentTypeUnsafe, 61 | "formnovalidate": contentTypeUnsafe, 62 | "formtarget": contentTypePlain, 63 | "headers": contentTypePlain, 64 | "height": contentTypePlain, 65 | "hidden": contentTypePlain, 66 | "high": contentTypePlain, 67 | "href": contentTypeURL, 68 | "hreflang": contentTypePlain, 69 | "http-equiv": contentTypeUnsafe, 70 | "icon": contentTypeURL, 71 | "id": contentTypePlain, 72 | "ismap": contentTypePlain, 73 | "keytype": contentTypeUnsafe, 74 | "kind": contentTypePlain, 75 | "label": contentTypePlain, 76 | "lang": contentTypePlain, 77 | "language": contentTypeUnsafe, 78 | "list": contentTypePlain, 79 | "longdesc": contentTypeURL, 80 | "loop": contentTypePlain, 81 | "low": contentTypePlain, 82 | "manifest": contentTypeURL, 83 | "max": contentTypePlain, 84 | "maxlength": contentTypePlain, 85 | "media": contentTypePlain, 86 | "mediagroup": contentTypePlain, 87 | "method": contentTypeUnsafe, 88 | "min": contentTypePlain, 89 | "multiple": contentTypePlain, 90 | "name": contentTypePlain, 91 | "novalidate": contentTypeUnsafe, 92 | // Skip handler names from 93 | // http://www.w3.org/TR/html5/Overview.html#event-handlers-on-elements-document-objects-and-window-objects 94 | // since we have special handling in attrType. 95 | "open": contentTypePlain, 96 | "optimum": contentTypePlain, 97 | "pattern": contentTypeUnsafe, 98 | "placeholder": contentTypePlain, 99 | "poster": contentTypeURL, 100 | "profile": contentTypeURL, 101 | "preload": contentTypePlain, 102 | "pubdate": contentTypePlain, 103 | "radiogroup": contentTypePlain, 104 | "readonly": contentTypePlain, 105 | "rel": contentTypeUnsafe, 106 | "required": contentTypePlain, 107 | "reversed": contentTypePlain, 108 | "rows": contentTypePlain, 109 | "rowspan": contentTypePlain, 110 | "sandbox": contentTypeUnsafe, 111 | "spellcheck": contentTypePlain, 112 | "scope": contentTypePlain, 113 | "scoped": contentTypePlain, 114 | "seamless": contentTypePlain, 115 | "selected": contentTypePlain, 116 | "shape": contentTypePlain, 117 | "size": contentTypePlain, 118 | "sizes": contentTypePlain, 119 | "span": contentTypePlain, 120 | "src": contentTypeURL, 121 | "srcdoc": contentTypeHTML, 122 | "srclang": contentTypePlain, 123 | "start": contentTypePlain, 124 | "step": contentTypePlain, 125 | "style": contentTypeCSS, 126 | "tabindex": contentTypePlain, 127 | "target": contentTypePlain, 128 | "title": contentTypePlain, 129 | "type": contentTypeUnsafe, 130 | "usemap": contentTypeURL, 131 | "value": contentTypeUnsafe, 132 | "width": contentTypePlain, 133 | "wrap": contentTypePlain, 134 | "xmlns": contentTypeURL, 135 | } 136 | 137 | // attrType returns a conservative (upper-bound on authority) guess at the 138 | // type of the named attribute. 139 | func attrType(name string) contentType { 140 | name = strings.ToLower(name) 141 | if strings.HasPrefix(name, "data-") { 142 | // Strip data- so that custom attribute heuristics below are 143 | // widely applied. 144 | // Treat data-action as URL below. 145 | name = name[5:] 146 | } else if colon := strings.IndexRune(name, ':'); colon != -1 { 147 | if name[:colon] == "xmlns" { 148 | return contentTypeURL 149 | } 150 | // Treat svg:href and xlink:href as href below. 151 | name = name[colon+1:] 152 | } 153 | if t, ok := attrTypeMap[name]; ok { 154 | return t 155 | } 156 | // Treat partial event handler names as script. 157 | if strings.HasPrefix(name, "on") { 158 | return contentTypeJS 159 | } 160 | 161 | // Heuristics to prevent "javascript:..." injection in custom 162 | // data attributes and custom attributes like g:tweetUrl. 163 | // http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible-data-with-the-data-attributes: 164 | // "Custom data attributes are intended to store custom data 165 | // private to the page or application, for which there are no 166 | // more appropriate attributes or elements." 167 | // Developers seem to store URL content in data URLs that start 168 | // or end with "URI" or "URL". 169 | if strings.Contains(name, "src") || 170 | strings.Contains(name, "uri") || 171 | strings.Contains(name, "url") { 172 | return contentTypeURL 173 | } 174 | return contentTypePlain 175 | } 176 | -------------------------------------------------------------------------------- /v0/funcs.go: -------------------------------------------------------------------------------- 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 | "fmt" 9 | "reflect" 10 | 11 | "github.com/gorilla/template/v0/escape" 12 | ) 13 | 14 | // FuncMap is the type of the map defining the mapping from names to functions. 15 | // Each function must have either a single return value, or two return values of 16 | // which the second has type error. In that case, if the second (error) 17 | // argument evaluates to non-nil during execution, execution terminates and 18 | // Execute returns that error. 19 | type FuncMap map[string]interface{} 20 | 21 | var builtins = FuncMap{ 22 | "and": and, 23 | "call": call, 24 | "html": escape.HTMLEscaper, 25 | "index": index, 26 | "js": escape.JSEscaper, 27 | "len": length, 28 | "not": not, 29 | "or": or, 30 | "print": fmt.Sprint, 31 | "printf": fmt.Sprintf, 32 | "println": fmt.Sprintln, 33 | "urlquery": escape.URLQueryEscaper, 34 | } 35 | 36 | var builtinFuncs = createValueFuncs(builtins) 37 | 38 | // createValueFuncs turns a FuncMap into a map[string]reflect.Value 39 | func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { 40 | m := make(map[string]reflect.Value) 41 | addValueFuncs(m, funcMap) 42 | return m 43 | } 44 | 45 | // addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. 46 | func addValueFuncs(out map[string]reflect.Value, in FuncMap) { 47 | for name, fn := range in { 48 | v := reflect.ValueOf(fn) 49 | if v.Kind() != reflect.Func { 50 | panic("value for " + name + " not a function") 51 | } 52 | if !goodFunc(v.Type()) { 53 | panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) 54 | } 55 | out[name] = v 56 | } 57 | } 58 | 59 | // addFuncs adds to values the functions in funcs. It does no checking of the input - 60 | // call addValueFuncs first. 61 | func addFuncs(out, in FuncMap) { 62 | for name, fn := range in { 63 | out[name] = fn 64 | } 65 | } 66 | 67 | // goodFunc checks that the function or method has the right result signature. 68 | func goodFunc(typ reflect.Type) bool { 69 | // We allow functions with 1 result or 2 results where the second is an error. 70 | switch { 71 | case typ.NumOut() == 1: 72 | return true 73 | case typ.NumOut() == 2 && typ.Out(1) == errorType: 74 | return true 75 | } 76 | return false 77 | } 78 | 79 | // findFunction looks for a function in the template, and global map. 80 | func findFunction(name string, set *Set) (reflect.Value, bool) { 81 | if set != nil { 82 | if fn := set.execFuncs[name]; fn.IsValid() { 83 | return fn, true 84 | } 85 | } 86 | if fn := builtinFuncs[name]; fn.IsValid() { 87 | return fn, true 88 | } 89 | return reflect.Value{}, false 90 | } 91 | 92 | // Indexing. 93 | 94 | // index returns the result of indexing its first argument by the following 95 | // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each 96 | // indexed item must be a map, slice, or array. 97 | func index(item interface{}, indices ...interface{}) (interface{}, error) { 98 | v := reflect.ValueOf(item) 99 | for _, i := range indices { 100 | index := reflect.ValueOf(i) 101 | var isNil bool 102 | if v, isNil = indirect(v); isNil { 103 | return nil, fmt.Errorf("index of nil pointer") 104 | } 105 | switch v.Kind() { 106 | case reflect.Array, reflect.Slice, reflect.String: 107 | var x int64 108 | switch index.Kind() { 109 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 110 | x = index.Int() 111 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 112 | x = int64(index.Uint()) 113 | default: 114 | return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) 115 | } 116 | if x < 0 || x >= int64(v.Len()) { 117 | return nil, fmt.Errorf("index out of range: %d", x) 118 | } 119 | v = v.Index(int(x)) 120 | case reflect.Map: 121 | if !index.IsValid() { 122 | index = reflect.Zero(v.Type().Key()) 123 | } 124 | if !index.Type().AssignableTo(v.Type().Key()) { 125 | return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) 126 | } 127 | if x := v.MapIndex(index); x.IsValid() { 128 | v = x 129 | } else { 130 | v = reflect.Zero(v.Type().Elem()) 131 | } 132 | default: 133 | return nil, fmt.Errorf("can't index item of type %s", v.Type()) 134 | } 135 | } 136 | return v.Interface(), nil 137 | } 138 | 139 | // Length 140 | 141 | // length returns the length of the item, with an error if it has no defined length. 142 | func length(item interface{}) (int, error) { 143 | v, isNil := indirect(reflect.ValueOf(item)) 144 | if isNil { 145 | return 0, fmt.Errorf("len of nil pointer") 146 | } 147 | switch v.Kind() { 148 | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: 149 | return v.Len(), nil 150 | } 151 | return 0, fmt.Errorf("len of type %s", v.Type()) 152 | } 153 | 154 | // Function invocation 155 | 156 | // call returns the result of evaluating the first argument as a function. 157 | // The function must return 1 result, or 2 results, the second of which is an error. 158 | func call(fn interface{}, args ...interface{}) (interface{}, error) { 159 | v := reflect.ValueOf(fn) 160 | typ := v.Type() 161 | if typ.Kind() != reflect.Func { 162 | return nil, fmt.Errorf("non-function of type %s", typ) 163 | } 164 | if !goodFunc(typ) { 165 | return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) 166 | } 167 | numIn := typ.NumIn() 168 | var dddType reflect.Type 169 | if typ.IsVariadic() { 170 | if len(args) < numIn-1 { 171 | return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) 172 | } 173 | dddType = typ.In(numIn - 1).Elem() 174 | } else { 175 | if len(args) != numIn { 176 | return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) 177 | } 178 | } 179 | argv := make([]reflect.Value, len(args)) 180 | for i, arg := range args { 181 | value := reflect.ValueOf(arg) 182 | // Compute the expected type. Clumsy because of variadics. 183 | var argType reflect.Type 184 | if !typ.IsVariadic() || i < numIn-1 { 185 | argType = typ.In(i) 186 | } else { 187 | argType = dddType 188 | } 189 | if !value.IsValid() && canBeNil(argType) { 190 | value = reflect.Zero(argType) 191 | } 192 | if !value.Type().AssignableTo(argType) { 193 | return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType) 194 | } 195 | argv[i] = value 196 | } 197 | result := v.Call(argv) 198 | if len(result) == 2 { 199 | return result[0].Interface(), result[1].Interface().(error) 200 | } 201 | return result[0].Interface(), nil 202 | } 203 | 204 | // Boolean logic. 205 | 206 | func truth(a interface{}) bool { 207 | t, _ := isTrue(reflect.ValueOf(a)) 208 | return t 209 | } 210 | 211 | // and computes the Boolean AND of its arguments, returning 212 | // the first false argument it encounters, or the last argument. 213 | func and(arg0 interface{}, args ...interface{}) interface{} { 214 | if !truth(arg0) { 215 | return arg0 216 | } 217 | for i := range args { 218 | arg0 = args[i] 219 | if !truth(arg0) { 220 | break 221 | } 222 | } 223 | return arg0 224 | } 225 | 226 | // or computes the Boolean OR of its arguments, returning 227 | // the first true argument it encounters, or the last argument. 228 | func or(arg0 interface{}, args ...interface{}) interface{} { 229 | if truth(arg0) { 230 | return arg0 231 | } 232 | for i := range args { 233 | arg0 = args[i] 234 | if truth(arg0) { 235 | break 236 | } 237 | } 238 | return arg0 239 | } 240 | 241 | // not returns the Boolean negation of its argument. 242 | func not(arg interface{}) (truth bool) { 243 | truth, _ = isTrue(reflect.ValueOf(arg)) 244 | return !truth 245 | } 246 | -------------------------------------------------------------------------------- /v0/escape/error.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // Error describes a problem encountered during template Escaping. 12 | type Error struct { 13 | // ErrorCode describes the kind of error. 14 | ErrorCode ErrorCode 15 | // Name is the name of the template in which the error was encountered. 16 | Name string 17 | // Line is the line number of the error in the template source or 0. 18 | Line int 19 | // Description is a human-readable description of the problem. 20 | Description string 21 | } 22 | 23 | // ErrorCode is a code for a kind of error. 24 | type ErrorCode int 25 | 26 | // We define codes for each error that manifests while escaping templates, but 27 | // escaped templates may also fail at runtime. 28 | // 29 | // Output: "ZgotmplZ" 30 | // Example: 31 | // 32 | // where {{.X}} evaluates to `javascript:...` 33 | // Discussion: 34 | // "ZgotmplZ" is a special value that indicates that unsafe content reached a 35 | // CSS or URL context at runtime. The output of the example will be 36 | // 37 | // If the data comes from a trusted source, use content types to exempt it 38 | // from filtering: URL(`javascript:...`). 39 | const ( 40 | // OK indicates the lack of an error. 41 | OK ErrorCode = iota 42 | 43 | // ErrAmbigContext: "... appears in an ambiguous URL context" 44 | // Example: 45 | // 53 | // Discussion: 54 | // {{.X}} is in an ambiguous URL context since, depending on {{.C}}, 55 | // it may be either a URL suffix or a query parameter. 56 | // Moving {{.X}} into the condition removes the ambiguity: 57 | // 58 | ErrAmbigContext 59 | 60 | // ErrBadHTML: "expected space, attr name, or end of tag, but got ...", 61 | // "... in unquoted attr", "... in attribute name" 62 | // Example: 63 | // 64 | // 65 | //
66 | //
{{end}} 113 | // {{define "attrs"}}href="{{.URL}}"{{end}} 114 | // Discussion: 115 | // Package html/template looks through template calls to compute the 116 | // context. 117 | // Here the {{.URL}} in "attrs" must be treated as a URL when called 118 | // from "main", but you will get this error if "attrs" is not defined 119 | // when "main" is parsed. 120 | ErrNoSuchTemplate 121 | 122 | // ErrOutputContext: "cannot compute output context for template ..." 123 | // Examples: 124 | // {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}} 125 | // Discussion: 126 | // A recursive template does not end in the same context in which it 127 | // starts, and a reliable output context cannot be computed. 128 | // Look for typos in the named template. 129 | // If the template should not be called in the named start context, 130 | // look for calls to that template in unexpected contexts. 131 | // Maybe refactor recursive templates to not be recursive. 132 | ErrOutputContext 133 | 134 | // ErrPartialCharset: "unfinished JS regexp charset in ..." 135 | // Example: 136 | // 137 | // Discussion: 138 | // Package html/template does not support interpolation into regular 139 | // expression literal character sets. 140 | ErrPartialCharset 141 | 142 | // ErrPartialEscape: "unfinished escape sequence in ..." 143 | // Example: 144 | // 145 | // Discussion: 146 | // Package html/template does not support actions following a 147 | // backslash. 148 | // This is usually an error and there are better solutions; for 149 | // example 150 | // 151 | // should work, and if {{.X}} is a partial escape sequence such as 152 | // "xA0", mark the whole sequence as safe content: JSStr(`\xA0`) 153 | ErrPartialEscape 154 | 155 | // ErrRangeLoopReentry: "on range loop re-entry: ..." 156 | // Example: 157 | // 158 | // Discussion: 159 | // If an iteration through a range would cause it to end in a 160 | // different context than an earlier pass, there is no single context. 161 | // In the example, there is missing a quote, so it is not clear 162 | // whether {{.}} is meant to be inside a JS string or in a JS value 163 | // context. The second iteration would produce something like 164 | // 165 | // 166 | ErrRangeLoopReentry 167 | 168 | // ErrSlashAmbig: '/' could start a division or regexp. 169 | // Example: 170 | // 174 | // Discussion: 175 | // The example above could produce `var x = 1/-2/i.test(s)...` 176 | // in which the first '/' is a mathematical division operator or it 177 | // could produce `/-2/i.test(s)` in which the first '/' starts a 178 | // regexp literal. 179 | // Look for missing semicolons inside branches, and maybe add 180 | // parentheses to make it clear which interpretation you intend. 181 | ErrSlashAmbig 182 | ) 183 | 184 | func (e *Error) Error() string { 185 | if e.Line != 0 { 186 | return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description) 187 | } else if e.Name != "" { 188 | return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description) 189 | } 190 | return "html/template: " + e.Description 191 | } 192 | 193 | // errorf creates an error given a format string f and args. 194 | // The template Name still needs to be supplied. 195 | func errorf(k ErrorCode, line int, f string, args ...interface{}) *Error { 196 | return &Error{k, "", line, fmt.Sprintf(f, args...)} 197 | } 198 | -------------------------------------------------------------------------------- /v0/escape/css.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "unicode" 11 | "unicode/utf8" 12 | ) 13 | 14 | // endsWithCSSKeyword returns whether b ends with an ident that 15 | // case-insensitively matches the lower-case kw. 16 | func endsWithCSSKeyword(b []byte, kw string) bool { 17 | i := len(b) - len(kw) 18 | if i < 0 { 19 | // Too short. 20 | return false 21 | } 22 | if i != 0 { 23 | r, _ := utf8.DecodeLastRune(b[:i]) 24 | if isCSSNmchar(r) { 25 | // Too long. 26 | return false 27 | } 28 | } 29 | // Many CSS keywords, such as "!important" can have characters encoded, 30 | // but the URI production does not allow that according to 31 | // http://www.w3.org/TR/css3-syntax/#TOK-URI 32 | // This does not attempt to recognize encoded keywords. For example, 33 | // given "\75\72\6c" and "url" this return false. 34 | return string(bytes.ToLower(b[i:])) == kw 35 | } 36 | 37 | // isCSSNmchar returns whether rune is allowed anywhere in a CSS identifier. 38 | func isCSSNmchar(r rune) bool { 39 | // Based on the CSS3 nmchar production but ignores multi-rune escape 40 | // sequences. 41 | // http://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar 42 | return 'a' <= r && r <= 'z' || 43 | 'A' <= r && r <= 'Z' || 44 | '0' <= r && r <= '9' || 45 | r == '-' || 46 | r == '_' || 47 | // Non-ASCII cases below. 48 | 0x80 <= r && r <= 0xd7ff || 49 | 0xe000 <= r && r <= 0xfffd || 50 | 0x10000 <= r && r <= 0x10ffff 51 | } 52 | 53 | // decodeCSS decodes CSS3 escapes given a sequence of stringchars. 54 | // If there is no change, it returns the input, otherwise it returns a slice 55 | // backed by a new array. 56 | // http://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar. 57 | func decodeCSS(s []byte) []byte { 58 | i := bytes.IndexByte(s, '\\') 59 | if i == -1 { 60 | return s 61 | } 62 | // The UTF-8 sequence for a codepoint is never longer than 1 + the 63 | // number hex digits need to represent that codepoint, so len(s) is an 64 | // upper bound on the output length. 65 | b := make([]byte, 0, len(s)) 66 | for len(s) != 0 { 67 | i := bytes.IndexByte(s, '\\') 68 | if i == -1 { 69 | i = len(s) 70 | } 71 | b, s = append(b, s[:i]...), s[i:] 72 | if len(s) < 2 { 73 | break 74 | } 75 | // http://www.w3.org/TR/css3-syntax/#SUBTOK-escape 76 | // escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF] 77 | if isHex(s[1]) { 78 | // http://www.w3.org/TR/css3-syntax/#SUBTOK-unicode 79 | // unicode ::= '\' [0-9a-fA-F]{1,6} wc? 80 | j := 2 81 | for j < len(s) && j < 7 && isHex(s[j]) { 82 | j++ 83 | } 84 | r := hexDecode(s[1:j]) 85 | if r > unicode.MaxRune { 86 | r, j = r/16, j-1 87 | } 88 | n := utf8.EncodeRune(b[len(b):cap(b)], r) 89 | // The optional space at the end allows a hex 90 | // sequence to be followed by a literal hex. 91 | // string(decodeCSS([]byte(`\A B`))) == "\nB" 92 | b, s = b[:len(b)+n], skipCSSSpace(s[j:]) 93 | } else { 94 | // `\\` decodes to `\` and `\"` to `"`. 95 | _, n := utf8.DecodeRune(s[1:]) 96 | b, s = append(b, s[1:1+n]...), s[1+n:] 97 | } 98 | } 99 | return b 100 | } 101 | 102 | // isHex returns whether the given character is a hex digit. 103 | func isHex(c byte) bool { 104 | return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' 105 | } 106 | 107 | // hexDecode decodes a short hex digit sequence: "10" -> 16. 108 | func hexDecode(s []byte) rune { 109 | n := '\x00' 110 | for _, c := range s { 111 | n <<= 4 112 | switch { 113 | case '0' <= c && c <= '9': 114 | n |= rune(c - '0') 115 | case 'a' <= c && c <= 'f': 116 | n |= rune(c-'a') + 10 117 | case 'A' <= c && c <= 'F': 118 | n |= rune(c-'A') + 10 119 | default: 120 | panic(fmt.Sprintf("Bad hex digit in %q", s)) 121 | } 122 | } 123 | return n 124 | } 125 | 126 | // skipCSSSpace returns a suffix of c, skipping over a single space. 127 | func skipCSSSpace(c []byte) []byte { 128 | if len(c) == 0 { 129 | return c 130 | } 131 | // wc ::= #x9 | #xA | #xC | #xD | #x20 132 | switch c[0] { 133 | case '\t', '\n', '\f', ' ': 134 | return c[1:] 135 | case '\r': 136 | // This differs from CSS3's wc production because it contains a 137 | // probable spec error whereby wc contains all the single byte 138 | // sequences in nl (newline) but not CRLF. 139 | if len(c) >= 2 && c[1] == '\n' { 140 | return c[2:] 141 | } 142 | return c[1:] 143 | } 144 | return c 145 | } 146 | 147 | // isCSSSpace returns whether b is a CSS space char as defined in wc. 148 | func isCSSSpace(b byte) bool { 149 | switch b { 150 | case '\t', '\n', '\f', '\r', ' ': 151 | return true 152 | } 153 | return false 154 | } 155 | 156 | // cssEscaper escapes HTML and CSS special characters using \+ escapes. 157 | func cssEscaper(args ...interface{}) string { 158 | s, _ := stringify(args...) 159 | var b bytes.Buffer 160 | written := 0 161 | for i, r := range s { 162 | var repl string 163 | switch r { 164 | case 0: 165 | repl = `\0` 166 | case '\t': 167 | repl = `\9` 168 | case '\n': 169 | repl = `\a` 170 | case '\f': 171 | repl = `\c` 172 | case '\r': 173 | repl = `\d` 174 | // Encode HTML specials as hex so the output can be embedded 175 | // in HTML attributes without further encoding. 176 | case '"': 177 | repl = `\22` 178 | case '&': 179 | repl = `\26` 180 | case '\'': 181 | repl = `\27` 182 | case '(': 183 | repl = `\28` 184 | case ')': 185 | repl = `\29` 186 | case '+': 187 | repl = `\2b` 188 | case '/': 189 | repl = `\2f` 190 | case ':': 191 | repl = `\3a` 192 | case ';': 193 | repl = `\3b` 194 | case '<': 195 | repl = `\3c` 196 | case '>': 197 | repl = `\3e` 198 | case '\\': 199 | repl = `\\` 200 | case '{': 201 | repl = `\7b` 202 | case '}': 203 | repl = `\7d` 204 | default: 205 | continue 206 | } 207 | b.WriteString(s[written:i]) 208 | b.WriteString(repl) 209 | written = i + utf8.RuneLen(r) 210 | if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) { 211 | b.WriteByte(' ') 212 | } 213 | } 214 | if written == 0 { 215 | return s 216 | } 217 | b.WriteString(s[written:]) 218 | return b.String() 219 | } 220 | 221 | var expressionBytes = []byte("expression") 222 | var mozBindingBytes = []byte("mozbinding") 223 | 224 | // cssValueFilter allows innocuous CSS values in the output including CSS 225 | // quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values 226 | // (inherit, blue), and colors (#888). 227 | // It filters out unsafe values, such as those that affect token boundaries, 228 | // and anything that might execute scripts. 229 | func cssValueFilter(args ...interface{}) string { 230 | s, t := stringify(args...) 231 | if t == contentTypeCSS { 232 | return s 233 | } 234 | b, id := decodeCSS([]byte(s)), make([]byte, 0, 64) 235 | 236 | // CSS3 error handling is specified as honoring string boundaries per 237 | // http://www.w3.org/TR/css3-syntax/#error-handling : 238 | // Malformed declarations. User agents must handle unexpected 239 | // tokens encountered while parsing a declaration by reading until 240 | // the end of the declaration, while observing the rules for 241 | // matching pairs of (), [], {}, "", and '', and correctly handling 242 | // escapes. For example, a malformed declaration may be missing a 243 | // property, colon (:) or value. 244 | // So we need to make sure that values do not have mismatched bracket 245 | // or quote characters to prevent the browser from restarting parsing 246 | // inside a string that might embed JavaScript source. 247 | for i, c := range b { 248 | switch c { 249 | case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}': 250 | return filterFailsafe 251 | case '-': 252 | // Disallow . 253 | // -- should not appear in valid identifiers. 254 | if i != 0 && b[i-1] == '-' { 255 | return filterFailsafe 256 | } 257 | default: 258 | if c < 0x80 && isCSSNmchar(rune(c)) { 259 | id = append(id, c) 260 | } 261 | } 262 | } 263 | id = bytes.ToLower(id) 264 | if bytes.Index(id, expressionBytes) != -1 || bytes.Index(id, mozBindingBytes) != -1 { 265 | return filterFailsafe 266 | } 267 | return string(b) 268 | } 269 | -------------------------------------------------------------------------------- /v0/escape/css_test.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "strconv" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestEndsWithCSSKeyword(t *testing.T) { 14 | tests := []struct { 15 | css, kw string 16 | want bool 17 | }{ 18 | {"", "url", false}, 19 | {"url", "url", true}, 20 | {"URL", "url", true}, 21 | {"Url", "url", true}, 22 | {"url", "important", false}, 23 | {"important", "important", true}, 24 | {"image-url", "url", false}, 25 | {"imageurl", "url", false}, 26 | {"image url", "url", true}, 27 | } 28 | for _, test := range tests { 29 | got := endsWithCSSKeyword([]byte(test.css), test.kw) 30 | if got != test.want { 31 | t.Errorf("want %t but got %t for css=%v, kw=%v", test.want, got, test.css, test.kw) 32 | } 33 | } 34 | } 35 | 36 | func TestIsCSSNmchar(t *testing.T) { 37 | tests := []struct { 38 | rune rune 39 | want bool 40 | }{ 41 | {0, false}, 42 | {'0', true}, 43 | {'9', true}, 44 | {'A', true}, 45 | {'Z', true}, 46 | {'a', true}, 47 | {'z', true}, 48 | {'_', true}, 49 | {'-', true}, 50 | {':', false}, 51 | {';', false}, 52 | {' ', false}, 53 | {0x7f, false}, 54 | {0x80, true}, 55 | {0x1234, true}, 56 | {0xd800, false}, 57 | {0xdc00, false}, 58 | {0xfffe, false}, 59 | {0x10000, true}, 60 | {0x110000, false}, 61 | } 62 | for _, test := range tests { 63 | got := isCSSNmchar(test.rune) 64 | if got != test.want { 65 | t.Errorf("%q: want %t but got %t", string(test.rune), test.want, got) 66 | } 67 | } 68 | } 69 | 70 | func TestDecodeCSS(t *testing.T) { 71 | tests := []struct { 72 | css, want string 73 | }{ 74 | {``, ``}, 75 | {`foo`, `foo`}, 76 | {`foo\`, `foo`}, 77 | {`foo\\`, `foo\`}, 78 | {`\`, ``}, 79 | {`\A`, "\n"}, 80 | {`\a`, "\n"}, 81 | {`\0a`, "\n"}, 82 | {`\00000a`, "\n"}, 83 | {`\000000a`, "\u0000a"}, 84 | {`\1234 5`, "\u1234" + "5"}, 85 | {`\1234\20 5`, "\u1234" + " 5"}, 86 | {`\1234\A 5`, "\u1234" + "\n5"}, 87 | {"\\1234\t5", "\u1234" + "5"}, 88 | {"\\1234\n5", "\u1234" + "5"}, 89 | {"\\1234\r\n5", "\u1234" + "5"}, 90 | {`\12345`, "\U00012345"}, 91 | {`\\`, `\`}, 92 | {`\\ `, `\ `}, 93 | {`\"`, `"`}, 94 | {`\'`, `'`}, 95 | {`\.`, `.`}, 96 | {`\. .`, `. .`}, 97 | { 98 | `The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3e dog\3c/canine\3e`, 99 | "The quick,\r\nbrown fox jumps\u2028over the dog", 100 | }, 101 | } 102 | for _, test := range tests { 103 | got1 := string(decodeCSS([]byte(test.css))) 104 | if got1 != test.want { 105 | t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.css, test.want, got1) 106 | } 107 | recoded := cssEscaper(got1) 108 | if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want { 109 | t.Errorf("%q: escape & decode not dual for %q", test.css, recoded) 110 | } 111 | } 112 | } 113 | 114 | func TestHexDecode(t *testing.T) { 115 | for i := 0; i < 0x200000; i += 101 /* coprime with 16 */ { 116 | s := strconv.FormatInt(int64(i), 16) 117 | if got := int(hexDecode([]byte(s))); got != i { 118 | t.Errorf("%s: want %d but got %d", s, i, got) 119 | } 120 | s = strings.ToUpper(s) 121 | if got := int(hexDecode([]byte(s))); got != i { 122 | t.Errorf("%s: want %d but got %d", s, i, got) 123 | } 124 | } 125 | } 126 | 127 | func TestSkipCSSSpace(t *testing.T) { 128 | tests := []struct { 129 | css, want string 130 | }{ 131 | {"", ""}, 132 | {"foo", "foo"}, 133 | {"\n", ""}, 134 | {"\r\n", ""}, 135 | {"\r", ""}, 136 | {"\t", ""}, 137 | {" ", ""}, 138 | {"\f", ""}, 139 | {" foo", "foo"}, 140 | {" foo", " foo"}, 141 | {`\20`, `\20`}, 142 | } 143 | for _, test := range tests { 144 | got := string(skipCSSSpace([]byte(test.css))) 145 | if got != test.want { 146 | t.Errorf("%q: want %q but got %q", test.css, test.want, got) 147 | } 148 | } 149 | } 150 | 151 | func TestCSSEscaper(t *testing.T) { 152 | input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 153 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 154 | ` !"#$%&'()*+,-./` + 155 | `0123456789:;<=>?` + 156 | `@ABCDEFGHIJKLMNO` + 157 | `PQRSTUVWXYZ[\]^_` + 158 | "`abcdefghijklmno" + 159 | "pqrstuvwxyz{|}~\x7f" + 160 | "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") 161 | 162 | want := ("\\0\x01\x02\x03\x04\x05\x06\x07" + 163 | "\x08\\9 \\a\x0b\\c \\d\x0E\x0F" + 164 | "\x10\x11\x12\x13\x14\x15\x16\x17" + 165 | "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 166 | ` !\22#$%\26\27\28\29*\2b,-.\2f ` + 167 | `0123456789\3a\3b\3c=\3e?` + 168 | `@ABCDEFGHIJKLMNO` + 169 | `PQRSTUVWXYZ[\\]^_` + 170 | "`abcdefghijklmno" + 171 | `pqrstuvwxyz\7b|\7d~` + "\u007f" + 172 | "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") 173 | 174 | got := cssEscaper(input) 175 | if got != want { 176 | t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got) 177 | } 178 | 179 | got = string(decodeCSS([]byte(got))) 180 | if input != got { 181 | t.Errorf("decode: want\n\t%q\nbut got\n\t%q", input, got) 182 | } 183 | } 184 | 185 | func TestCSSValueFilter(t *testing.T) { 186 | tests := []struct { 187 | css, want string 188 | }{ 189 | {"", ""}, 190 | {"foo", "foo"}, 191 | {"0", "0"}, 192 | {"0px", "0px"}, 193 | {"-5px", "-5px"}, 194 | {"1.25in", "1.25in"}, 195 | {"+.33em", "+.33em"}, 196 | {"100%", "100%"}, 197 | {"12.5%", "12.5%"}, 198 | {".foo", ".foo"}, 199 | {"#bar", "#bar"}, 200 | {"corner-radius", "corner-radius"}, 201 | {"-moz-corner-radius", "-moz-corner-radius"}, 202 | {"#000", "#000"}, 203 | {"#48f", "#48f"}, 204 | {"#123456", "#123456"}, 205 | {"U+00-FF, U+980-9FF", "U+00-FF, U+980-9FF"}, 206 | {"color: red", "color: red"}, 207 | {"", "ZgotmplZ"}, 209 | {"", "ZgotmplZ"}, 211 | {"quick,\r\nbrown fox jumps\u2028over the dog") 246 | } 247 | } 248 | 249 | func BenchmarkCSSEscaperNoSpecials(b *testing.B) { 250 | for i := 0; i < b.N; i++ { 251 | cssEscaper("The quick, brown fox jumps over the lazy dog.") 252 | } 253 | } 254 | 255 | func BenchmarkDecodeCSS(b *testing.B) { 256 | s := []byte(`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3edog\3c/canine\3e`) 257 | b.ResetTimer() 258 | for i := 0; i < b.N; i++ { 259 | decodeCSS(s) 260 | } 261 | } 262 | 263 | func BenchmarkDecodeCSSNoSpecials(b *testing.B) { 264 | s := []byte("The quick, brown fox jumps over the lazy dog.") 265 | b.ResetTimer() 266 | for i := 0; i < b.N; i++ { 267 | decodeCSS(s) 268 | } 269 | } 270 | 271 | func BenchmarkCSSValueFilter(b *testing.B) { 272 | for i := 0; i < b.N; i++ { 273 | cssValueFilter(` e\78preS\0Sio/**/n(alert(1337))`) 274 | } 275 | } 276 | 277 | func BenchmarkCSSValueFilterOk(b *testing.B) { 278 | for i := 0; i < b.N; i++ { 279 | cssValueFilter(`Times New Roman`) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /v0/escape/html.go: -------------------------------------------------------------------------------- 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 escape 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | "unicode/utf8" 12 | ) 13 | 14 | // htmlNospaceEscaper escapes for inclusion in unquoted attribute values. 15 | func htmlNospaceEscaper(args ...interface{}) string { 16 | s, t := stringify(args...) 17 | if t == contentTypeHTML { 18 | return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false) 19 | } 20 | return htmlReplacer(s, htmlNospaceReplacementTable, false) 21 | } 22 | 23 | // attrEscaper escapes for inclusion in quoted attribute values. 24 | func attrEscaper(args ...interface{}) string { 25 | s, t := stringify(args...) 26 | if t == contentTypeHTML { 27 | return htmlReplacer(stripTags(s), htmlNormReplacementTable, true) 28 | } 29 | return htmlReplacer(s, htmlReplacementTable, true) 30 | } 31 | 32 | // rcdataEscaper escapes for inclusion in an RCDATA element body. 33 | func rcdataEscaper(args ...interface{}) string { 34 | s, t := stringify(args...) 35 | if t == contentTypeHTML { 36 | return htmlReplacer(s, htmlNormReplacementTable, true) 37 | } 38 | return htmlReplacer(s, htmlReplacementTable, true) 39 | } 40 | 41 | // htmlEscaper escapes for inclusion in HTML text. 42 | func htmlEscaper(args ...interface{}) string { 43 | s, t := stringify(args...) 44 | if t == contentTypeHTML { 45 | return s 46 | } 47 | return htmlReplacer(s, htmlReplacementTable, true) 48 | } 49 | 50 | // htmlReplacementTable contains the runes that need to be escaped 51 | // inside a quoted attribute value or in a text node. 52 | var htmlReplacementTable = []string{ 53 | // http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state: " 54 | // U+0000 NULL Parse error. Append a U+FFFD REPLACEMENT 55 | // CHARACTER character to the current attribute's value. 56 | // " 57 | // and similarly 58 | // http://www.w3.org/TR/html5/tokenization.html#before-attribute-value-state 59 | 0: "\uFFFD", 60 | '"': """, 61 | '&': "&", 62 | '\'': "'", 63 | '+': "+", 64 | '<': "<", 65 | '>': ">", 66 | } 67 | 68 | // htmlNormReplacementTable is like htmlReplacementTable but without '&' to 69 | // avoid over-encoding existing entities. 70 | var htmlNormReplacementTable = []string{ 71 | 0: "\uFFFD", 72 | '"': """, 73 | '\'': "'", 74 | '+': "+", 75 | '<': "<", 76 | '>': ">", 77 | } 78 | 79 | // htmlNospaceReplacementTable contains the runes that need to be escaped 80 | // inside an unquoted attribute value. 81 | // The set of runes escaped is the union of the HTML specials and 82 | // those determined by running the JS below in browsers: 83 | //
84 | // 94 | var htmlNospaceReplacementTable = []string{ 95 | 0: "�", 96 | '\t': " ", 97 | '\n': " ", 98 | '\v': " ", 99 | '\f': " ", 100 | '\r': " ", 101 | ' ': " ", 102 | '"': """, 103 | '&': "&", 104 | '\'': "'", 105 | '+': "+", 106 | '<': "<", 107 | '=': "=", 108 | '>': ">", 109 | // A parse error in the attribute value (unquoted) and 110 | // before attribute value states. 111 | // Treated as a quoting character by IE. 112 | '`': "`", 113 | } 114 | 115 | // htmlNospaceNormReplacementTable is like htmlNospaceReplacementTable but 116 | // without '&' to avoid over-encoding existing entities. 117 | var htmlNospaceNormReplacementTable = []string{ 118 | 0: "�", 119 | '\t': " ", 120 | '\n': " ", 121 | '\v': " ", 122 | '\f': " ", 123 | '\r': " ", 124 | ' ': " ", 125 | '"': """, 126 | '\'': "'", 127 | '+': "+", 128 | '<': "<", 129 | '=': "=", 130 | '>': ">", 131 | // A parse error in the attribute value (unquoted) and 132 | // before attribute value states. 133 | // Treated as a quoting character by IE. 134 | '`': "`", 135 | } 136 | 137 | // htmlReplacer returns s with runes replaced according to replacementTable 138 | // and when badRunes is true, certain bad runes are allowed through unescaped. 139 | func htmlReplacer(s string, replacementTable []string, badRunes bool) string { 140 | written, b := 0, new(bytes.Buffer) 141 | for i, r := range s { 142 | if int(r) < len(replacementTable) { 143 | if repl := replacementTable[r]; len(repl) != 0 { 144 | b.WriteString(s[written:i]) 145 | b.WriteString(repl) 146 | // Valid as long as replacementTable doesn't 147 | // include anything above 0x7f. 148 | written = i + utf8.RuneLen(r) 149 | } 150 | } else if badRunes { 151 | // No-op. 152 | // IE does not allow these ranges in unquoted attrs. 153 | } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff { 154 | fmt.Fprintf(b, "%s&#x%x;", s[written:i], r) 155 | written = i + utf8.RuneLen(r) 156 | } 157 | } 158 | if written == 0 { 159 | return s 160 | } 161 | b.WriteString(s[written:]) 162 | return b.String() 163 | } 164 | 165 | // stripTags takes a snippet of HTML and returns only the text content. 166 | // For example, `¡Hi! ` -> `¡Hi! `. 167 | func stripTags(html string) string { 168 | var b bytes.Buffer 169 | s, c, i, allText := []byte(html), context{}, 0, true 170 | // Using the transition funcs helps us avoid mangling 171 | // `
` or `I <3 Ponies!`. 172 | for i != len(s) { 173 | if c.delim == delimNone { 174 | st := c.state 175 | // Use RCDATA instead of parsing into JS or CSS styles. 176 | if c.element != elementNone && !isInTag(st) { 177 | st = stateRCDATA 178 | } 179 | d, nread := transitionFunc[st](c, s[i:]) 180 | i1 := i + nread 181 | if c.state == stateText || c.state == stateRCDATA { 182 | // Emit text up to the start of the tag or comment. 183 | j := i1 184 | if d.state != c.state { 185 | for j1 := j - 1; j1 >= i; j1-- { 186 | if s[j1] == '<' { 187 | j = j1 188 | break 189 | } 190 | } 191 | } 192 | b.Write(s[i:j]) 193 | } else { 194 | allText = false 195 | } 196 | c, i = d, i1 197 | continue 198 | } 199 | i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim]) 200 | if i1 < i { 201 | break 202 | } 203 | if c.delim != delimSpaceOrTagEnd { 204 | // Consume any quote. 205 | i1++ 206 | } 207 | c, i = context{state: stateTag, element: c.element}, i1 208 | } 209 | if allText { 210 | return html 211 | } else if c.state == stateText || c.state == stateRCDATA { 212 | b.Write(s[i:]) 213 | } 214 | return b.String() 215 | } 216 | 217 | // htmlNameFilter accepts valid parts of an HTML attribute or tag name or 218 | // a known-safe HTML attribute. 219 | func htmlNameFilter(args ...interface{}) string { 220 | s, t := stringify(args...) 221 | if t == contentTypeHTMLAttr { 222 | return s 223 | } 224 | if len(s) == 0 { 225 | // Avoid violation of structure preservation. 226 | // . 227 | // Without this, if .K is empty then .V is the value of 228 | // checked, but otherwise .V is the value of the attribute 229 | // named .K. 230 | return filterFailsafe 231 | } 232 | s = strings.ToLower(s) 233 | if t := attrType(s); t != contentTypePlain { 234 | // TODO: Split attr and element name part filters so we can whitelist 235 | // attributes. 236 | return filterFailsafe 237 | } 238 | for _, r := range s { 239 | switch { 240 | case '0' <= r && r <= '9': 241 | case 'a' <= r && r <= 'z': 242 | default: 243 | return filterFailsafe 244 | } 245 | } 246 | return s 247 | } 248 | 249 | // commentEscaper returns the empty string regardless of input. 250 | // Comment content does not correspond to any parsed structure or 251 | // human-readable content, so the simplest and most secure policy is to drop 252 | // content interpolated into comments. 253 | // This approach is equally valid whether or not static comment content is 254 | // removed from the template. 255 | func commentEscaper(args ...interface{}) string { 256 | return "" 257 | } 258 | -------------------------------------------------------------------------------- /v0/multi_test.go: -------------------------------------------------------------------------------- 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 | // Tests for mulitple-template parsing and execution. 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/gorilla/template/v0/parse" 16 | ) 17 | 18 | const ( 19 | noError = true 20 | hasError = false 21 | ) 22 | 23 | type multiParseTest struct { 24 | name string 25 | input string 26 | ok bool 27 | names []string 28 | results []string 29 | } 30 | 31 | var multiParseTests = []multiParseTest{ 32 | {"empty", "", noError, 33 | nil, 34 | nil}, 35 | {"one", `{{define "foo"}} FOO {{end}}`, noError, 36 | []string{"foo"}, 37 | []string{`{{define "foo"}} FOO {{end}}`}}, 38 | {"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError, 39 | []string{"foo", "bar"}, 40 | []string{`{{define "foo"}} FOO {{end}}`, `{{define "bar"}} BAR {{end}}`}}, 41 | // errors 42 | {"missing end", `{{define "foo"}} FOO `, hasError, 43 | nil, 44 | nil}, 45 | {"malformed name", `{{define "foo}} FOO `, hasError, 46 | nil, 47 | nil}, 48 | } 49 | 50 | func TestMultiParse(t *testing.T) { 51 | for _, test := range multiParseTests { 52 | template, err := new(Set).Parse(test.input) 53 | switch { 54 | case err == nil && !test.ok: 55 | t.Errorf("%q: expected error; got none", test.name) 56 | continue 57 | case err != nil && test.ok: 58 | t.Errorf("%q: unexpected error: %v", test.name, err) 59 | continue 60 | case err != nil && !test.ok: 61 | // expected error, got one 62 | if *debug { 63 | fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err) 64 | } 65 | continue 66 | } 67 | if template == nil { 68 | continue 69 | } 70 | if len(template.tree) != len(test.names) { 71 | t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tree)) 72 | continue 73 | } 74 | for i, name := range test.names { 75 | tmpl, ok := template.tree[name] 76 | if !ok { 77 | t.Errorf("%s: can't find template %q", test.name, name) 78 | continue 79 | } 80 | result := tmpl.String() 81 | if result != test.results[i] { 82 | t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i]) 83 | } 84 | } 85 | } 86 | } 87 | 88 | var multiExecTests = []execTest{ 89 | {"empty", "", "", nil, true}, 90 | {"text", "some text", "some text", nil, true}, 91 | {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true}, 92 | {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true}, 93 | {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true}, 94 | {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, 95 | {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, 96 | {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true}, 97 | {"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true}, 98 | 99 | // User-defined function: test argument evaluator. 100 | {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, 101 | {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true}, 102 | } 103 | 104 | // These strings are also in testdata/*. 105 | const multiText1 = ` 106 | {{define "x"}}TEXT{{end}} 107 | {{define "dotV"}}{{.V}}{{end}} 108 | ` 109 | 110 | const multiText2 = ` 111 | {{define "dot"}}{{.}}{{end}} 112 | {{define "nested"}}{{template "dot" .}}{{end}} 113 | ` 114 | 115 | func TestMultiExecute(t *testing.T) { 116 | // Declare a couple of templates first. 117 | set, err := new(Set).Parse(multiText1) 118 | if err != nil { 119 | t.Fatalf("parse error for 1: %s", err) 120 | } 121 | _, err = set.Parse(multiText2) 122 | if err != nil { 123 | t.Fatalf("parse error for 2: %s", err) 124 | } 125 | testExecute(multiExecTests, set, t) 126 | } 127 | 128 | func TestParseFiles(t *testing.T) { 129 | _, err := new(Set).ParseFiles("DOES NOT EXIST") 130 | if err == nil { 131 | t.Error("expected error for non-existent file; got none") 132 | } 133 | template := new(Set) 134 | _, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl") 135 | if err != nil { 136 | t.Fatalf("error parsing files: %v", err) 137 | } 138 | testExecute(multiExecTests, template, t) 139 | } 140 | 141 | func TestParseGlob(t *testing.T) { 142 | _, err := new(Set).ParseGlob("DOES NOT EXIST") 143 | if err == nil { 144 | t.Error("expected error for non-existent file; got none") 145 | } 146 | _, err = new(Set).ParseGlob("[x") 147 | if err == nil { 148 | t.Error("expected error for bad pattern; got none") 149 | } 150 | template := new(Set) 151 | _, err = template.ParseGlob("testdata/file*.tmpl") 152 | if err != nil { 153 | t.Fatalf("error parsing files: %v", err) 154 | } 155 | testExecute(multiExecTests, template, t) 156 | } 157 | 158 | // In these tests, actual content (not just template definitions) comes from the parsed files. 159 | 160 | var templateFileExecTests = []execTest{ 161 | {"test", `{{define "test"}}{{template "template1"}}{{template "template2"}}{{end}}`, "\ntemplate1\ny\n\ntemplate2\nx\n", 0, true}, 162 | } 163 | 164 | func TestParseFilesWithData(t *testing.T) { 165 | template, err := new(Set).ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl") 166 | if err != nil { 167 | t.Fatalf("error parsing files: %v", err) 168 | } 169 | testExecute(templateFileExecTests, template, t) 170 | } 171 | 172 | func TestParseGlobWithData(t *testing.T) { 173 | template, err := new(Set).ParseGlob("testdata/tmpl*.tmpl") 174 | if err != nil { 175 | t.Fatalf("error parsing files: %v", err) 176 | } 177 | testExecute(templateFileExecTests, template, t) 178 | } 179 | 180 | const ( 181 | cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` 182 | cloneText2 = `{{define "b"}}b{{end}}` 183 | cloneText3 = `{{define "c"}}root{{end}}` 184 | cloneText4 = `{{define "c"}}clone{{end}}` 185 | ) 186 | 187 | func TestClone(t *testing.T) { 188 | // Create some templates and clone the root. 189 | root, err := new(Set).Parse(cloneText1) 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | _, err = root.Parse(cloneText2) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | clone := Must(root.Clone()) 198 | // Add variants to both. 199 | _, err = root.Parse(cloneText3) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | _, err = clone.Parse(cloneText4) 204 | if err != nil { 205 | t.Fatal(err) 206 | } 207 | // Execute root. 208 | var b bytes.Buffer 209 | err = root.Execute(&b, "a", 0) 210 | if err != nil { 211 | t.Fatal(err) 212 | } 213 | if b.String() != "broot" { 214 | t.Errorf("expected %q got %q", "broot", b.String()) 215 | } 216 | // Execute copy. 217 | b.Reset() 218 | err = clone.Execute(&b, "a", 0) 219 | if err != nil { 220 | t.Fatal(err) 221 | } 222 | if b.String() != "bclone" { 223 | t.Errorf("expected %q got %q", "bclone", b.String()) 224 | } 225 | } 226 | 227 | func TestAddParseTree(t *testing.T) { 228 | // Create some templates. 229 | root, err := new(Set).Parse(cloneText1) 230 | if err != nil { 231 | t.Fatal(err) 232 | } 233 | _, err = root.Parse(cloneText2) 234 | if err != nil { 235 | t.Fatal(err) 236 | } 237 | // Add a new parse tree. 238 | tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins) 239 | if err != nil { 240 | t.Fatal(err) 241 | } 242 | err = root.tree.Add(tree["c"]) 243 | if err != nil { 244 | t.Fatal(err) 245 | } 246 | // Execute. 247 | var b bytes.Buffer 248 | err = root.Execute(&b, "a", 0) 249 | if err != nil { 250 | t.Fatal(err) 251 | } 252 | if b.String() != "broot" { 253 | t.Errorf("expected %q got %q", "broot", b.String()) 254 | } 255 | } 256 | 257 | func TestRedefinition(t *testing.T) { 258 | var tmpl *Set 259 | var err error 260 | if tmpl, err = new(Set).Parse(`{{define "test"}}foo{{end}}`); err != nil { 261 | t.Fatalf("parse 1: %v", err) 262 | } 263 | if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err == nil { 264 | t.Fatal("expected error") 265 | } 266 | if !strings.Contains(err.Error(), "duplicated template name") { 267 | t.Fatalf("expected redefinition error; got %v", err) 268 | } 269 | if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err == nil { 270 | t.Fatal("expected error") 271 | } 272 | if !strings.Contains(err.Error(), "duplicated template name") { 273 | t.Fatalf("expected redefinition error; got %v", err) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /v0/content_test.go: -------------------------------------------------------------------------------- 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 | "github.com/gorilla/template/v0/escape" 14 | ) 15 | 16 | func TestTypedContent(t *testing.T) { 17 | data := []interface{}{ 18 | ` "foo%" O'Reilly &bar;`, 19 | escape.CSS(`a[href =~ "//example.com"]#foo`), 20 | escape.HTML(`Hello, World &tc!`), 21 | escape.HTMLAttr(` dir="ltr"`), 22 | escape.JS(`c && alert("Hello, World!");`), 23 | escape.JSStr(`Hello, World & O'Reilly\x21`), 24 | escape.URL(`greeting=H%69&addressee=(World)`), 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 | ``, 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 | }, 46 | }, 47 | { 48 | `
`, 49 | []string{ 50 | `ZgotmplZ`, 51 | // Allowed and HTML escaped. 52 | `a[href =~ "//example.com"]#foo`, 53 | `ZgotmplZ`, 54 | `ZgotmplZ`, 55 | `ZgotmplZ`, 56 | `ZgotmplZ`, 57 | `ZgotmplZ`, 58 | }, 59 | }, 60 | { 61 | `{{.}}`, 62 | []string{ 63 | `<b> "foo%" O'Reilly &bar;`, 64 | `a[href =~ "//example.com"]#foo`, 65 | // Not escaped. 66 | `Hello, World &tc!`, 67 | ` dir="ltr"`, 68 | `c && alert("Hello, World!");`, 69 | `Hello, World & O'Reilly\x21`, 70 | `greeting=H%69&addressee=(World)`, 71 | }, 72 | }, 73 | { 74 | ``, 75 | []string{ 76 | `ZgotmplZ`, 77 | `ZgotmplZ`, 78 | `ZgotmplZ`, 79 | // Allowed and HTML escaped. 80 | ` dir="ltr"`, 81 | `ZgotmplZ`, 82 | `ZgotmplZ`, 83 | `ZgotmplZ`, 84 | }, 85 | }, 86 | { 87 | ``, 88 | []string{ 89 | `<b> "foo%" O'Reilly &bar;`, 90 | `a[href =~ "//example.com"]#foo`, 91 | // Tags stripped, spaces escaped, entity not re-escaped. 92 | `Hello, World &tc!`, 93 | ` dir="ltr"`, 94 | `c && alert("Hello, World!");`, 95 | `Hello, World & O'Reilly\x21`, 96 | `greeting=H%69&addressee=(World)`, 97 | }, 98 | }, 99 | { 100 | ``, 101 | []string{ 102 | `<b> "foo%" O'Reilly &bar;`, 103 | `a[href =~ "//example.com"]#foo`, 104 | // Tags stripped, entity not re-escaped. 105 | `Hello, World &tc!`, 106 | ` dir="ltr"`, 107 | `c && alert("Hello, World!");`, 108 | `Hello, World & O'Reilly\x21`, 109 | `greeting=H%69&addressee=(World)`, 110 | }, 111 | }, 112 | { 113 | ``, 114 | []string{ 115 | `<b> "foo%" O'Reilly &bar;`, 116 | `a[href =~ "//example.com"]#foo`, 117 | // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. 118 | `Hello, <b>World</b> &tc!`, 119 | ` dir="ltr"`, 120 | `c && alert("Hello, World!");`, 121 | `Hello, World & O'Reilly\x21`, 122 | `greeting=H%69&addressee=(World)`, 123 | }, 124 | }, 125 | { 126 | ``, 127 | []string{ 128 | `"\u003cb\u003e \"foo%\" O'Reilly &bar;"`, 129 | `"a[href =~ \"//example.com\"]#foo"`, 130 | `"Hello, \u003cb\u003eWorld\u003c/b\u003e &tc!"`, 131 | `" dir=\"ltr\""`, 132 | // Not escaped. 133 | `c && alert("Hello, World!");`, 134 | // Escape sequence not over-escaped. 135 | `"Hello, World & O'Reilly\x21"`, 136 | `"greeting=H%69&addressee=(World)"`, 137 | }, 138 | }, 139 | { 140 | `