mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
Merge pull request #22 from rtfb/master
Add some protection against script injection
This commit is contained in:
commit
2336fd3109
80
html.go
80
html.go
|
@ -28,6 +28,7 @@ const (
|
||||||
HTML_SKIP_STYLE // skip embedded <style> elements
|
HTML_SKIP_STYLE // skip embedded <style> elements
|
||||||
HTML_SKIP_IMAGES // skip embedded images
|
HTML_SKIP_IMAGES // skip embedded images
|
||||||
HTML_SKIP_LINKS // skip all links
|
HTML_SKIP_LINKS // skip all links
|
||||||
|
HTML_SKIP_SCRIPT // skip embedded <script> elements
|
||||||
HTML_SAFELINK // only link to trusted protocols
|
HTML_SAFELINK // only link to trusted protocols
|
||||||
HTML_TOC // generate a table of contents
|
HTML_TOC // generate a table of contents
|
||||||
HTML_OMIT_CONTENTS // skip the main contents (for a standalone 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)
|
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')
|
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) {
|
func (options *Html) HRule(out *bytes.Buffer) {
|
||||||
doubleSpace(out)
|
doubleSpace(out)
|
||||||
out.WriteString("<hr")
|
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") {
|
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if options.flags&HTML_SKIP_SCRIPT != 0 && isHtmlTag(text, "script") {
|
||||||
|
return
|
||||||
|
}
|
||||||
out.Write(text)
|
out.Write(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,39 +672,65 @@ func (options *Html) TocFinalize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHtmlTag(tag []byte, tagname string) bool {
|
func isHtmlTag(tag []byte, tagname string) bool {
|
||||||
|
found, _ := findHtmlTagPos(tag, tagname)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
|
||||||
i := 0
|
i := 0
|
||||||
if i < len(tag) && tag[0] != '<' {
|
if i < len(tag) && tag[0] != '<' {
|
||||||
return false
|
return false, -1
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
for i < len(tag) && isspace(tag[i]) {
|
i = skipSpace(tag, i)
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < len(tag) && tag[i] == '/' {
|
if i < len(tag) && tag[i] == '/' {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
for i < len(tag) && isspace(tag[i]) {
|
i = skipSpace(tag, i)
|
||||||
i++
|
j := 0
|
||||||
}
|
|
||||||
|
|
||||||
j := i
|
|
||||||
for ; i < len(tag); i, j = i+1, j+1 {
|
for ; i < len(tag); i, j = i+1, j+1 {
|
||||||
if j >= len(tagname) {
|
if j >= len(tagname) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if tag[i] != tagname[j] {
|
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||||
return false
|
return false, -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len(tag) {
|
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) {
|
func doubleSpace(out *bytes.Buffer) {
|
||||||
|
|
|
@ -17,12 +17,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runMarkdownInline(input string) string {
|
func runMarkdownInline(input string, extensions, htmlFlags int) string {
|
||||||
extensions := 0
|
|
||||||
extensions |= EXTENSION_AUTOLINK
|
extensions |= EXTENSION_AUTOLINK
|
||||||
extensions |= EXTENSION_STRIKETHROUGH
|
extensions |= EXTENSION_STRIKETHROUGH
|
||||||
|
|
||||||
htmlFlags := 0
|
|
||||||
htmlFlags |= HTML_USE_XHTML
|
htmlFlags |= HTML_USE_XHTML
|
||||||
|
|
||||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
@ -31,6 +29,10 @@ func runMarkdownInline(input string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestsInline(t *testing.T, tests []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
|
// catch and report panics
|
||||||
var candidate string
|
var candidate string
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -43,7 +45,7 @@ func doTestsInline(t *testing.T, tests []string) {
|
||||||
input := tests[i]
|
input := tests[i]
|
||||||
candidate = input
|
candidate = input
|
||||||
expected := tests[i+1]
|
expected := tests[i+1]
|
||||||
actual := runMarkdownInline(candidate)
|
actual := runMarkdownInline(candidate, extensions, htmlFlags)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
candidate, expected, actual)
|
candidate, expected, actual)
|
||||||
|
@ -54,13 +56,48 @@ func doTestsInline(t *testing.T, tests []string) {
|
||||||
for start := 0; start < len(input); start++ {
|
for start := 0; start < len(input); start++ {
|
||||||
for end := start + 1; end <= len(input); end++ {
|
for end := start + 1; end <= len(input); end++ {
|
||||||
candidate = input[start: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) {
|
func TestEmphasis(t *testing.T) {
|
||||||
var tests = []string{
|
var tests = []string{
|
||||||
"nothing inline\n",
|
"nothing inline\n",
|
||||||
|
|
|
@ -202,7 +202,7 @@ func MarkdownBasic(input []byte) []byte {
|
||||||
//
|
//
|
||||||
// * Smartypants processing with smart fractions and LaTeX dashes
|
// * Smartypants processing with smart fractions and LaTeX dashes
|
||||||
//
|
//
|
||||||
// * Intra-word emphasis supression
|
// * Intra-word emphasis suppression
|
||||||
//
|
//
|
||||||
// * Tables
|
// * Tables
|
||||||
//
|
//
|
||||||
|
@ -220,6 +220,7 @@ func MarkdownCommon(input []byte) []byte {
|
||||||
htmlFlags |= HTML_USE_SMARTYPANTS
|
htmlFlags |= HTML_USE_SMARTYPANTS
|
||||||
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
|
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
|
||||||
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
|
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
|
||||||
|
htmlFlags |= HTML_SKIP_SCRIPT
|
||||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
// set up the parser
|
// set up the parser
|
||||||
|
|
Loading…
Reference in New Issue
Block a user