Merge pull request #22 from rtfb/master Add some protection against script injection
Vytautas Ĺ altenis Vytautas.Shaltenis@gmail.com
Tue, 21 May 2013 13:19:17 -0700
3 files changed,
110 insertions(+),
20 deletions(-)
M
html.go
→
html.go
@@ -28,6 +28,7 @@ HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
HTML_SKIP_STYLE // skip embedded <style> elements HTML_SKIP_IMAGES // skip embedded images HTML_SKIP_LINKS // skip all links + HTML_SKIP_SCRIPT // skip embedded <script> elements HTML_SAFELINK // only link to trusted protocols HTML_TOC // generate a table of contents HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)@@ -167,10 +168,32 @@ return
} doubleSpace(out) - out.Write(text) + if options.flags&HTML_SKIP_SCRIPT != 0 { + out.Write(stripTag(string(text), "script", "p")) + } else { + out.Write(text) + } out.WriteByte('\n') } +func stripTag(text, tag, newTag string) []byte { + closeNewTag := fmt.Sprintf("</%s>", newTag) + i := 0 + for i < len(text) && text[i] != '<' { + i++ + } + if i == len(text) { + return []byte(text) + } + found, end := findHtmlTagPos([]byte(text[i:]), tag) + closeTag := fmt.Sprintf("</%s>", tag) + noOpen := text + if found { + noOpen = text[0:i+1] + newTag + text[end:] + } + return []byte(strings.Replace(noOpen, closeTag, closeNewTag, -1)) +} + func (options *Html) HRule(out *bytes.Buffer) { doubleSpace(out) out.WriteString("<hr")@@ -460,6 +483,9 @@ }
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") { return } + if options.flags&HTML_SKIP_SCRIPT != 0 && isHtmlTag(text, "script") { + return + } out.Write(text) }@@ -646,39 +672,65 @@ }
} func isHtmlTag(tag []byte, tagname string) bool { + found, _ := findHtmlTagPos(tag, tagname) + return found +} + +func findHtmlTagPos(tag []byte, tagname string) (bool, int) { i := 0 if i < len(tag) && tag[0] != '<' { - return false + return false, -1 } i++ - for i < len(tag) && isspace(tag[i]) { - i++ - } + i = skipSpace(tag, i) if i < len(tag) && tag[i] == '/' { i++ } - for i < len(tag) && isspace(tag[i]) { - i++ - } - - j := i + i = skipSpace(tag, i) + j := 0 for ; i < len(tag); i, j = i+1, j+1 { if j >= len(tagname) { break } - if tag[i] != tagname[j] { - return false + if strings.ToLower(string(tag[i]))[0] != tagname[j] { + return false, -1 } } if i == len(tag) { - return false + return false, -1 } - return isspace(tag[i]) || tag[i] == '>' + // Now look for closing '>', but ignore it when it's in any kind of quotes, + // it might be JavaScript + inSingleQuote := false + inDoubleQuote := false + inGraveQuote := false + for i < len(tag) { + switch { + case tag[i] == '>' && !inSingleQuote && !inDoubleQuote && !inGraveQuote: + return true, i + case tag[i] == '\'': + inSingleQuote = !inSingleQuote + case tag[i] == '"': + inDoubleQuote = !inDoubleQuote + case tag[i] == '`': + inGraveQuote = !inGraveQuote + } + i++ + } + + return false, -1 +} + +func skipSpace(tag []byte, i int) int { + for i < len(tag) && isspace(tag[i]) { + i++ + } + return i } func doubleSpace(out *bytes.Buffer) {
M
inline_test.go
→
inline_test.go
@@ -17,12 +17,10 @@ import (
"testing" ) -func runMarkdownInline(input string) string { - extensions := 0 +func runMarkdownInline(input string, extensions, htmlFlags int) string { extensions |= EXTENSION_AUTOLINK extensions |= EXTENSION_STRIKETHROUGH - htmlFlags := 0 htmlFlags |= HTML_USE_XHTML renderer := HtmlRenderer(htmlFlags, "", "")@@ -31,6 +29,10 @@ return string(Markdown([]byte(input), renderer, extensions))
} func doTestsInline(t *testing.T, tests []string) { + doTestsInlineParam(t, tests, 0, 0) +} + +func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int) { // catch and report panics var candidate string defer func() {@@ -43,7 +45,7 @@ for i := 0; i+1 < len(tests); i += 2 {
input := tests[i] candidate = input expected := tests[i+1] - actual := runMarkdownInline(candidate) + actual := runMarkdownInline(candidate, extensions, htmlFlags) if actual != expected { t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", candidate, expected, actual)@@ -54,11 +56,46 @@ if !testing.Short() {
for start := 0; start < len(input); start++ { for end := start + 1; end <= len(input); end++ { candidate = input[start:end] - _ = runMarkdownInline(candidate) + _ = runMarkdownInline(candidate, extensions, htmlFlags) } } } } +} + +func TestRawHtmlTag(t *testing.T) { + tests := []string{ + "zz <style>p {}</style>\n", + "<p>zz p {}</p>\n", + + "zz <STYLE>p {}</STYLE>\n", + "<p>zz p {}</p>\n", + + "<SCRIPT>alert()</SCRIPT>\n", + "<p>alert()</p>\n", + + "zz <SCRIPT>alert()</SCRIPT>\n", + "<p>zz alert()</p>\n", + + "zz <script>alert()</script>\n", + "<p>zz alert()</p>\n", + + " <script>alert()</script>\n", + "<p>alert()</p>\n", + + "<script>alert()</script>\n", + "<p>alert()</p>\n", + + "<script src='foo'></script>\n", + "<p></p>\n", + + "zz <script src='foo'></script>\n", + "<p>zz </p>\n", + + "zz <script src=foo></script>\n", + "<p>zz </p>\n", + } + doTestsInlineParam(t, tests, 0, HTML_SKIP_STYLE|HTML_SKIP_SCRIPT) } func TestEmphasis(t *testing.T) {
M
markdown.go
→
markdown.go
@@ -202,7 +202,7 @@ // It processes markdown input with common extensions enabled, including:
// // * Smartypants processing with smart fractions and LaTeX dashes // -// * Intra-word emphasis supression +// * Intra-word emphasis suppression // // * Tables //@@ -220,6 +220,7 @@ htmlFlags |= HTML_USE_XHTML
htmlFlags |= HTML_USE_SMARTYPANTS htmlFlags |= HTML_SMARTYPANTS_FRACTIONS htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES + htmlFlags |= HTML_SKIP_SCRIPT renderer := HtmlRenderer(htmlFlags, "", "") // set up the parser