Merge pull request #22 from rtfb/master

Add some protection against script injection
This commit is contained in:
Vytautas Šaltenis 2013-05-21 13:19:17 -07:00
commit 2336fd3109
3 changed files with 110 additions and 20 deletions

78
html.go
View File

@ -28,6 +28,7 @@ const (
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 @@ func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
}
doubleSpace(out)
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 @@ func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
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 (options *Html) TocFinalize() {
}
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) {

View File

@ -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 @@ func runMarkdownInline(input string) string {
}
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 @@ func doTestsInline(t *testing.T, tests []string) {
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,13 +56,48 @@ func doTestsInline(t *testing.T, tests []string) {
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) {
var tests = []string{
"nothing inline\n",

View File

@ -202,7 +202,7 @@ func MarkdownBasic(input []byte) []byte {
//
// * Smartypants processing with smart fractions and LaTeX dashes
//
// * Intra-word emphasis supression
// * Intra-word emphasis suppression
//
// * Tables
//
@ -220,6 +220,7 @@ func MarkdownCommon(input []byte) []byte {
htmlFlags |= HTML_USE_SMARTYPANTS
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
htmlFlags |= HTML_SKIP_SCRIPT
renderer := HtmlRenderer(htmlFlags, "", "")
// set up the parser