mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-20 11:07:01 +00:00
Improved message rendering
Added a decodeSection function to mime.go that uses go-qprintable to parse quoted-printable emails or MIME parts, fixes #7 Added a very basic TextToHtml converter to provide nicer rending of text message bodies.
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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", "<br/>\n", "\r", "<br/>\n", "\n", "<br/>\n")
|
||||
return replacer.Replace(text)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"), "<html>")
|
||||
|
||||
// Check for linebreaks
|
||||
assert.Equal(t, TextToHtml("line\nbreak"), "line<br/>\nbreak")
|
||||
assert.Equal(t, TextToHtml("line\r\nbreak"), "line<br/>\nbreak")
|
||||
assert.Equal(t, TextToHtml("line\rbreak"), "line<br/>\nbreak")
|
||||
}
|
||||
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
<table>
|
||||
<div id="emailSubject"><h3>{{.message.Subject}}</h3></div>
|
||||
|
||||
<pre id="emailBody">{{.body}}</pre>
|
||||
<div id="emailBody">{{.body}}</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user