diff --git a/app/inbucket/mime.go b/app/inbucket/mime.go index 8992e08..0d6116f 100644 --- a/app/inbucket/mime.go +++ b/app/inbucket/mime.go @@ -7,6 +7,7 @@ import ( "mime" "mime/multipart" "net/mail" + "os" ) const MIME_BUF_BYTES = 1024 @@ -16,6 +17,30 @@ type MIMEMessage struct { html string } +type MIMENode struct { + Parent *MIMENode + FirstChild *MIMENode + NextSibling *MIMENode + Type string + Content *bytes.Buffer +} + +func NewMIMENode(parent *MIMENode, contentType string) *MIMENode { + return &MIMENode{Parent: parent, Type: contentType} +} + +func (n *MIMENode) String() string { + children := "" + siblings := "" + if n.FirstChild != nil { + children = n.FirstChild.String() + } + if n.NextSibling != nil { + siblings = n.NextSibling.String() + } + return fmt.Sprintf("[%v %v] %v", n.Type, children, siblings) +} + func ParseMIMEMessage(mailMsg *mail.Message) (*MIMEMessage, error) { mimeMsg := new(MIMEMessage) @@ -36,11 +61,19 @@ func ParseMIMEMessage(mailMsg *mail.Message) (*MIMEMessage, error) { return nil, fmt.Errorf("Unable to locate boundary param in Content-Type header") } - // Buffer used by Part.Read to hand us chunks of data - readBytes := make([]byte, MIME_BUF_BYTES) + // Root Node of our tree + root := NewMIMENode(nil, mediatype) + + err = parseNodes(root, mailMsg.Body, boundary) + fmt.Println(root.String()) + return mimeMsg, err +} + +func parseNodes(parent *MIMENode, reader io.Reader, boundary string) error { + var prevSibling *MIMENode // Loop over MIME parts - mr := multipart.NewReader(mailMsg.Body, boundary) + mr := multipart.NewReader(reader, boundary) for { part, err := mr.NextPart() if err != nil { @@ -48,34 +81,41 @@ func ParseMIMEMessage(mailMsg *mail.Message) (*MIMEMessage, error) { // This is a clean end-of-message signal break } - return nil, err + return err } - mediatype, params, err = mime.ParseMediaType(part.Header.Get("Content-Type")) + mediatype, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { - return nil, err + return err } - if mediatype == "text/plain" && mimeMsg.text == "" { - // First text section, we'll have that, but first we have to deal with - // the odd way Part lets us read data. - var buf bytes.Buffer - for { - n, err := part.Read(readBytes) - if err != nil { - if err == io.EOF { - // Clean end of part signal - break - } - return nil, err - } - // Extra data in readBytes is not cleared, so we must respect - // the value returned in 'n' - buf.Write(readBytes[:n]) - } - mimeMsg.text = buf.String() - fmt.Printf("text: '%v'\n", mimeMsg.text) + // Insert ourselves into tree + node := NewMIMENode(parent, mediatype) + if prevSibling != nil { + prevSibling.NextSibling = node + } else { + parent.FirstChild = node + } + prevSibling = node + + boundary := params["boundary"] + if boundary != "" { + // Content is another multipart + err = parseNodes(node, part, boundary) + if err != nil { + return err + } + } else { + // Content is data, allocate a buffer + node.Content = new(bytes.Buffer) + _, err = io.Copy(node.Content, part) + if err != nil { + return err + } + fmt.Printf("\n----\n") + io.Copy(os.Stdout, node.Content) + fmt.Printf("\n----\n") } } - return mimeMsg, nil + return nil }