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