diff --git a/sanitize/html.go b/sanitize/html.go
new file mode 100644
index 00000000..d8e1c6eb
--- /dev/null
+++ b/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
+}
diff --git a/sanitize/html_test.go b/sanitize/html_test.go
new file mode 100644
index 00000000..fa72e0cc
--- /dev/null
+++ b/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{
+ "
paragraph
",
+ "bold",
+ "italic",
+ "emphasis",
+ "strong",
+ "text
",
+ }
+ 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`,
+ `safe`,
+ },
+ {
+ `mysite`,
+ `mysite`,
+ },
+ }
+ 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)
+ }
+ })
+ }
+}
diff --git a/themes/bootstrap/public/inbucket.css b/themes/bootstrap/public/inbucket.css
index c3c9f490..c895626e 100644
--- a/themes/bootstrap/public/inbucket.css
+++ b/themes/bootstrap/public/inbucket.css
@@ -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;
}
diff --git a/themes/bootstrap/public/mailbox.js b/themes/bootstrap/public/mailbox.js
index 886c9a51..e807b1ce 100644
--- a/themes/bootstrap/public/mailbox.js
+++ b/themes/bootstrap/public/mailbox.js
@@ -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);
}
diff --git a/themes/bootstrap/templates/mailbox/_show.html b/themes/bootstrap/templates/mailbox/_show.html
index 33a9975b..49621f07 100644
--- a/themes/bootstrap/templates/mailbox/_show.html
+++ b/themes/bootstrap/templates/mailbox/_show.html
@@ -24,7 +24,7 @@
class="btn btn-primary"
onClick="htmlView('{{.message.ID}}');">
- HTML
+ Raw HTML
{{end}}
@@ -78,7 +78,22 @@
{{end}}
-{{.body}}
+
+
+
{{.htmlBody}}
+
{{.body}}
+
{{with .attachments}}
diff --git a/webui/mailbox_controller.go b/webui/mailbox_controller.go
index b7c8a4e6..7f2d6e88 100644
--- a/webui/mailbox_controller.go
+++ b/webui/mailbox_controller.go
@@ -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"
)
@@ -118,6 +119,14 @@ 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,
@@ -125,6 +134,7 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
"message": msg,
"body": body,
"htmlAvailable": htmlAvailable,
+ "htmlBody": htmlBody,
"mimeErrors": mime.Errors,
"attachments": mime.Attachments,
})