diff options
author | Ian Lance Taylor <iant@golang.org> | 2017-01-14 00:05:42 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2017-01-14 00:05:42 +0000 |
commit | c2047754c300b68c05d65faa8dc2925fe67b71b4 (patch) | |
tree | e183ae81a1f48a02945cb6de463a70c5be1b06f6 /libgo/go/html | |
parent | 829afb8f05602bb31c9c597b24df7377fed4f059 (diff) | |
download | gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.zip gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.tar.gz gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.tar.bz2 |
libgo: update to Go 1.8 release candidate 1
Compiler changes:
* Change map assignment to use mapassign and assign value directly.
* Change string iteration to use decoderune, faster for ASCII strings.
* Change makeslice to take int, and use makeslice64 for larger values.
* Add new noverflow field to hmap struct used for maps.
Unresolved problems, to be fixed later:
* Commented out test in go/types/sizes_test.go that doesn't compile.
* Commented out reflect.TestStructOf test for padding after zero-sized field.
Reviewed-on: https://go-review.googlesource.com/35231
gotools/:
Updates for Go 1.8rc1.
* Makefile.am (go_cmd_go_files): Add bug.go.
(s-zdefaultcc): Write defaultPkgConfig.
* Makefile.in: Rebuild.
From-SVN: r244456
Diffstat (limited to 'libgo/go/html')
-rw-r--r-- | libgo/go/html/template/clone_test.go | 68 | ||||
-rw-r--r-- | libgo/go/html/template/content_test.go | 41 | ||||
-rw-r--r-- | libgo/go/html/template/context.go | 14 | ||||
-rw-r--r-- | libgo/go/html/template/doc.go | 2 | ||||
-rw-r--r-- | libgo/go/html/template/error.go | 2 | ||||
-rw-r--r-- | libgo/go/html/template/escape.go | 14 | ||||
-rw-r--r-- | libgo/go/html/template/escape_test.go | 14 | ||||
-rw-r--r-- | libgo/go/html/template/js.go | 44 | ||||
-rw-r--r-- | libgo/go/html/template/js_test.go | 19 | ||||
-rw-r--r-- | libgo/go/html/template/template.go | 81 | ||||
-rw-r--r-- | libgo/go/html/template/template_test.go | 130 | ||||
-rw-r--r-- | libgo/go/html/template/transition.go | 30 | ||||
-rw-r--r-- | libgo/go/html/template/url.go | 2 |
13 files changed, 411 insertions, 50 deletions
diff --git a/libgo/go/html/template/clone_test.go b/libgo/go/html/template/clone_test.go index d7c62fa3..b500715 100644 --- a/libgo/go/html/template/clone_test.go +++ b/libgo/go/html/template/clone_test.go @@ -7,7 +7,9 @@ package template import ( "bytes" "errors" + "fmt" "io/ioutil" + "sync" "testing" "text/template/parse" ) @@ -194,3 +196,69 @@ func TestFuncMapWorksAfterClone(t *testing.T) { t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) } } + +// https://golang.org/issue/16101 +func TestTemplateCloneExecuteRace(t *testing.T) { + const ( + input = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>` + overlay = `{{define "b"}}A{{end}}` + ) + outer := Must(New("outer").Parse(input)) + tmpl := Must(Must(outer.Clone()).Parse(overlay)) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + if err := tmpl.Execute(ioutil.Discard, "data"); err != nil { + panic(err) + } + } + }() + } + wg.Wait() +} + +func TestTemplateCloneLookup(t *testing.T) { + // Template.escape makes an assumption that the template associated + // with t.Name() is t. Check that this holds. + tmpl := Must(New("x").Parse("a")) + tmpl = Must(tmpl.Clone()) + if tmpl.Lookup(tmpl.Name()) != tmpl { + t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl") + } +} + +func TestCloneGrowth(t *testing.T) { + tmpl := Must(New("root").Parse(`<title>{{block "B". }}Arg{{end}}</title>`)) + tmpl = Must(tmpl.Clone()) + Must(tmpl.Parse(`{{define "B"}}Text{{end}}`)) + for i := 0; i < 10; i++ { + tmpl.Execute(ioutil.Discard, nil) + } + if len(tmpl.DefinedTemplates()) > 200 { + t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates())) + } +} + +// https://golang.org/issue/17735 +func TestCloneRedefinedName(t *testing.T) { + const base = ` +{{ define "a" -}}<title>{{ template "b" . -}}</title>{{ end -}} +{{ define "b" }}{{ end -}} +` + const page = `{{ template "a" . }}` + + t1 := Must(New("a").Parse(base)) + + 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) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/libgo/go/html/template/content_test.go b/libgo/go/html/template/content_test.go index e698328..0b4365c 100644 --- a/libgo/go/html/template/content_test.go +++ b/libgo/go/html/template/content_test.go @@ -162,6 +162,47 @@ func TestTypedContent(t *testing.T) { }, }, { + `<script type="text/javascript">alert("{{.}}")</script>`, + []string{ + `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`, + `a[href =~ \x22\/\/example.com\x22]#foo`, + `Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`, + ` dir=\x22ltr\x22`, + `c \x26\x26 alert(\x22Hello, World!\x22);`, + // Escape sequence not over-escaped. + `Hello, World \x26 O\x27Reilly\x21`, + `greeting=H%69\x26addressee=(World)`, + }, + }, + { + `<script type="text/javascript">alert({{.}})</script>`, + []string{ + `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, + `"a[href =~ \"//example.com\"]#foo"`, + `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, + `" dir=\"ltr\""`, + // Not escaped. + `c && alert("Hello, World!");`, + // Escape sequence not over-escaped. + `"Hello, World & O'Reilly\x21"`, + `"greeting=H%69\u0026addressee=(World)"`, + }, + }, + { + // Not treated as JS. The output is same as for <div>{{.}}</div> + `<script type="text/template">{{.}}</script>`, + []string{ + `<b> "foo%" O'Reilly &bar;`, + `a[href =~ "//example.com"]#foo`, + // Not escaped. + `Hello, <b>World</b> &tc!`, + ` dir="ltr"`, + `c && alert("Hello, World!");`, + `Hello, World & O'Reilly\x21`, + `greeting=H%69&addressee=(World)`, + }, + }, + { `<button onclick='alert("{{.}}")'>`, []string{ `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`, diff --git a/libgo/go/html/template/context.go b/libgo/go/html/template/context.go index c90fc1f..37a3faf 100644 --- a/libgo/go/html/template/context.go +++ b/libgo/go/html/template/context.go @@ -285,7 +285,8 @@ type element uint8 const ( // elementNone occurs outside a special tag or special element body. elementNone element = iota - // elementScript corresponds to the raw text <script> element. + // elementScript corresponds to the raw text <script> element + // with JS MIME type or no type attribute. elementScript // elementStyle corresponds to the raw text <style> element. elementStyle @@ -319,6 +320,8 @@ const ( attrNone attr = iota // attrScript corresponds to an event handler attribute. attrScript + // attrScriptType corresponds to the type attribute in script HTML element + attrScriptType // attrStyle corresponds to the style attribute whose value is CSS. attrStyle // attrURL corresponds to an attribute whose value is a URL. @@ -326,10 +329,11 @@ const ( ) var attrNames = [...]string{ - attrNone: "attrNone", - attrScript: "attrScript", - attrStyle: "attrStyle", - attrURL: "attrURL", + attrNone: "attrNone", + attrScript: "attrScript", + attrScriptType: "attrScriptType", + attrStyle: "attrStyle", + attrURL: "attrURL", } func (a attr) String() string { diff --git a/libgo/go/html/template/doc.go b/libgo/go/html/template/doc.go index e1e9cad..cb89812 100644 --- a/libgo/go/html/template/doc.go +++ b/libgo/go/html/template/doc.go @@ -129,7 +129,7 @@ then the template output is <script>var pair = {"A": "foo", "B": "bar"};</script> -See package json to understand how non-string content is marshalled for +See package json to understand how non-string content is marshaled for embedding in JavaScript contexts. diff --git a/libgo/go/html/template/error.go b/libgo/go/html/template/error.go index 5637384..cbcaf92 100644 --- a/libgo/go/html/template/error.go +++ b/libgo/go/html/template/error.go @@ -44,7 +44,7 @@ const ( // OK indicates the lack of an error. OK ErrorCode = iota - // ErrAmbigContext: "... appears in an ambiguous URL context" + // ErrAmbigContext: "... appears in an ambiguous context within a URL" // Example: // <a href=" // {{if .C}} diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go index 8f2fe46..0e7d2be 100644 --- a/libgo/go/html/template/escape.go +++ b/libgo/go/html/template/escape.go @@ -161,7 +161,7 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context { case urlPartUnknown: return context{ state: stateError, - err: errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous URL context", n), + err: errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous context within a URL", n), } default: panic(c.urlPart.String()) @@ -673,6 +673,8 @@ func contextAfterText(c context, s []byte) (context, int) { return transitionFunc[c.state](c, s[:i]) } + // We are at the beginning of an attribute value. + i := bytes.IndexAny(s, delimEnds[c.delim]) if i == -1 { i = len(s) @@ -703,13 +705,21 @@ func contextAfterText(c context, s []byte) (context, int) { } return c, len(s) } + + element := c.element + + // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS. + if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) { + element = elementNone + } + if c.delim != delimSpaceOrTagEnd { // Consume any quote. i++ } // On exiting an attribute, we discard all state information // except the state and element. - return context{state: stateTag, element: c.element}, i + return context{state: stateTag, element: element}, i } // editActionNode records a change to an action pipeline for later commit. diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go index 023ee57..f6ace49 100644 --- a/libgo/go/html/template/escape_test.go +++ b/libgo/go/html/template/escape_test.go @@ -903,7 +903,7 @@ func TestErrors(t *testing.T) { }, { `<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`, - "z:1:47: {{.H}} appears in an ambiguous URL context", + "z:1:47: {{.H}} appears in an ambiguous context within a URL", }, { `<a onclick="alert('Hello \`, @@ -1365,6 +1365,10 @@ func TestEscapeText(t *testing.T) { context{state: stateTag, element: elementScript}, }, { + `<script>`, + context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript}, + }, + { `<script>foo`, context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript}, }, @@ -1389,6 +1393,14 @@ func TestEscapeText(t *testing.T) { context{state: stateText}, }, { + `<script type="text/template">`, + context{state: stateText}, + }, + { + `<script type="notjs">`, + context{state: stateText}, + }, + { `<Script>`, context{state: stateJS, element: elementScript}, }, diff --git a/libgo/go/html/template/js.go b/libgo/go/html/template/js.go index f6d166b..6434fa3 100644 --- a/libgo/go/html/template/js.go +++ b/libgo/go/html/template/js.go @@ -162,14 +162,14 @@ func jsValEscaper(args ...interface{}) string { // a division operator it is not turned into a line comment: // x/{{y}} // turning into - // x//* error marshalling y: + // x//* error marshaling y: // second line of error message */null return fmt.Sprintf(" /* %s */null ", strings.Replace(err.Error(), "*/", "* /", -1)) } // TODO: maybe post-process output to prevent it from containing // "<!--", "-->", "<![CDATA[", "]]>", or "</script" - // in case custom marshallers produce output containing those. + // in case custom marshalers produce output containing those. // TODO: Maybe abbreviate \u00ab to \xab to produce more compact output. if len(b) == 0 { @@ -362,3 +362,43 @@ func isJSIdentPart(r rune) bool { } return false } + +// isJSType returns true if the given MIME type should be considered JavaScript. +// +// It is used to determine whether a script tag with a type attribute is a javascript container. +func isJSType(mimeType string) bool { + // per + // https://www.w3.org/TR/html5/scripting-1.html#attr-script-type + // https://tools.ietf.org/html/rfc7231#section-3.1.1 + // https://tools.ietf.org/html/rfc4329#section-3 + // https://www.ietf.org/rfc/rfc4627.txt + + // discard parameters + if i := strings.Index(mimeType, ";"); i >= 0 { + mimeType = mimeType[:i] + } + mimeType = strings.TrimSpace(mimeType) + switch mimeType { + case + "application/ecmascript", + "application/javascript", + "application/json", + "application/x-ecmascript", + "application/x-javascript", + "text/ecmascript", + "text/javascript", + "text/javascript1.0", + "text/javascript1.1", + "text/javascript1.2", + "text/javascript1.3", + "text/javascript1.4", + "text/javascript1.5", + "text/jscript", + "text/livescript", + "text/x-ecmascript", + "text/x-javascript": + return true + default: + return false + } +} diff --git a/libgo/go/html/template/js_test.go b/libgo/go/html/template/js_test.go index 7af7997..7484f60 100644 --- a/libgo/go/html/template/js_test.go +++ b/libgo/go/html/template/js_test.go @@ -332,6 +332,25 @@ func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) { } } +func TestIsJsMimeType(t *testing.T) { + tests := []struct { + in string + out bool + }{ + {"application/javascript;version=1.8", true}, + {"application/javascript;version=1.8;foo=bar", true}, + {"application/javascript/version=1.8", false}, + {"text/javascript", true}, + {"application/json", true}, + } + + for _, test := range tests { + if isJSType(test.in) != test.out { + t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out) + } + } +} + func BenchmarkJSValEscaperWithNum(b *testing.B) { for i := 0; i < b.N; i++ { jsValEscaper(3.141592654) diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go index 063e46d..b313a6b 100644 --- a/libgo/go/html/template/template.go +++ b/libgo/go/html/template/template.go @@ -33,8 +33,9 @@ var escapeOK = fmt.Errorf("template escaped correctly") // nameSpace is the data structure shared by all templates in an association. type nameSpace struct { - mu sync.Mutex - set map[string]*Template + mu sync.Mutex + set map[string]*Template + escaped bool } // Templates returns a slice of the templates associated with t, including t @@ -74,13 +75,28 @@ func (t *Template) Option(opt ...string) *Template { return t } +// checkCanParse checks whether it is OK to parse templates. +// If not, it returns an error. +func (t *Template) checkCanParse() error { + if t == nil { + return nil + } + t.nameSpace.mu.Lock() + defer t.nameSpace.mu.Unlock() + if t.nameSpace.escaped { + return fmt.Errorf("html/template: cannot Parse after Execute") + } + return nil +} + // escape escapes all associated templates. func (t *Template) escape() error { t.nameSpace.mu.Lock() defer t.nameSpace.mu.Unlock() + t.nameSpace.escaped = true if t.escapeErr == nil { if t.Tree == nil { - return fmt.Errorf("template: %q is an incomplete or empty template%s", t.Name(), t.DefinedTemplates()) + return fmt.Errorf("template: %q is an incomplete or empty template", t.Name()) } if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil { return err @@ -124,6 +140,7 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) { t.nameSpace.mu.Lock() defer t.nameSpace.mu.Unlock() + t.nameSpace.escaped = true tmpl = t.set[name] if tmpl == nil { return nil, fmt.Errorf("html/template: %q is undefined", name) @@ -150,22 +167,27 @@ func (t *Template) DefinedTemplates() string { return t.text.DefinedTemplates() } -// Parse parses a string into a template. Nested template definitions -// will be associated with the top-level template t. Parse may be -// called multiple times to parse definitions of templates to associate -// with t. It is an error if a resulting template is non-empty (contains -// content other than template definitions) and would replace a -// non-empty template with the same name. (In multiple calls to Parse -// with the same receiver template, only one call can contain text -// other than space, comments, and template definitions.) -func (t *Template) Parse(src string) (*Template, error) { - t.nameSpace.mu.Lock() - t.escapeErr = nil - t.nameSpace.mu.Unlock() - ret, err := t.text.Parse(src) +// Parse parses text as a template body for t. +// Named template definitions ({{define ...}} or {{block ...}} statements) in text +// define additional templates associated with t and are removed from the +// definition of t itself. +// +// Templates can be redefined in successive calls to Parse, +// before the first use of Execute on t or any associated template. +// A template definition with a body containing only white space and comments +// is considered empty and will not replace an existing template's body. +// This allows using Parse to add new named template definitions without +// overwriting the main template body. +func (t *Template) Parse(text string) (*Template, error) { + if err := t.checkCanParse(); err != nil { + return nil, err + } + + ret, err := t.text.Parse(text) if err != nil { return nil, err } + // In general, all the named templates might have changed underfoot. // Regardless, some new ones may have been defined. // The template.Template set has been updated; update ours. @@ -176,11 +198,7 @@ func (t *Template) Parse(src string) (*Template, error) { tmpl := t.set[name] if tmpl == nil { tmpl = t.new(name) - } else if tmpl.escapeErr != nil { - return nil, fmt.Errorf("html/template: cannot redefine %q after it has executed", name) } - // Restore our record of this text/template to its unescaped original state. - tmpl.escapeErr = nil tmpl.text = v tmpl.Tree = v.Tree } @@ -190,13 +208,14 @@ func (t *Template) Parse(src string) (*Template, error) { // AddParseTree creates a new template with the name and parse tree // and associates it with t. // -// It returns an error if t has already been executed. +// It returns an error if t or any associated template has already been executed. func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { + if err := t.checkCanParse(); err != nil { + return nil, err + } + t.nameSpace.mu.Lock() defer t.nameSpace.mu.Unlock() - if t.escapeErr != nil { - return nil, fmt.Errorf("html/template: cannot AddParseTree to %q after it has executed", t.Name()) - } text, err := t.text.AddParseTree(name, tree) if err != nil { return nil, err @@ -252,7 +271,8 @@ func (t *Template) Clone() (*Template, error) { ret.nameSpace, } } - return ret, nil + // Return the template associated with the name of this template. + return ret.set[ret.Name()], nil } // New allocates a new HTML template with the given name. @@ -361,6 +381,8 @@ func ParseFiles(filenames ...string) (*Template, error) { // // When parsing multiple files with the same name in different directories, // the last one mentioned will be the one that results. +// +// 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...) } @@ -368,6 +390,10 @@ func (t *Template) ParseFiles(filenames ...string) (*Template, error) { // 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) { + if err := t.checkCanParse(); err != nil { + return nil, err + } + if len(filenames) == 0 { // Not really a problem, but be consistent. return nil, fmt.Errorf("html/template: no files named in call to ParseFiles") @@ -422,12 +448,17 @@ func ParseGlob(pattern string) (*Template, error) { // // When parsing multiple files with the same name in different directories, // the last one mentioned will be the one that results. +// +// ParseGlob returns an error if t or any associated template has already been executed. func (t *Template) ParseGlob(pattern string) (*Template, error) { return parseGlob(t, pattern) } // parseGlob is the implementation of the function and method ParseGlob. func parseGlob(t *Template, pattern string) (*Template, error) { + if err := t.checkCanParse(); err != nil { + return nil, err + } filenames, err := filepath.Glob(pattern) if err != nil { return nil, err diff --git a/libgo/go/html/template/template_test.go b/libgo/go/html/template/template_test.go index 46df1f8..90c5a73 100644 --- a/libgo/go/html/template/template_test.go +++ b/libgo/go/html/template/template_test.go @@ -1,7 +1,13 @@ -package template +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template_test import ( "bytes" + . "html/template" + "strings" "testing" ) @@ -27,3 +33,125 @@ func TestTemplateClone(t *testing.T) { t.Fatalf("got %q; want %q", got, want) } } + +func TestRedefineNonEmptyAfterExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `foo`) + c.mustExecute(c.root, nil, "foo") + c.mustNotParse(c.root, `bar`) +} + +func TestRedefineEmptyAfterExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, ``) + c.mustExecute(c.root, nil, "") + c.mustNotParse(c.root, `foo`) + c.mustExecute(c.root, nil, "") +} + +func TestRedefineAfterNonExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`) + c.mustExecute(c.root, 0, "") + c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) + c.mustExecute(c.root, 1, "<foo>") +} + +func TestRedefineAfterNamedExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`) + c.mustExecute(c.root, nil, "<foo>") + c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) + c.mustExecute(c.root, nil, "<foo>") +} + +func TestRedefineNestedByNameAfterExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `{{define "X"}}foo{{end}}`) + c.mustExecute(c.lookup("X"), nil, "foo") + c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`) + c.mustExecute(c.lookup("X"), nil, "foo") +} + +func TestRedefineNestedByTemplateAfterExecution(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `{{define "X"}}foo{{end}}`) + c.mustExecute(c.lookup("X"), nil, "foo") + c.mustNotParse(c.lookup("X"), `bar`) + c.mustExecute(c.lookup("X"), nil, "foo") +} + +func TestRedefineSafety(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`) + c.mustExecute(c.root, nil, `<html><a href="">`) + // Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X" + // on the next line, but luckily kept it from being used in the outer template. + // Now we reject it, which makes clearer that we're not going to use it. + c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`) + c.mustExecute(c.root, nil, `<html><a href="">`) +} + +func TestRedefineTopUse(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`) + c.mustExecute(c.root, 42, `42`) + c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`) + c.mustExecute(c.root, 42, `42`) +} + +func TestRedefineOtherParsers(t *testing.T) { + c := newTestCase(t) + c.mustParse(c.root, ``) + c.mustExecute(c.root, nil, ``) + if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") { + t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err) + } + if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") { + t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err) + } + if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") { + t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err) + } +} + +type testCase struct { + t *testing.T + root *Template +} + +func newTestCase(t *testing.T) *testCase { + return &testCase{ + t: t, + root: New("root"), + } +} + +func (c *testCase) lookup(name string) *Template { + return c.root.Lookup(name) +} + +func (c *testCase) mustParse(t *Template, text string) { + _, err := t.Parse(text) + if err != nil { + c.t.Fatalf("parse: %v", err) + } +} + +func (c *testCase) mustNotParse(t *Template, text string) { + _, err := t.Parse(text) + if err == nil { + c.t.Fatalf("parse: unexpected success") + } +} + +func (c *testCase) mustExecute(t *Template, val interface{}, want string) { + var buf bytes.Buffer + err := t.Execute(&buf, val) + if err != nil { + c.t.Fatalf("execute: %v", err) + } + if buf.String() != want { + c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want) + } +} diff --git a/libgo/go/html/template/transition.go b/libgo/go/html/template/transition.go index aefe035..4a4716d 100644 --- a/libgo/go/html/template/transition.go +++ b/libgo/go/html/template/transition.go @@ -105,14 +105,21 @@ func tTag(c context, s []byte) (context, int) { err: errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]), }, len(s) } - switch attrType(string(s[i:j])) { - case contentTypeURL: - attr = attrURL - case contentTypeCSS: - attr = attrStyle - case contentTypeJS: - attr = attrScript + + attrName := string(s[i:j]) + if c.element == elementScript && attrName == "type" { + attr = attrScriptType + } else { + switch attrType(attrName) { + case contentTypeURL: + attr = attrURL + case contentTypeCSS: + attr = attrStyle + case contentTypeJS: + attr = attrScript + } } + if j == len(s) { state = stateAttrName } else { @@ -149,10 +156,11 @@ func tAfterName(c context, s []byte) (context, int) { } var attrStartStates = [...]state{ - attrNone: stateAttr, - attrScript: stateJS, - attrStyle: stateCSS, - attrURL: stateURL, + attrNone: stateAttr, + attrScript: stateJS, + attrScriptType: stateAttr, + attrStyle: stateCSS, + attrURL: stateURL, } // tBeforeValue is the context transition function for stateBeforeValue. diff --git a/libgo/go/html/template/url.go b/libgo/go/html/template/url.go index 246bfd3..02123b2 100644 --- a/libgo/go/html/template/url.go +++ b/libgo/go/html/template/url.go @@ -32,7 +32,7 @@ func urlEscaper(args ...interface{}) string { return urlProcessor(false, args...) } -// urlEscaper normalizes URL content so it can be embedded in a quote-delimited +// urlNormalizer normalizes URL content so it can be embedded in a quote-delimited // string or parenthesis delimited url(...). // The normalizer does not encode all HTML specials. Specifically, it does not // encode '&' so correct embedding in an HTML attribute requires escaping of |