blob: e0e87b98d043fdfe8aa22b2177c576e8be0825de [file] [log] [blame]
// 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)
}