all repos — grayfriday @ 2336fd31093e8d1d9cab8642e0c08a82520a2abc

blackfriday fork with a few changes

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
commit

2336fd31093e8d1d9cab8642e0c08a82520a2abc

parent

015b0af4355b1120202677944761a0caf40e89ed

3 files changed, 110 insertions(+), 20 deletions(-)

jump to
M html.gohtml.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.goinline_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.gomarkdown.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