diff options
Diffstat (limited to 'src/pkg/exp/template/html/escape.go')
-rw-r--r-- | src/pkg/exp/template/html/escape.go | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/src/pkg/exp/template/html/escape.go b/src/pkg/exp/template/html/escape.go new file mode 100644 index 000000000..e0e87b98d --- /dev/null +++ b/src/pkg/exp/template/html/escape.go @@ -0,0 +1,105 @@ +// Copyright 2011 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 html is a specialization of exp/template that automates the +// construction of safe HTML output. +// At the moment, the escaping is naive. All dynamic content is assumed to be +// plain text interpolated in an HTML PCDATA context. +package html + +import ( + "template" + "template/parse" +) + +// Escape rewrites each action in the template to guarantee the output is +// HTML-escaped. +func Escape(t *template.Template) { + // If the parser shares trees based on common-subexpression + // joining then we will need to avoid multiply escaping the same action. + escapeListNode(t.Tree.Root) +} + +// escapeNode dispatches to escape<NodeType> helpers by type. +func escapeNode(node parse.Node) { + switch n := node.(type) { + case *parse.ListNode: + escapeListNode(n) + case *parse.TextNode: + // Nothing to do. + case *parse.ActionNode: + escapeActionNode(n) + case *parse.IfNode: + escapeIfNode(n) + case *parse.RangeNode: + escapeRangeNode(n) + case *parse.TemplateNode: + // Nothing to do. + case *parse.WithNode: + escapeWithNode(n) + default: + panic("handling for " + node.String() + " not implemented") + // TODO: Handle other inner node types. + } +} + +// escapeListNode recursively escapes its input's children. +func escapeListNode(node *parse.ListNode) { + if node == nil { + return + } + children := node.Nodes + for _, child := range children { + escapeNode(child) + } +} + +// escapeActionNode adds a pipeline call to the end that escapes the result +// of the expression before it is interpolated into the template output. +func escapeActionNode(node *parse.ActionNode) { + pipe := node.Pipe + + cmds := pipe.Cmds + nCmds := len(cmds) + + // If it already has an escaping command, do not interfere. + if nCmds != 0 { + if lastCmd := cmds[nCmds-1]; len(lastCmd.Args) != 0 { + // TODO: Recognize url and js as escaping functions once + // we have enough context to know whether additional + // escaping is necessary. + if arg, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok && arg.Ident == "html" { + return + } + } + } + + htmlEscapeCommand := parse.CommandNode{ + NodeType: parse.NodeCommand, + Args: []parse.Node{parse.NewIdentifier("html")}, + } + + node.Pipe.Cmds = append(node.Pipe.Cmds, &htmlEscapeCommand) +} + +// escapeIfNode recursively escapes the if and then clauses but leaves the +// condition unchanged. +func escapeIfNode(node *parse.IfNode) { + escapeListNode(node.List) + escapeListNode(node.ElseList) +} + +// escapeRangeNode recursively escapes the loop body and else clause but +// leaves the series unchanged. +func escapeRangeNode(node *parse.RangeNode) { + escapeListNode(node.List) + escapeListNode(node.ElseList) +} + +// escapeWithNode recursively escapes the scope body and else clause but +// leaves the pipeline unchanged. +func escapeWithNode(node *parse.WithNode) { + escapeListNode(node.List) + escapeListNode(node.ElseList) +} |