diff options
author | Ian Lance Taylor <iant@golang.org> | 2021-02-02 12:42:10 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2021-02-02 12:42:10 -0800 |
commit | 8910f1cd79445bbe2da01f8ccf7c37909349529e (patch) | |
tree | ba67a346969358fd7cc2b7c12384479de8364cab /libgo/go/html | |
parent | 45c32be1f96ace25b66c34a84818dc5e07e9d516 (diff) | |
parent | 8e4a738d2540ab6aff77506d368bf4e3fa6963bd (diff) | |
download | gcc-8910f1cd79445bbe2da01f8ccf7c37909349529e.zip gcc-8910f1cd79445bbe2da01f8ccf7c37909349529e.tar.gz gcc-8910f1cd79445bbe2da01f8ccf7c37909349529e.tar.bz2 |
Merge from trunk revision 8e4a738d2540ab6aff77506d368bf4e3fa6963bd.
Diffstat (limited to 'libgo/go/html')
-rw-r--r-- | libgo/go/html/template/clone_test.go | 14 | ||||
-rw-r--r-- | libgo/go/html/template/escape.go | 2 | ||||
-rw-r--r-- | libgo/go/html/template/examplefiles_test.go | 3 | ||||
-rw-r--r-- | libgo/go/html/template/exec_test.go | 135 | ||||
-rw-r--r-- | libgo/go/html/template/multi_test.go | 43 | ||||
-rw-r--r-- | libgo/go/html/template/template.go | 60 | ||||
-rw-r--r-- | libgo/go/html/template/template_test.go | 16 | ||||
-rw-r--r-- | libgo/go/html/template/testdata/fs.zip | bin | 0 -> 406 bytes |
8 files changed, 252 insertions, 21 deletions
diff --git a/libgo/go/html/template/clone_test.go b/libgo/go/html/template/clone_test.go index c9c619f..7cb1b9c 100644 --- a/libgo/go/html/template/clone_test.go +++ b/libgo/go/html/template/clone_test.go @@ -8,7 +8,7 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "strings" "sync" "testing" @@ -171,7 +171,7 @@ func TestCloneThenParse(t *testing.T) { t.Error("adding a template to a clone added it to the original") } // double check that the embedded template isn't available in the original - err := t0.ExecuteTemplate(ioutil.Discard, "a", nil) + err := t0.ExecuteTemplate(io.Discard, "a", nil) if err == nil { t.Error("expected 'no such template' error") } @@ -185,13 +185,13 @@ func TestFuncMapWorksAfterClone(t *testing.T) { // get the expected error output (no clone) uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - wantErr := uncloned.Execute(ioutil.Discard, nil) + wantErr := uncloned.Execute(io.Discard, nil) // toClone must be the same as uncloned. It has to be recreated from scratch, // since cloning cannot occur after execution. toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) cloned := Must(toClone.Clone()) - gotErr := cloned.Execute(ioutil.Discard, nil) + gotErr := cloned.Execute(io.Discard, nil) if wantErr.Error() != gotErr.Error() { t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) @@ -213,7 +213,7 @@ func TestTemplateCloneExecuteRace(t *testing.T) { go func() { defer wg.Done() for i := 0; i < 100; i++ { - if err := tmpl.Execute(ioutil.Discard, "data"); err != nil { + if err := tmpl.Execute(io.Discard, "data"); err != nil { panic(err) } } @@ -237,7 +237,7 @@ func TestCloneGrowth(t *testing.T) { tmpl = Must(tmpl.Clone()) Must(tmpl.Parse(`{{define "B"}}Text{{end}}`)) for i := 0; i < 10; i++ { - tmpl.Execute(ioutil.Discard, nil) + tmpl.Execute(io.Discard, nil) } if len(tmpl.DefinedTemplates()) > 200 { t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates())) @@ -257,7 +257,7 @@ func TestCloneRedefinedName(t *testing.T) { for i := 0; i < 2; i++ { t2 := Must(t1.Clone()) t2 = Must(t2.New(fmt.Sprintf("%d", i)).Parse(page)) - err := t2.Execute(ioutil.Discard, nil) + err := t2.Execute(io.Discard, nil) if err != nil { t.Fatal(err) } diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go index f12dafa..8739735 100644 --- a/libgo/go/html/template/escape.go +++ b/libgo/go/html/template/escape.go @@ -124,6 +124,8 @@ func (e *escaper) escape(c context, n parse.Node) context { switch n := n.(type) { case *parse.ActionNode: return e.escapeAction(c, n) + case *parse.CommentNode: + return c case *parse.IfNode: return e.escapeBranch(c, &n.BranchNode, "if") case *parse.ListNode: diff --git a/libgo/go/html/template/examplefiles_test.go b/libgo/go/html/template/examplefiles_test.go index 60518ae..5eb2597 100644 --- a/libgo/go/html/template/examplefiles_test.go +++ b/libgo/go/html/template/examplefiles_test.go @@ -6,7 +6,6 @@ package template_test import ( "io" - "io/ioutil" "log" "os" "path/filepath" @@ -20,7 +19,7 @@ type templateFile struct { } func createTestDir(files []templateFile) string { - dir, err := ioutil.TempDir("", "template") + dir, err := os.MkdirTemp("", "template") if err != nil { log.Fatal(err) } diff --git a/libgo/go/html/template/exec_test.go b/libgo/go/html/template/exec_test.go index ec2bfcc..7d1bef1 100644 --- a/libgo/go/html/template/exec_test.go +++ b/libgo/go/html/template/exec_test.go @@ -11,9 +11,10 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" "reflect" "strings" + "sync" "testing" "text/template" ) @@ -1302,7 +1303,7 @@ func TestUnterminatedStringError(t *testing.T) { t.Fatal("expected error") } str := err.Error() - if !strings.Contains(str, "X:3: unexpected unterminated raw quoted string") { + if !strings.Contains(str, "X:3: unterminated raw quoted string") { t.Fatalf("unexpected error: %s", str) } } @@ -1335,7 +1336,7 @@ func TestExecuteGivesExecError(t *testing.T) { if err != nil { t.Fatal(err) } - err = tmpl.Execute(ioutil.Discard, 0) + err = tmpl.Execute(io.Discard, 0) if err == nil { t.Fatal("expected error; got none") } @@ -1481,7 +1482,7 @@ func TestEvalFieldErrors(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { tmpl := Must(New("tmpl").Parse(tc.src)) - err := tmpl.Execute(ioutil.Discard, tc.value) + err := tmpl.Execute(io.Discard, tc.value) got := "<nil>" if err != nil { got = err.Error() @@ -1498,7 +1499,7 @@ func TestMaxExecDepth(t *testing.T) { t.Skip("skipping in -short mode") } tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`)) - err := tmpl.Execute(ioutil.Discard, nil) + err := tmpl.Execute(io.Discard, nil) got := "<nil>" if err != nil { got = err.Error() @@ -1706,3 +1707,127 @@ func TestIssue31810(t *testing.T) { t.Errorf("%s got %q, expected %q", textCall, b.String(), "result") } } + +// Issue 39807. There was a race applying escapeTemplate. + +const raceText = ` +{{- define "jstempl" -}} +var v = "v"; +{{- end -}} +<script type="application/javascript"> +{{ template "jstempl" $ }} +</script> +` + +func TestEscapeRace(t *testing.T) { + t.Skip("this test currently fails with -race; see issue #39807") + + tmpl := New("") + _, err := tmpl.New("templ.html").Parse(raceText) + if err != nil { + t.Fatal(err) + } + const count = 20 + for i := 0; i < count; i++ { + _, err := tmpl.New(fmt.Sprintf("x%d.html", i)).Parse(`{{ template "templ.html" .}}`) + if err != nil { + t.Fatal(err) + } + } + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < count; j++ { + sub := tmpl.Lookup(fmt.Sprintf("x%d.html", j)) + if err := sub.Execute(io.Discard, nil); err != nil { + t.Error(err) + } + } + }() + } + wg.Wait() +} + +func TestRecursiveExecute(t *testing.T) { + tmpl := New("") + + recur := func() (HTML, error) { + var sb strings.Builder + if err := tmpl.ExecuteTemplate(&sb, "subroutine", nil); err != nil { + t.Fatal(err) + } + return HTML(sb.String()), nil + } + + m := FuncMap{ + "recur": recur, + } + + top, err := tmpl.New("x.html").Funcs(m).Parse(`{{recur}}`) + if err != nil { + t.Fatal(err) + } + _, err = tmpl.New("subroutine").Parse(`<a href="/x?p={{"'a<b'"}}">`) + if err != nil { + t.Fatal(err) + } + if err := top.Execute(io.Discard, nil); err != nil { + t.Fatal(err) + } +} + +// recursiveInvoker is for TestRecursiveExecuteViaMethod. +type recursiveInvoker struct { + t *testing.T + tmpl *Template +} + +func (r *recursiveInvoker) Recur() (string, error) { + var sb strings.Builder + if err := r.tmpl.ExecuteTemplate(&sb, "subroutine", nil); err != nil { + r.t.Fatal(err) + } + return sb.String(), nil +} + +func TestRecursiveExecuteViaMethod(t *testing.T) { + tmpl := New("") + top, err := tmpl.New("x.html").Parse(`{{.Recur}}`) + if err != nil { + t.Fatal(err) + } + _, err = tmpl.New("subroutine").Parse(`<a href="/x?p={{"'a<b'"}}">`) + if err != nil { + t.Fatal(err) + } + r := &recursiveInvoker{ + t: t, + tmpl: tmpl, + } + if err := top.Execute(io.Discard, r); err != nil { + t.Fatal(err) + } +} + +// Issue 43295. +func TestTemplateFuncsAfterClone(t *testing.T) { + s := `{{ f . }}` + want := "test" + orig := New("orig").Funcs(map[string]interface{}{ + "f": func(in string) string { + return in + }, + }).New("child") + + overviewTmpl := Must(Must(orig.Clone()).Parse(s)) + var out strings.Builder + if err := overviewTmpl.Execute(&out, want); err != nil { + t.Fatal(err) + } + if got := out.String(); got != want { + t.Fatalf("got %q; want %q", got, want) + } +} diff --git a/libgo/go/html/template/multi_test.go b/libgo/go/html/template/multi_test.go index 50526c5..6535ab6 100644 --- a/libgo/go/html/template/multi_test.go +++ b/libgo/go/html/template/multi_test.go @@ -7,7 +7,9 @@ package template import ( + "archive/zip" "bytes" + "os" "testing" "text/template/parse" ) @@ -82,6 +84,35 @@ func TestParseGlob(t *testing.T) { testExecute(multiExecTests, template, t) } +func TestParseFS(t *testing.T) { + fs := os.DirFS("testdata") + + { + _, err := ParseFS(fs, "DOES NOT EXIST") + if err == nil { + t.Error("expected error for non-existent file; got none") + } + } + + { + template := New("root") + _, err := template.ParseFS(fs, "file1.tmpl", "file2.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) + } + + { + template := New("root") + _, err := template.ParseFS(fs, "file*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) + } +} + // In these tests, actual content (not just template definitions) comes from the parsed files. var templateFileExecTests = []execTest{ @@ -104,6 +135,18 @@ func TestParseGlobWithData(t *testing.T) { testExecute(templateFileExecTests, template, t) } +func TestParseZipFS(t *testing.T) { + z, err := zip.OpenReader("testdata/fs.zip") + if err != nil { + t.Fatalf("error parsing zip: %v", err) + } + template, err := New("root").ParseFS(z, "tmpl*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(templateFileExecTests, template, t) +} + const ( cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` cloneText2 = `{{define "b"}}b{{end}}` diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go index 7543787..69312d3 100644 --- a/libgo/go/html/template/template.go +++ b/libgo/go/html/template/template.go @@ -7,7 +7,9 @@ package template import ( "fmt" "io" - "io/ioutil" + "io/fs" + "os" + "path" "path/filepath" "sync" "text/template" @@ -384,7 +386,7 @@ func Must(t *Template, err error) *Template { // For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template // named "foo", while "a/foo" is unavailable. func ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(nil, filenames...) + return parseFiles(nil, readFileOS, filenames...) } // ParseFiles parses the named files and associates the resulting templates with @@ -396,12 +398,12 @@ func ParseFiles(filenames ...string) (*Template, error) { // // ParseFiles returns an error if t or any associated template has already been executed. func (t *Template) ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(t, filenames...) + return parseFiles(t, readFileOS, filenames...) } // parseFiles is the helper for the method and function. If the argument // template is nil, it is created from the first file. -func parseFiles(t *Template, filenames ...string) (*Template, error) { +func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) { if err := t.checkCanParse(); err != nil { return nil, err } @@ -411,12 +413,11 @@ func parseFiles(t *Template, filenames ...string) (*Template, error) { return nil, fmt.Errorf("html/template: no files named in call to ParseFiles") } for _, filename := range filenames { - b, err := ioutil.ReadFile(filename) + name, b, err := readFile(filename) if err != nil { return nil, err } s := string(b) - name := filepath.Base(filename) // First template becomes return value if not already defined, // and we use that one for subsequent New calls to associate // all the templates together. Also, if this file has the same name @@ -479,7 +480,7 @@ func parseGlob(t *Template, pattern string) (*Template, error) { if len(filenames) == 0 { return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern) } - return parseFiles(t, filenames...) + return parseFiles(t, readFileOS, filenames...) } // IsTrue reports whether the value is 'true', in the sense of not the zero of its type, @@ -488,3 +489,48 @@ func parseGlob(t *Template, pattern string) (*Template, error) { func IsTrue(val interface{}) (truth, ok bool) { return template.IsTrue(val) } + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func ParseFS(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(nil, fs, patterns) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(t, fs, patterns) +} + +func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) { + var filenames []string + for _, pattern := range patterns { + list, err := fs.Glob(fsys, pattern) + if err != nil { + return nil, err + } + if len(list) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + filenames = append(filenames, list...) + } + return parseFiles(t, readFileFS(fsys), filenames...) +} + +func readFileOS(file string) (name string, b []byte, err error) { + name = filepath.Base(file) + b, err = os.ReadFile(file) + return +} + +func readFileFS(fsys fs.FS) func(string) (string, []byte, error) { + return func(file string) (name string, b []byte, err error) { + name = path.Base(file) + b, err = fs.ReadFile(fsys, file) + return + } +} diff --git a/libgo/go/html/template/template_test.go b/libgo/go/html/template/template_test.go index 86bd4db..1f2c888 100644 --- a/libgo/go/html/template/template_test.go +++ b/libgo/go/html/template/template_test.go @@ -10,6 +10,7 @@ import ( . "html/template" "strings" "testing" + "text/template/parse" ) func TestTemplateClone(t *testing.T) { @@ -160,6 +161,21 @@ func TestStringsInScriptsWithJsonContentTypeAreCorrectlyEscaped(t *testing.T) { } } +func TestSkipEscapeComments(t *testing.T) { + c := newTestCase(t) + tr := parse.New("root") + tr.Mode = parse.ParseComments + newT, err := tr.Parse("{{/* A comment */}}{{ 1 }}{{/* Another comment */}}", "", "", make(map[string]*parse.Tree)) + if err != nil { + t.Fatalf("Cannot parse template text: %v", err) + } + c.root, err = c.root.AddParseTree("root", newT) + if err != nil { + t.Fatalf("Cannot add parse tree to template: %v", err) + } + c.mustExecute(c.root, nil, "1") +} + type testCase struct { t *testing.T root *Template diff --git a/libgo/go/html/template/testdata/fs.zip b/libgo/go/html/template/testdata/fs.zip Binary files differnew file mode 100644 index 0000000..8581313 --- /dev/null +++ b/libgo/go/html/template/testdata/fs.zip |