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:
9
sanitize/html.go
Normal file
9
sanitize/html.go
Normal 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
77
sanitize/html_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user