1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +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:
James Hillyerd
2012-10-19 12:50:16 -07:00
parent 9e389e00d3
commit 2b3491fc87
8 changed files with 175 additions and 25 deletions

View File

@@ -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")

View File

@@ -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
}
}

View File

@@ -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")

View File

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

View File

@@ -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>"), "&lt;html&gt;")
// 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")
}

View File

@@ -17,5 +17,5 @@
<table>
<div id="emailSubject"><h3>{{.message.Subject}}</h3></div>
<pre id="emailBody">{{.body}}</pre>
<div id="emailBody">{{.body}}</div>

View File

@@ -286,6 +286,7 @@ a:hover {
#emailBody {
color: #000000;
margin-top: 15px;
}
#emailActions {

View File

@@ -0,0 +1,79 @@
Message-ID: <5081A889.3020108@jamehi03lx.noa.com>
Date: Fri, 19 Oct 2012 12:22:49 -0700
From: James Hillyerd <jamehi03@jamehi03lx.noa.com>
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20121010 Thunderbird/16.0.1
MIME-Version: 1.0
To: greg@inbucket.com
Subject: MIME Quoted Printable
Content-Type: multipart/alternative;
boundary="------------020203040006070307010003"
This is a multi-part message in MIME format.
--------------020203040006070307010003
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam =
venenatis ante fermentum justo varius hendrerit. Sed adipiscing =
adipiscing nisi at placerat. Sed luctus neque pellentesque sem laoreet =
dapibus. Class aptent taciti sociosqu ad litora torquent per conubia =
nostra, per inceptos himenaeos. In eu scelerisque nibh. Fusce faucibus, =
nisl vel tincidunt sodales, quam est condimentum lorem, vitae semper =
lacus nisl vel lacus. Vestibulum vitae iaculis urna. Donec pellentesque =
pellentesque ipsum sit amet suscipit. Ut aliquam vestibulum justo sit =
amet congue. Mauris nisl lacus, varius a ultrices id, suscipit mattis =
libero.
Donec iaculis dapibus purus in accumsan. Cras faucibus, orci dictum =
molestie congue, neque magna adipiscing lacus, ut laoreet sapien ante et =
mi. Suspendisse potenti. Nullam sed dui magna. Nullam vel purus augue, =
imperdiet vehicula purus. Lorem ipsum dolor sit amet, consectetur =
adipiscing elit. In aliquet, ante at tincidunt ultricies, arcu dolor =
rutrum odio, quis tempus lacus leo a quam. Fusce hendrerit, urna sed =
elementum pulvinar, dolor sem imperdiet arcu, ut ornare erat metus sit =
amet diam. Duis sagittis libero ut metus vulputate dictum. Etiam eget =
augue dolor, in lacinia felis. Nulla facilisi. Proin sollicitudin =
laoreet vehicula. Praesent non nibh odio.
--------------020203040006070307010003
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<div id="lipsum">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
venenatis ante fermentum justo varius hendrerit. Sed adipiscing
adipiscing nisi at placerat. Sed luctus neque pellentesque sem
laoreet dapibus. Class aptent taciti sociosqu ad litora torquent
per conubia nostra, per inceptos himenaeos. In eu scelerisque
nibh. Fusce faucibus, nisl vel tincidunt sodales, quam est
condimentum lorem, vitae semper lacus nisl vel lacus. Vestibulum
vitae iaculis urna. Donec pellentesque pellentesque ipsum sit
amet suscipit. Ut aliquam vestibulum justo sit amet congue.
Mauris nisl lacus, varius a ultrices id, suscipit mattis libero.
</p>
<p>
Donec iaculis dapibus purus in accumsan. Cras faucibus, orci
dictum molestie congue, neque magna adipiscing lacus, ut laoreet
sapien ante et mi. Suspendisse potenti. Nullam sed dui magna.
Nullam vel purus augue, imperdiet vehicula purus. Lorem ipsum
dolor sit amet, consectetur adipiscing elit. In aliquet, ante at
tincidunt ultricies, arcu dolor rutrum odio, quis tempus lacus
leo a quam. Fusce hendrerit, urna sed elementum pulvinar, dolor
sem imperdiet arcu, ut ornare erat metus sit amet diam. Duis
sagittis libero ut metus vulputate dictum. Etiam eget augue
dolor, in lacinia felis. Nulla facilisi. Proin sollicitudin
laoreet vehicula. Praesent non nibh odio.
</p>
</div>
</body>
</html>
--------------020203040006070307010003--