From 0efb28ef384cf82ae9d8be515d7ebc75190ae7c7 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Wed, 17 Oct 2012 21:10:02 -0700 Subject: [PATCH] Basic MIME parsing is now integrated, this closes #1 --- app/controllers/mailbox.go | 3 +- app/inbucket/datastore.go | 11 ++--- app/inbucket/mime.go | 90 +++++++++++++++++++++----------------- app/inbucket/mime_test.go | 16 ++++--- 4 files changed, 68 insertions(+), 52 deletions(-) diff --git a/app/controllers/mailbox.go b/app/controllers/mailbox.go index 02ba4be..c4bc434 100644 --- a/app/controllers/mailbox.go +++ b/app/controllers/mailbox.go @@ -64,10 +64,11 @@ func (c Mailbox) Show(name string, id string) rev.Result { if err != nil { return c.RenderError(err) } - _, body, err := message.ReadBody() + _, mime, err := message.ReadBody() if err != nil { return c.RenderError(err) } + body := mime.Text c.Response.Out.Header().Set("Expires", "-1") return c.Render(name, message, body) diff --git a/app/inbucket/datastore.go b/app/inbucket/datastore.go index 42e99d8..8986932 100644 --- a/app/inbucket/datastore.go +++ b/app/inbucket/datastore.go @@ -174,8 +174,10 @@ func (m *Message) ReadHeader() (msg *mail.Message, err error) { return msg, err } -// ReadBody opens the .raw portion of a Message and returns a standard Go mail.Message object -func (m *Message) ReadBody() (msg *mail.Message, body *string, err error) { +// ReadBody opens the .raw portion of a Message and returns a MIMEBody object, along +// with a free mail.Message containing the Headers, since we had to make one of those +// anyway. +func (m *Message) ReadBody() (msg *mail.Message, body *MIMEBody, err error) { file, err := os.Open(m.rawPath()) defer file.Close() if err != nil { @@ -186,12 +188,11 @@ func (m *Message) ReadBody() (msg *mail.Message, body *string, err error) { if err != nil { return nil, nil, err } - bodyBytes, err := ioutil.ReadAll(reader) + mime, err := ParseMIMEBody(msg) if err != nil { return nil, nil, err } - bodyString := string(bodyBytes) - return msg, &bodyString, err + return msg, mime, err } // ReadRaw opens the .raw portion of a Message and returns it as a string diff --git a/app/inbucket/mime.go b/app/inbucket/mime.go index a8393b3..6142a1f 100644 --- a/app/inbucket/mime.go +++ b/app/inbucket/mime.go @@ -5,6 +5,7 @@ import ( "container/list" "fmt" "io" + "io/ioutil" "mime" "mime/multipart" "net/mail" @@ -20,7 +21,7 @@ type MIMENode struct { Content []byte } -type MIMEMessage struct { +type MIMEBody struct { Text string Html string Root *MIMENode @@ -79,50 +80,59 @@ func IsMIMEMessage(mailMsg *mail.Message) bool { return false } -func ParseMIMEMessage(mailMsg *mail.Message) (*MIMEMessage, error) { - mimeMsg := new(MIMEMessage) +func ParseMIMEBody(mailMsg *mail.Message) (*MIMEBody, error) { + mimeMsg := new(MIMEBody) - // Parse top-level multipart - ctype := mailMsg.Header.Get("Content-Type") - mediatype, params, err := mime.ParseMediaType(ctype) - if err != nil { - return nil, err - } - switch mediatype { - case "multipart/alternative": - // Good - default: - return nil, fmt.Errorf("Unknown mediatype: %v", mediatype) - } - boundary := params["boundary"] - if boundary == "" { - return nil, fmt.Errorf("Unable to locate boundary param in Content-Type header") + if !IsMIMEMessage(mailMsg) { + // Parse as text only + bodyBytes, err := ioutil.ReadAll(mailMsg.Body) + if err != nil { + return nil, err + } + mimeMsg.Text = string(bodyBytes) + } else { + // Parse top-level multipart + ctype := mailMsg.Header.Get("Content-Type") + mediatype, params, err := mime.ParseMediaType(ctype) + if err != nil { + return nil, err + } + switch mediatype { + case "multipart/alternative": + // Good + default: + return nil, fmt.Errorf("Unknown mediatype: %v", mediatype) + } + boundary := params["boundary"] + if boundary == "" { + return nil, fmt.Errorf("Unable to locate boundary param in Content-Type header") + } + + // Root Node of our tree + root := NewMIMENode(nil, mediatype) + err = parseNodes(root, mailMsg.Body, boundary) + + // Locate text body + match := root.BreadthFirstSearch(func(node *MIMENode) bool { + return node.Type == "text/plain" + }) + if match != nil { + mimeMsg.Text = string(match.Content) + } + + // Locate HTML body + match = root.BreadthFirstSearch(func(node *MIMENode) bool { + return node.Type == "text/html" + }) + if match != nil { + mimeMsg.Html = string(match.Content) + } } - // Root Node of our tree - root := NewMIMENode(nil, mediatype) - err = parseNodes(root, mailMsg.Body, boundary) - - // Locate text body - match := root.BreadthFirstSearch(func(node *MIMENode) bool { - return node.Type == "text/plain" - }) - if match != nil { - mimeMsg.Text = string(match.Content) - } - - // Locate HTML body - match = root.BreadthFirstSearch(func(node *MIMENode) bool { - return node.Type == "text/html" - }) - if match != nil { - mimeMsg.Html = string(match.Content) - } - - return mimeMsg, err + return mimeMsg, nil } -func (m *MIMEMessage) String() string { +func (m *MIMEBody) String() string { return fmt.Sprintf("----TEXT----\n%v\n----HTML----\n%v\n----END----\n", m.Text, m.Html) } diff --git a/app/inbucket/mime_test.go b/app/inbucket/mime_test.go index 3675985..743344f 100644 --- a/app/inbucket/mime_test.go +++ b/app/inbucket/mime_test.go @@ -23,16 +23,20 @@ func TestIdentifyMime(t *testing.T) { func TestParseNonMime(t *testing.T) { msg := readMessage("non-mime.raw") - _, err := ParseMIMEMessage(msg) - assert.NotNil(t, err, "Expected error parsing a non-MIME message") + mime, err := ParseMIMEBody(msg) + if err != nil { + t.Fatalf("Failed to parse non-MIME: %v", err) + } + + assert.Contains(t, mime.Text, "This is a test mailing") } func TestParseInlineText(t *testing.T) { msg := readMessage("html-mime-inline.raw") - mime, err := ParseMIMEMessage(msg) + mime, err := ParseMIMEBody(msg) if err != nil { - t.Fatalf("Failed to parse mime: %v", err) + t.Fatalf("Failed to parse MIME: %v", err) } assert.Equal(t, mime.Text, "Test of HTML section") @@ -41,9 +45,9 @@ func TestParseInlineText(t *testing.T) { func TestParseInlineHtml(t *testing.T) { msg := readMessage("html-mime-inline.raw") - mime, err := ParseMIMEMessage(msg) + mime, err := ParseMIMEBody(msg) if err != nil { - t.Fatalf("Failed to parse mime: %v", err) + t.Fatalf("Failed to parse MIME: %v", err) } assert.Contains(t, mime.Html, "")