1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

Basic MIME parsing is now integrated, this closes #1

This commit is contained in:
James Hillyerd
2012-10-17 21:10:02 -07:00
parent e76fad1523
commit 0efb28ef38
4 changed files with 68 additions and 52 deletions

View File

@@ -64,10 +64,11 @@ func (c Mailbox) Show(name string, id string) rev.Result {
if err != nil { if err != nil {
return c.RenderError(err) return c.RenderError(err)
} }
_, body, err := message.ReadBody() _, mime, err := message.ReadBody()
if err != nil { if err != nil {
return c.RenderError(err) return c.RenderError(err)
} }
body := mime.Text
c.Response.Out.Header().Set("Expires", "-1") c.Response.Out.Header().Set("Expires", "-1")
return c.Render(name, message, body) return c.Render(name, message, body)

View File

@@ -174,8 +174,10 @@ func (m *Message) ReadHeader() (msg *mail.Message, err error) {
return msg, err return msg, err
} }
// ReadBody opens the .raw portion of a Message and returns a standard Go mail.Message object // ReadBody opens the .raw portion of a Message and returns a MIMEBody object, along
func (m *Message) ReadBody() (msg *mail.Message, body *string, err error) { // 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()) file, err := os.Open(m.rawPath())
defer file.Close() defer file.Close()
if err != nil { if err != nil {
@@ -186,12 +188,11 @@ func (m *Message) ReadBody() (msg *mail.Message, body *string, err error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
bodyBytes, err := ioutil.ReadAll(reader) mime, err := ParseMIMEBody(msg)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
bodyString := string(bodyBytes) return msg, mime, err
return msg, &bodyString, err
} }
// ReadRaw opens the .raw portion of a Message and returns it as a string // ReadRaw opens the .raw portion of a Message and returns it as a string

View File

@@ -5,6 +5,7 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"mime" "mime"
"mime/multipart" "mime/multipart"
"net/mail" "net/mail"
@@ -20,7 +21,7 @@ type MIMENode struct {
Content []byte Content []byte
} }
type MIMEMessage struct { type MIMEBody struct {
Text string Text string
Html string Html string
Root *MIMENode Root *MIMENode
@@ -79,50 +80,59 @@ func IsMIMEMessage(mailMsg *mail.Message) bool {
return false return false
} }
func ParseMIMEMessage(mailMsg *mail.Message) (*MIMEMessage, error) { func ParseMIMEBody(mailMsg *mail.Message) (*MIMEBody, error) {
mimeMsg := new(MIMEMessage) mimeMsg := new(MIMEBody)
// Parse top-level multipart if !IsMIMEMessage(mailMsg) {
ctype := mailMsg.Header.Get("Content-Type") // Parse as text only
mediatype, params, err := mime.ParseMediaType(ctype) bodyBytes, err := ioutil.ReadAll(mailMsg.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch mediatype { mimeMsg.Text = string(bodyBytes)
case "multipart/alternative": } else {
// Good // Parse top-level multipart
default: ctype := mailMsg.Header.Get("Content-Type")
return nil, fmt.Errorf("Unknown mediatype: %v", mediatype) mediatype, params, err := mime.ParseMediaType(ctype)
} if err != nil {
boundary := params["boundary"] return nil, err
if boundary == "" { }
return nil, fmt.Errorf("Unable to locate boundary param in Content-Type header") 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 return mimeMsg, nil
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
} }
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) return fmt.Sprintf("----TEXT----\n%v\n----HTML----\n%v\n----END----\n", m.Text, m.Html)
} }

View File

@@ -23,16 +23,20 @@ func TestIdentifyMime(t *testing.T) {
func TestParseNonMime(t *testing.T) { func TestParseNonMime(t *testing.T) {
msg := readMessage("non-mime.raw") msg := readMessage("non-mime.raw")
_, err := ParseMIMEMessage(msg) mime, err := ParseMIMEBody(msg)
assert.NotNil(t, err, "Expected error parsing a non-MIME message") 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) { func TestParseInlineText(t *testing.T) {
msg := readMessage("html-mime-inline.raw") msg := readMessage("html-mime-inline.raw")
mime, err := ParseMIMEMessage(msg) mime, err := ParseMIMEBody(msg)
if err != nil { 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") assert.Equal(t, mime.Text, "Test of HTML section")
@@ -41,9 +45,9 @@ func TestParseInlineText(t *testing.T) {
func TestParseInlineHtml(t *testing.T) { func TestParseInlineHtml(t *testing.T) {
msg := readMessage("html-mime-inline.raw") msg := readMessage("html-mime-inline.raw")
mime, err := ParseMIMEMessage(msg) mime, err := ParseMIMEBody(msg)
if err != nil { if err != nil {
t.Fatalf("Failed to parse mime: %v", err) t.Fatalf("Failed to parse MIME: %v", err)
} }
assert.Contains(t, mime.Html, "<html>") assert.Contains(t, mime.Html, "<html>")