Skip to content

Commit

Permalink
Simple HTML sanitizer implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jhillyerd committed Jan 7, 2018
1 parent dedd0ea commit 26c38b1
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 4 deletions.
9 changes: 9 additions & 0 deletions sanitize/html.go
@@ -0,0 +1,9 @@
package sanitize

import "github.com/microcosm-cc/bluemonday"

func HTML(html string) (output string, err error) {
policy := bluemonday.UGCPolicy()
output = policy.Sanitize(html)
return
}
77 changes: 77 additions & 0 deletions sanitize/html_test.go
@@ -0,0 +1,77 @@
package sanitize_test

import (
"testing"

"github.com/jhillyerd/inbucket/sanitize"
)

// TestHTMLPlainStrings test plain text passthrough
func TestHTMLPlainStrings(t *testing.T) {
testStrings := []string{
"",
"plain string",
"one < two",
}
for _, ts := range testStrings {
t.Run(ts, func(t *testing.T) {
got, err := sanitize.HTML(ts)
if err != nil {
t.Fatal(err)
}
if got != ts {
t.Errorf("Got: %q, want: %q", got, ts)
}
})
}
}

// TestHTMLSimpleFormatting tests basic tags we should allow
func TestHTMLSimpleFormatting(t *testing.T) {
testStrings := []string{
"<p>paragraph</p>",
"<b>bold</b>",
"<i>italic</b>",
"<em>emphasis</em>",
"<strong>strong</strong>",
"<div><span>text</span></div>",
}
for _, ts := range testStrings {
t.Run(ts, func(t *testing.T) {
got, err := sanitize.HTML(ts)
if err != nil {
t.Fatal(err)
}
if got != ts {
t.Errorf("Got: %q, want: %q", got, ts)
}
})
}
}

// TestHTMLScriptTags tests some strings with JavaScript
func TestHTMLScriptTags(t *testing.T) {
testCases := []struct {
input, want string
}{
{
`safe<script>nope</script>`,
`safe`,
},
{
`<a onblur="alert(something)" href="http://mysite.com">mysite</a>`,
`<a href="http://mysite.com" rel="nofollow">mysite</a>`,
},
}
for _, tc := range testCases {
t.Run(tc.input, func(t *testing.T) {
got, err := sanitize.HTML(tc.input)
if err != nil {
t.Fatal(err)
}
if got != tc.want {
t.Errorf("Got: %q, want: %q", got, tc.want)
}
})
}
}
9 changes: 7 additions & 2 deletions themes/bootstrap/public/inbucket.css
Expand Up @@ -51,12 +51,17 @@ body {
}
}

#body-tabs > li > a {
padding-top: 6px;
padding-bottom: 6px;
}

.message-body {
padding: 0 5px;
padding: 10px 4px;
}

.message-attachments {
margin-top: 20px;
margin-top: 5px;
padding: 10px 10px 0 0;
}

Expand Down
1 change: 1 addition & 0 deletions themes/bootstrap/public/mailbox.js
Expand Up @@ -157,6 +157,7 @@ function onMessageLoaded(responseText, textStatus, XMLHttpRequest) {
return;
}
onDocumentChange();
$('#body-tabs a:first').tab('show')
var top = $('#message-container').offset().top - navBarOffset;
$(window).scrollTop(top);
}
Expand Down
19 changes: 17 additions & 2 deletions themes/bootstrap/templates/mailbox/_show.html
Expand Up @@ -24,7 +24,7 @@
class="btn btn-primary"
onClick="htmlView('{{.message.ID}}');">
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
HTML
Raw HTML
</button>
{{end}}
</div>
Expand Down Expand Up @@ -78,7 +78,22 @@
</div>
{{end}}

<div class="message-body">{{.body}}</div>
<nav>
<ul id="body-tabs" class="nav nav-tabs" role="tablist">
{{if .htmlAvailable}}
<li role="presentation">
<a href="#body-html" aria-controls="body-html" role="tab" data-toggle="tab">Safe HTML</a>
</li>
{{end}}
<li role="presentation">
<a href="#body-text" aria-controls="body-text" role="tab" data-toggle="tab">Plain Text</a>
</li>
</ul>
</nav>
<div class="tab-content">
<div role="tabpanel" class="tab-pane message-body" id="body-html">{{.htmlBody}}</div>
<div role="tabpanel" class="tab-pane message-body" id="body-text">{{.body}}</div>
</div>

{{with .attachments}}
<div class="well message-attachments">
Expand Down
10 changes: 10 additions & 0 deletions webui/mailbox_controller.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/jhillyerd/inbucket/datastore"
"github.com/jhillyerd/inbucket/httpd"
"github.com/jhillyerd/inbucket/log"
"github.com/jhillyerd/inbucket/sanitize"
"github.com/jhillyerd/inbucket/stringutil"
)

Expand Down Expand Up @@ -118,13 +119,22 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
}
body := template.HTML(httpd.TextToHTML(mime.Text))
htmlAvailable := mime.HTML != ""
var htmlBody template.HTML
if htmlAvailable {
if str, err := sanitize.HTML(mime.HTML); err == nil {
htmlBody = template.HTML(str)
} else {
log.Warnf("HTML sanitizer failed: %s", err)
}
}
// Render partial template
return httpd.RenderPartial("mailbox/_show.html", w, map[string]interface{}{
"ctx": ctx,
"name": name,
"message": msg,
"body": body,
"htmlAvailable": htmlAvailable,
"htmlBody": htmlBody,
"mimeErrors": mime.Errors,
"attachments": mime.Attachments,
})
Expand Down

0 comments on commit 26c38b1

Please sign in to comment.