1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00

Simple HTML sanitizer implementation

This commit is contained in:
James Hillyerd
2018-01-06 16:45:12 -08:00
parent dedd0eacff
commit 26c38b1148
6 changed files with 121 additions and 4 deletions

9
sanitize/html.go Normal file
View File

@@ -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
sanitize/html_test.go Normal file
View File

@@ -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)
}
})
}
}

View File

@@ -51,12 +51,17 @@ body {
} }
} }
#body-tabs > li > a {
padding-top: 6px;
padding-bottom: 6px;
}
.message-body { .message-body {
padding: 0 5px; padding: 10px 4px;
} }
.message-attachments { .message-attachments {
margin-top: 20px; margin-top: 5px;
padding: 10px 10px 0 0; padding: 10px 10px 0 0;
} }

View File

@@ -157,6 +157,7 @@ function onMessageLoaded(responseText, textStatus, XMLHttpRequest) {
return; return;
} }
onDocumentChange(); onDocumentChange();
$('#body-tabs a:first').tab('show')
var top = $('#message-container').offset().top - navBarOffset; var top = $('#message-container').offset().top - navBarOffset;
$(window).scrollTop(top); $(window).scrollTop(top);
} }

View File

@@ -24,7 +24,7 @@
class="btn btn-primary" class="btn btn-primary"
onClick="htmlView('{{.message.ID}}');"> onClick="htmlView('{{.message.ID}}');">
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
HTML Raw HTML
</button> </button>
{{end}} {{end}}
</div> </div>
@@ -78,7 +78,22 @@
</div> </div>
{{end}} {{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}} {{with .attachments}}
<div class="well message-attachments"> <div class="well message-attachments">

View File

@@ -10,6 +10,7 @@ import (
"github.com/jhillyerd/inbucket/datastore" "github.com/jhillyerd/inbucket/datastore"
"github.com/jhillyerd/inbucket/httpd" "github.com/jhillyerd/inbucket/httpd"
"github.com/jhillyerd/inbucket/log" "github.com/jhillyerd/inbucket/log"
"github.com/jhillyerd/inbucket/sanitize"
"github.com/jhillyerd/inbucket/stringutil" "github.com/jhillyerd/inbucket/stringutil"
) )
@@ -118,6 +119,14 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
} }
body := template.HTML(httpd.TextToHTML(mime.Text)) body := template.HTML(httpd.TextToHTML(mime.Text))
htmlAvailable := mime.HTML != "" 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 // Render partial template
return httpd.RenderPartial("mailbox/_show.html", w, map[string]interface{}{ return httpd.RenderPartial("mailbox/_show.html", w, map[string]interface{}{
"ctx": ctx, "ctx": ctx,
@@ -125,6 +134,7 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
"message": msg, "message": msg,
"body": body, "body": body,
"htmlAvailable": htmlAvailable, "htmlAvailable": htmlAvailable,
"htmlBody": htmlBody,
"mimeErrors": mime.Errors, "mimeErrors": mime.Errors,
"attachments": mime.Attachments, "attachments": mime.Attachments,
}) })