diff --git a/app/controllers/mailbox.go b/app/controllers/mailbox.go
index 389c136..45d1777 100644
--- a/app/controllers/mailbox.go
+++ b/app/controllers/mailbox.go
@@ -69,7 +69,7 @@ func (c Mailbox) Show(name string, id string) rev.Result {
if err != nil {
return c.RenderError(err)
}
- body := mime.Text
+ body := template.HTML(inbucket.TextToHtml(mime.Text))
htmlAvailable := mime.Html != ""
c.Response.Out.Header().Set("Expires", "-1")
diff --git a/app/inbucket/mime.go b/app/inbucket/mime.go
index 6142a1f..66f4738 100644
--- a/app/inbucket/mime.go
+++ b/app/inbucket/mime.go
@@ -4,11 +4,12 @@ import (
"bytes"
"container/list"
"fmt"
+ "github.com/sloonz/go-qprintable"
"io"
- "io/ioutil"
"mime"
"mime/multipart"
"net/mail"
+ "strings"
)
type MIMENodeMatcher func(node *MIMENode) bool
@@ -85,7 +86,7 @@ func ParseMIMEBody(mailMsg *mail.Message) (*MIMEBody, error) {
if !IsMIMEMessage(mailMsg) {
// Parse as text only
- bodyBytes, err := ioutil.ReadAll(mailMsg.Body)
+ bodyBytes, err := decodeSection(mailMsg.Header.Get("Content-Transfer-Encoding"), mailMsg.Body)
if err != nil {
return nil, err
}
@@ -136,6 +137,29 @@ func (m *MIMEBody) String() string {
return fmt.Sprintf("----TEXT----\n%v\n----HTML----\n%v\n----END----\n", m.Text, m.Html)
}
+// decodeSection attempts to decode the data from reader using the algorithm listed in
+// the Content-Transfer-Encoding header, returning the raw data if it does not know
+// the encoding type.
+func decodeSection(encoding string, reader io.Reader) ([]byte, error) {
+ switch strings.ToLower(encoding) {
+ case "quoted-printable":
+ decoder := qprintable.NewDecoder(qprintable.WindowsTextEncoding, reader)
+ buf := new(bytes.Buffer)
+ _, err := buf.ReadFrom(decoder)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+ }
+ // Don't know this type, just return bytes
+ buf := new(bytes.Buffer)
+ _, err := buf.ReadFrom(reader)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
func parseNodes(parent *MIMENode, reader io.Reader, boundary string) error {
var prevSibling *MIMENode
@@ -172,13 +196,12 @@ func parseNodes(parent *MIMENode, reader io.Reader, boundary string) error {
return err
}
} else {
- // Content is data, allocate a buffer
- buf := new(bytes.Buffer)
- _, err = buf.ReadFrom(part)
+ // Content is text or data, decode it
+ data, err := decodeSection(part.Header.Get("Content-Transfer-Encoding"), part)
if err != nil {
return err
}
- node.Content = buf.Bytes()
+ node.Content = data
}
}
diff --git a/app/inbucket/mime_test.go b/app/inbucket/mime_test.go
index 743344f..b2e1e4a 100644
--- a/app/inbucket/mime_test.go
+++ b/app/inbucket/mime_test.go
@@ -42,6 +42,28 @@ func TestParseInlineText(t *testing.T) {
assert.Equal(t, mime.Text, "Test of HTML section")
}
+func TestParseQuotedPrintable(t *testing.T) {
+ msg := readMessage("quoted-printable.raw")
+
+ mime, err := ParseMIMEBody(msg)
+ if err != nil {
+ t.Fatalf("Failed to parse MIME: %v", err)
+ }
+
+ assert.Contains(t, mime.Text, "Phasellus sit amet arcu")
+}
+
+func TestParseQuotedPrintableMime(t *testing.T) {
+ msg := readMessage("quoted-printable-mime.raw")
+
+ mime, err := ParseMIMEBody(msg)
+ if err != nil {
+ t.Fatalf("Failed to parse MIME: %v", err)
+ }
+
+ assert.Contains(t, mime.Text, "Nullam venenatis ante")
+}
+
func TestParseInlineHtml(t *testing.T) {
msg := readMessage("html-mime-inline.raw")
diff --git a/app/inbucket/utils.go b/app/inbucket/utils.go
index 8ae9130..85047a0 100644
--- a/app/inbucket/utils.go
+++ b/app/inbucket/utils.go
@@ -3,6 +3,7 @@ package inbucket
import (
"crypto/sha1"
"fmt"
+ "html"
"io"
"strings"
)
@@ -26,3 +27,11 @@ func HashMailboxName(mailbox string) string {
return fmt.Sprintf("%x", h.Sum(nil))
}
+// TextToHtml takes plain text, escapes it and tries to pretty it up for
+// HTML display
+func TextToHtml(text string) string {
+ text = html.EscapeString(text)
+ replacer := strings.NewReplacer("\r\n", "
\n", "\r", "
\n", "\n", "
\n")
+ return replacer.Replace(text)
+}
+
diff --git a/app/inbucket/utils_test.go b/app/inbucket/utils_test.go
index af12e16..0d6f9f3 100644
--- a/app/inbucket/utils_test.go
+++ b/app/inbucket/utils_test.go
@@ -1,28 +1,44 @@
package inbucket
-import "testing"
+import (
+ "github.com/stretchrcom/testify/assert"
+ "testing"
+)
func TestParseMailboxName(t *testing.T) {
- in, out := "MailBOX", "mailbox"
- if x := ParseMailboxName(in); x != out {
- t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
- }
+ in, out := "MailBOX", "mailbox"
+ if x := ParseMailboxName(in); x != out {
+ t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
+ }
- in, out = "MailBox@Host.Com", "mailbox"
- if x := ParseMailboxName(in); x != out {
- t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
- }
+ in, out = "MailBox@Host.Com", "mailbox"
+ if x := ParseMailboxName(in); x != out {
+ t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
+ }
- in, out = "Mail+extra@Host.Com", "mail"
- if x := ParseMailboxName(in); x != out {
- t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
- }
+ in, out = "Mail+extra@Host.Com", "mail"
+ if x := ParseMailboxName(in); x != out {
+ t.Errorf("ParseMailboxName(%v) = %v, want %v", in, x, out)
+ }
}
func TestHashMailboxName(t *testing.T) {
- in, out := "mail", "1d6e1cf70ec6f9ab28d3ea4b27a49a77654d370e"
- if x := HashMailboxName(in); x != out {
- t.Errorf("HashMailboxName(%v) = %v, want %v", in, x, out)
- }
+ in, out := "mail", "1d6e1cf70ec6f9ab28d3ea4b27a49a77654d370e"
+ if x := HashMailboxName(in); x != out {
+ t.Errorf("HashMailboxName(%v) = %v, want %v", in, x, out)
+ }
+}
+
+func TestTextToHtml(t *testing.T) {
+ // Identity
+ assert.Equal(t, TextToHtml("html"), "html")
+
+ // Check it escapes
+ assert.Equal(t, TextToHtml(""), "<html>")
+
+ // Check for linebreaks
+ assert.Equal(t, TextToHtml("line\nbreak"), "line
\nbreak")
+ assert.Equal(t, TextToHtml("line\r\nbreak"), "line
\nbreak")
+ assert.Equal(t, TextToHtml("line\rbreak"), "line
\nbreak")
}
diff --git a/app/views/Mailbox/Show.html b/app/views/Mailbox/Show.html
index 53a5367..116debd 100644
--- a/app/views/Mailbox/Show.html
+++ b/app/views/Mailbox/Show.html
@@ -17,5 +17,5 @@