diff --git a/src/pkg/exp/template/html/Makefile b/src/pkg/exp/template/html/Makefile index 0398c78fd6..e53270c9c8 100644 --- a/src/pkg/exp/template/html/Makefile +++ b/src/pkg/exp/template/html/Makefile @@ -7,6 +7,7 @@ include ../../../../Make.inc TARG=exp/template/html GOFILES=\ clone.go\ + content.go\ context.go\ css.go\ doc.go\ diff --git a/src/pkg/exp/template/html/content.go b/src/pkg/exp/template/html/content.go new file mode 100644 index 0000000000..4f79200405 --- /dev/null +++ b/src/pkg/exp/template/html/content.go @@ -0,0 +1,83 @@ +// 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 + +import ( + "fmt" +) + +// Strings of content from a trusted source. +type ( + // CSS encapsulates known safe content that matches any of: + // (1) The CSS3 stylesheet production, such as `p { color: purple }`. + // (2) The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. + // (3) CSS3 declaration productions, such as `color: red; margin: 2px`. + // (4) The CSS3 value production, such as `rgba(0, 0, 255, 127)`. + // See http://www.w3.org/TR/css3-syntax/#style + CSS string + + // HTML encapsulates a known safe HTML document fragment. + // Should not be used for HTML from a third-party, or HTML with + // unclosed tags or comments. The outputs of a sound HTML sanitizer + // and a template escaped by this package are fine for use with HTML. + HTML string + + // JS encapsulates a known safe EcmaScript5 Expression, or example, + // `(x + y * z())`. + // Template authors are responsible for ensuring that typed expressions + // do not break the intended precedence and that there is no + // statement/expression ambiguity as when passing an expression like + // "{ foo: bar() }\n['foo']()", which is both a valid Expression and a + // valid Program with a very different meaning. + JS string + + // JSStr encapsulates a sequence of characters meant to be embedded + // between quotes in a JavaScript expression. + // The string must match a series of StringCharacters: + // StringCharacter :: SourceCharacter but not `\` or LineTerminator + // | EscapeSequence + // Note that LineContinuations are not allowed. + // JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not. + JSStr string + + // URL encapsulates a known safe URL as defined in RFC 3896. + // A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()` + // from a trusted source should go in the page, but by default dynamic + // `javascript:` URLs are filtered out since they are a frequently + // exploited injection vector. + URL string +) + +type contentType uint8 + +const ( + contentTypePlain contentType = iota + contentTypeCSS + contentTypeHTML + contentTypeJS + contentTypeJSStr + contentTypeURL +) + +// stringify converts its arguments to a string and the type of the content. +func stringify(args ...interface{}) (string, contentType) { + if len(args) == 1 { + switch s := args[0].(type) { + case string: + return s, contentTypePlain + case CSS: + return string(s), contentTypeCSS + case HTML: + return string(s), contentTypeHTML + case JS: + return string(s), contentTypeJS + case JSStr: + return string(s), contentTypeJSStr + case URL: + return string(s), contentTypeURL + } + } + return fmt.Sprint(args...), contentTypePlain +} diff --git a/src/pkg/exp/template/html/content_test.go b/src/pkg/exp/template/html/content_test.go new file mode 100644 index 0000000000..caef5ade8e --- /dev/null +++ b/src/pkg/exp/template/html/content_test.go @@ -0,0 +1,196 @@ +// 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 + +import ( + "bytes" + "strings" + "template" + "testing" +) + +func TestTypedContent(t *testing.T) { + data := []interface{}{ + ` "foo%" O'Reilly &bar;`, + CSS(`a[href =~ "//example.com"]#foo`), + HTML(`Hello, World &tc!`), + JS(`c && alert("Hello, World!");`), + JSStr(`Hello, World & O'Reilly\x21`), + URL(`greeting=H%69&addressee=(World)`), + } + + // For each content sensitive escaper, see how it does on + // each of the typed strings above. + tests := []struct { + // A template containing a single {{.}}. + input string + want []string + }{ + { + ``, + []string{ + `ZgotmplZ`, + // Allowed but not escaped. + `a[href =~ "//example.com"]#foo`, + `ZgotmplZ`, + `ZgotmplZ`, + `ZgotmplZ`, + `ZgotmplZ`, + }, + }, + { + `
`, + []string{ + `ZgotmplZ`, + // Allowed and HTML escaped. + `a[href =~ "//example.com"]#foo`, + `ZgotmplZ`, + `ZgotmplZ`, + `ZgotmplZ`, + `ZgotmplZ`, + }, + }, + { + `{{.}}`, + []string{ + `<b> "foo%" O'Reilly &bar;`, + `a[href =~ "//example.com"]#foo`, + // Not escaped. + `Hello, World &tc!`, + `c && alert("Hello, World!");`, + `Hello, World & O'Reilly\x21`, + `greeting=H%69&addressee=(World)`, + }, + }, + { + ``, + []string{ + `<b> "foo%" O'Reilly &bar;`, + `a[href =~ "//example.com"]#foo`, + // Tags stripped, spaces escaped, entity not re-escaped. + `Hello, World &tc!`, + `c && alert("Hello, World!");`, + `Hello, World & O'Reilly\x21`, + `greeting=H%69&addressee=(World)`, + }, + }, + { + ``, + []string{ + `<b> "foo%" O'Reilly &bar;`, + `a[href =~ "//example.com"]#foo`, + // Tags stripped, entity not re-escaped. + `Hello, World &tc!`, + `c && alert("Hello, World!");`, + `Hello, World & O'Reilly\x21`, + `greeting=H%69&addressee=(World)`, + }, + }, + { + ``, + []string{ + `<b> "foo%" O'Reilly &bar;`, + `a[href =~ "//example.com"]#foo`, + // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. + `Hello, <b>World</b> &tc!`, + `c && alert("Hello, World!");`, + `Hello, World & O'Reilly\x21`, + `greeting=H%69&addressee=(World)`, + }, + }, + { + ``, + []string{ + `"\u003cb\u003e \"foo%\" O'Reilly &bar;"`, + `"a[href =~ \"//example.com\"]#foo"`, + `"Hello, \u003cb\u003eWorld\u003c/b\u003e &tc!"`, + // Not escaped. + `c && alert("Hello, World!");`, + // Escape sequence not over-escaped. + `"Hello, World & O'Reilly\x21"`, + `"greeting=H%69&addressee=(World)"`, + }, + }, + { + `