├── 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\n
brown 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\n
brown 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 | //