From 45b863f931314a5628005c149fe835c6d21f3eb6 Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Thu, 2 Jul 2020 12:50:09 +0200 Subject: [PATCH] feat: parse most header values --- internal/imap/mailbox_message.go | 2 +- internal/smtp/user.go | 3 +- pkg/message/header.go | 73 ---- pkg/message/parser.go | 608 +++++++------------------------ pkg/message/parser_test.go | 119 +----- 5 files changed, 147 insertions(+), 658 deletions(-) diff --git a/internal/imap/mailbox_message.go b/internal/imap/mailbox_message.go index 730f7079..93f46d16 100644 --- a/internal/imap/mailbox_message.go +++ b/internal/imap/mailbox_message.go @@ -68,7 +68,7 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L // Called from go-imap in goroutines - we need to handle panics for each function. defer im.panicHandler.HandlePanic() - m, _, _, readers, err := message.Parse(body, "", "") + m, _, _, readers, err := message.Parse(body) if err != nil { return err } diff --git a/internal/smtp/user.go b/internal/smtp/user.go index ae14f30f..521b8972 100644 --- a/internal/smtp/user.go +++ b/internal/smtp/user.go @@ -182,7 +182,8 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err attachedPublicKeyName = "publickey - " + kr.GetIdentities()[0].Name } - message, mimeBody, plainBody, attReaders, err := message.Parse(messageReader, attachedPublicKey, attachedPublicKeyName) + // TODO: Include public keys here! + message, mimeBody, plainBody, attReaders, err := message.Parse(messageReader) if err != nil { return } diff --git a/pkg/message/header.go b/pkg/message/header.go index f2b780f0..5fda7cc4 100644 --- a/pkg/message/header.go +++ b/pkg/message/header.go @@ -19,7 +19,6 @@ package message import ( "mime" - "net/mail" "net/textproto" "strings" "time" @@ -140,75 +139,3 @@ func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader { return h } - -// ========= Header parsing and sanitizing functions ========= - -func parseHeader(h mail.Header) (m *pmapi.Message, err error) { //nolint[unparam] - m = pmapi.NewMessage() - - if subject, err := pmmime.DecodeHeader(h.Get("Subject")); err == nil { - m.Subject = subject - } - if addrs, err := sanitizeAddressList(h, "From"); err == nil && len(addrs) > 0 { - m.Sender = addrs[0] - } - if addrs, err := sanitizeAddressList(h, "Reply-To"); err == nil && len(addrs) > 0 { - m.ReplyTos = addrs - } - if addrs, err := sanitizeAddressList(h, "To"); err == nil { - m.ToList = addrs - } - if addrs, err := sanitizeAddressList(h, "Cc"); err == nil { - m.CCList = addrs - } - if addrs, err := sanitizeAddressList(h, "Bcc"); err == nil { - m.BCCList = addrs - } - m.Time = 0 - if t, err := h.Date(); err == nil && !t.IsZero() { - m.Time = t.Unix() - } - - m.Header = h - return -} - -func sanitizeAddressList(h mail.Header, field string) (addrs []*mail.Address, err error) { - raw := h.Get(field) - if raw == "" { - err = mail.ErrHeaderNotPresent - return - } - var decoded string - decoded, err = pmmime.DecodeHeader(raw) - if err != nil { - return - } - addrs, err = mail.ParseAddressList(parseAddressComment(decoded)) - if err == nil { - if addrs == nil { - addrs = []*mail.Address{} - } - return - } - // Probably missing encoding error -- try to at least parse addresses in brackets. - addrStr := h.Get(field) - first := strings.Index(addrStr, "<") - last := strings.LastIndex(addrStr, ">") - if first < 0 || last < 0 || first >= last { - return - } - var addrList []string - open := first - for open < last && 0 <= open { - addrStr = addrStr[open:] - close := strings.Index(addrStr, ">") - addrList = append(addrList, addrStr[:close+1]) - addrStr = addrStr[close:] - open = strings.Index(addrStr, "<") - last = strings.LastIndex(addrStr, ">") - } - addrStr = strings.Join(addrList, ", ") - // - return mail.ParseAddressList(addrStr) -} diff --git a/pkg/message/parser.go b/pkg/message/parser.go index dab77874..cebe5340 100644 --- a/pkg/message/parser.go +++ b/pkg/message/parser.go @@ -19,466 +19,102 @@ package message import ( "bytes" - "crypto/sha256" - "encoding/base64" - "encoding/hex" - "fmt" "io" - "io/ioutil" - "math/rand" "mime" - "mime/quotedprintable" "net/mail" - "net/textproto" - "regexp" - "strconv" "strings" "github.com/ProtonMail/proton-bridge/pkg/message/parser" - pmmime "github.com/ProtonMail/proton-bridge/pkg/mime" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/emersion/go-message" "github.com/jaytaylor/html2text" ) -func parseAttachment(filename string, mediaType string, h textproto.MIMEHeader) (att *pmapi.Attachment) { - if decoded, err := pmmime.DecodeHeader(filename); err == nil { - filename = decoded - } - if filename == "" { - ext, err := mime.ExtensionsByType(mediaType) - if err == nil && len(ext) > 0 { - filename = "attachment" + ext[0] - } - } - - att = &pmapi.Attachment{ - Name: filename, - MIMEType: mediaType, - Header: h, - } - - headerContentID := strings.Trim(h.Get("Content-Id"), " <>") - - if headerContentID != "" { - att.ContentID = headerContentID - } - - return -} - -var reEmailComment = regexp.MustCompile("[(][^)]*[)]") //nolint[gochecknoglobals] - -// parseAddressComment removes the comments completely even though they should be allowed -// http://tools.wordtothewise.com/rfc/822 -// NOTE: This should be supported in go>1.10 but it seems it's not ¯\_(ツ)_/¯ -func parseAddressComment(raw string) string { - return reEmailComment.ReplaceAllString(raw, "") -} - -// Some clients incorrectly format messages with embedded attachments to have a format like -// I. text/plain II. attachment III. text/plain -// which we need to convert to a single HTML part with an embedded attachment. -func combineParts(m *pmapi.Message, parts []io.Reader, headers []textproto.MIMEHeader, convertPlainToHTML bool, atts *[]io.Reader) (isHTML bool, err error) { //nolint[funlen] - isHTML = true - foundText := false - - for i := len(parts) - 1; i >= 0; i-- { - part := parts[i] - h := headers[i] - - disp, dispParams, _ := pmmime.ParseMediaType(h.Get("Content-Disposition")) - - d := pmmime.DecodeContentEncoding(part, h.Get("Content-Transfer-Encoding")) - if d == nil { - log.Warnf("Unsupported Content-Transfer-Encoding '%v'", h.Get("Content-Transfer-Encoding")) - d = part - } - - contentType := h.Get("Content-Type") - if contentType == "" { - contentType = "text/plain" - } - mediaType, params, _ := pmmime.ParseMediaType(contentType) - - if strings.HasPrefix(mediaType, "text/") && mediaType != "text/calendar" && disp != "attachment" { - // This is text. - var b []byte - if b, err = ioutil.ReadAll(d); err != nil { - continue - } - b, err = pmmime.DecodeCharset(b, contentType) - if err != nil { - log.Warn("Decode charset error: ", err) - return false, err - } - contents := string(b) - if strings.Contains(mediaType, "text/plain") && len(contents) > 0 { - if !convertPlainToHTML { - isHTML = false - } else { - contents = plaintextToHTML(contents) - } - } else if strings.Contains(mediaType, "text/html") && len(contents) > 0 { - contents, err = stripHTML(contents) - if err != nil { - return isHTML, err - } - } - m.Body = contents + m.Body - foundText = true - } else { - // This is an attachment. - filename := dispParams["filename"] - if filename == "" { - // Using "name" in Content-Type is discouraged. - filename = params["name"] - } - if filename == "" && mediaType == "text/calendar" { - filename = "event.ics" - } - - att := parseAttachment(filename, mediaType, h) - - b := &bytes.Buffer{} - if d == nil { - continue - } - if _, err = io.Copy(b, d); err != nil { - continue - } - if foundText && att.ContentID == "" && strings.Contains(mediaType, "image") { - // Treat this as an inline attachment even though it is not marked as one. - hasher := sha256.New() - _, _ = hasher.Write([]byte(att.Name + strconv.Itoa(b.Len()))) - bytes := hasher.Sum(nil) - cid := hex.EncodeToString(bytes) + "@protonmail.com" - - att.ContentID = cid - embeddedHTML := makeEmbeddedImageHTML(cid, att.Name) - m.Body = embeddedHTML + m.Body - } - - m.Attachments = append(m.Attachments, att) - *atts = append(*atts, b) - } - } - if isHTML { - m.Body = addOuterHTMLTags(m.Body) - } - return isHTML, nil -} - -func checkHeaders(headers []textproto.MIMEHeader) bool { - foundAttachment := false - - for i := 0; i < len(headers); i++ { - h := headers[i] - - mediaType, _, _ := pmmime.ParseMediaType(h.Get("Content-Type")) - - if !strings.HasPrefix(mediaType, "text/") { - foundAttachment = true - } else if foundAttachment { - // This means that there is a text part after the first attachment, - // so we will have to convert the body from plain->HTML. - return true - } - } - return false -} - -// ============================== 7bit Filter ========================== -// For every MIME part in the tree that has "8bit" or "binary" content -// transfer encoding: transcode it to "quoted-printable". - -type SevenBitFilter struct { - target pmmime.VisitAcceptor -} - -func NewSevenBitFilter(targetAccepter pmmime.VisitAcceptor) *SevenBitFilter { - return &SevenBitFilter{ - target: targetAccepter, - } -} - -func decodePart(partReader io.Reader, header textproto.MIMEHeader) (decodedPart io.Reader) { - decodedPart = pmmime.DecodeContentEncoding(partReader, header.Get("Content-Transfer-Encoding")) - if decodedPart == nil { - log.Warnf("Unsupported Content-Transfer-Encoding '%v'", header.Get("Content-Transfer-Encoding")) - decodedPart = partReader - } - return -} - -func (sd SevenBitFilter) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) error { - cte := strings.ToLower(header.Get("Content-Transfer-Encoding")) - if isFirst && pmmime.IsLeaf(header) && cte != "quoted-printable" && cte != "base64" && cte != "7bit" { - decodedPart := decodePart(partReader, header) - - filteredHeader := textproto.MIMEHeader{} - for k, v := range header { - filteredHeader[k] = v - } - filteredHeader.Set("Content-Transfer-Encoding", "quoted-printable") - - filteredBuffer := &bytes.Buffer{} - decodedSlice, _ := ioutil.ReadAll(decodedPart) - w := quotedprintable.NewWriter(filteredBuffer) - if _, err := w.Write(decodedSlice); err != nil { - log.Errorf("cannot write quotedprintable from %q: %v", cte, err) - } - if err := w.Close(); err != nil { - log.Errorf("cannot close quotedprintable from %q: %v", cte, err) - } - - _ = sd.target.Accept(filteredBuffer, filteredHeader, hasPlainSibling, true, isLast) - } else { - _ = sd.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast) - } - return nil -} - -// =================== HTML Only convertor ================================== -// In any part of MIME tree structure, replace standalone text/html with -// multipart/alternative containing both text/html and text/plain. - -type HTMLOnlyConvertor struct { - target pmmime.VisitAcceptor -} - -func NewHTMLOnlyConvertor(targetAccepter pmmime.VisitAcceptor) *HTMLOnlyConvertor { - return &HTMLOnlyConvertor{ - target: targetAccepter, - } -} - -func randomBoundary() string { - buf := make([]byte, 30) - - // We specifically use `math/rand` here to allow the generator to be seeded for test purposes. - // The random numbers need not be cryptographically secure; we are simply generating random part boundaries. - if _, err := rand.Read(buf); err != nil { // nolint[gosec] - panic(err) - } - - return fmt.Sprintf("%x", buf) -} - -func (hoc HTMLOnlyConvertor) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSiblings bool, isFirst, isLast bool) error { - mediaType, _, err := pmmime.ParseMediaType(header.Get("Content-Type")) - if isFirst && err == nil && mediaType == "text/html" && !hasPlainSiblings { - multiPartHeaders := make(textproto.MIMEHeader) - for k, v := range header { - multiPartHeaders[k] = v - } - boundary := randomBoundary() - multiPartHeaders.Set("Content-Type", "multipart/alternative; boundary=\""+boundary+"\"") - childCte := header.Get("Content-Transfer-Encoding") - - _ = hoc.target.Accept(partReader, multiPartHeaders, false, true, false) - - partData, _ := ioutil.ReadAll(partReader) - - htmlChildHeaders := make(textproto.MIMEHeader) - htmlChildHeaders.Set("Content-Transfer-Encoding", childCte) - htmlChildHeaders.Set("Content-Type", "text/html") - htmlReader := bytes.NewReader(partData) - _ = hoc.target.Accept(htmlReader, htmlChildHeaders, false, true, false) - - _ = hoc.target.Accept(partReader, multiPartHeaders, hasPlainSiblings, false, false) - - plainChildHeaders := make(textproto.MIMEHeader) - plainChildHeaders.Set("Content-Transfer-Encoding", childCte) - plainChildHeaders.Set("Content-Type", "text/plain") - unHtmlized, err := html2text.FromReader(bytes.NewReader(partData)) - if err != nil { - unHtmlized = string(partData) - } - plainReader := strings.NewReader(unHtmlized) - _ = hoc.target.Accept(plainReader, plainChildHeaders, false, true, true) - - _ = hoc.target.Accept(partReader, multiPartHeaders, hasPlainSiblings, false, true) - } else { - _ = hoc.target.Accept(partReader, header, hasPlainSiblings, isFirst, isLast) - } - return nil -} - -// ======= Public Key Attacher ======== - -type PublicKeyAttacher struct { - target pmmime.VisitAcceptor - attachedPublicKey string - attachedPublicKeyName string - appendToMultipart bool - depth int -} - -func NewPublicKeyAttacher(targetAccepter pmmime.VisitAcceptor, attachedPublicKey, attachedPublicKeyName string) *PublicKeyAttacher { - return &PublicKeyAttacher{ - target: targetAccepter, - attachedPublicKey: attachedPublicKey, - attachedPublicKeyName: attachedPublicKeyName, - appendToMultipart: false, - depth: 0, - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func split(input string, sliceLength int) string { - processed := input - result := "" - for len(processed) > 0 { - cutPoint := min(sliceLength, len(processed)) - part := processed[0:cutPoint] - result = result + part + "\n" - processed = processed[cutPoint:] - } - return result -} - -func createKeyAttachment(publicKey, publicKeyName string) (headers textproto.MIMEHeader, contents io.Reader) { - attachmentHeaders := make(textproto.MIMEHeader) - attachmentHeaders.Set("Content-Type", "application/pgp-key; name=\""+publicKeyName+"\"") - attachmentHeaders.Set("Content-Transfer-Encoding", "base64") - attachmentHeaders.Set("Content-Disposition", "attachment; filename=\""+publicKeyName+".asc.pgp\"") - - buffer := &bytes.Buffer{} - w := base64.NewEncoder(base64.StdEncoding, buffer) - _, _ = w.Write([]byte(publicKey)) - _ = w.Close() - - return attachmentHeaders, strings.NewReader(split(buffer.String(), 73)) -} - -func (pka *PublicKeyAttacher) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSiblings bool, isFirst, isLast bool) error { - if isFirst && !pmmime.IsLeaf(header) { - pka.depth++ - } - if isLast && !pmmime.IsLeaf(header) { - defer func() { - pka.depth-- - }() - } - isRoot := (header.Get("From") != "") - - // NOTE: This should also work for unspecified Content-Type (in which case us-ascii text/plain is assumed)! - mediaType, _, err := pmmime.ParseMediaType(header.Get("Content-Type")) - if isRoot && isFirst && err == nil && pka.attachedPublicKey != "" { //nolint[gocritic] - if strings.HasPrefix(mediaType, "multipart/mixed") { - pka.appendToMultipart = true - _ = pka.target.Accept(partReader, header, hasPlainSiblings, isFirst, isLast) - } else { - // Create two siblings with attachment in the case toplevel is not multipart/mixed. - multiPartHeaders := make(textproto.MIMEHeader) - for k, v := range header { - multiPartHeaders[k] = v - } - boundary := randomBoundary() - multiPartHeaders.Set("Content-Type", "multipart/mixed; boundary=\""+boundary+"\"") - multiPartHeaders.Del("Content-Transfer-Encoding") - - _ = pka.target.Accept(partReader, multiPartHeaders, false, true, false) - - originalHeader := make(textproto.MIMEHeader) - originalHeader.Set("Content-Type", header.Get("Content-Type")) - if header.Get("Content-Transfer-Encoding") != "" { - originalHeader.Set("Content-Transfer-Encoding", header.Get("Content-Transfer-Encoding")) - } - - _ = pka.target.Accept(partReader, originalHeader, false, true, false) - _ = pka.target.Accept(partReader, multiPartHeaders, hasPlainSiblings, false, false) - - attachmentHeaders, attachmentReader := createKeyAttachment(pka.attachedPublicKey, pka.attachedPublicKeyName) - - _ = pka.target.Accept(attachmentReader, attachmentHeaders, false, true, true) - _ = pka.target.Accept(partReader, multiPartHeaders, hasPlainSiblings, false, true) - } - } else if isLast && pka.depth == 1 && pka.attachedPublicKey != "" { - _ = pka.target.Accept(partReader, header, hasPlainSiblings, isFirst, false) - attachmentHeaders, attachmentReader := createKeyAttachment(pka.attachedPublicKey, pka.attachedPublicKeyName) - _ = pka.target.Accept(attachmentReader, attachmentHeaders, hasPlainSiblings, true, true) - _ = pka.target.Accept(partReader, header, hasPlainSiblings, isFirst, true) - } else { - _ = pka.target.Accept(partReader, header, hasPlainSiblings, isFirst, isLast) - } - return nil -} - -// ======= Parser ========== -func parseGoMessageHeader(h message.Header) (m *pmapi.Message, err error) { - m = pmapi.NewMessage() - - m.Header = make(mail.Header) - - fields := h.Fields() - - for fields.Next() { - switch strings.ToLower(fields.Key()) { - case "subject": - if m.Subject, err = fields.Text(); err != nil { - return - } - - // TODO: Set these thingies. - case "from": - case "to": - case "reply-to": - case "cc": - case "bcc": - case "date": - } - } - - return -} - -func ParseGoMessage(r io.Reader) (m *pmapi.Message, mimeBody string, plainContents string, atts []io.Reader, err error) { - // Parse the message. +func Parse(r io.Reader) (m *pmapi.Message, mimeBody, plainBody string, atts []io.Reader, err error) { p, err := parser.New(r) if err != nil { return } - // Collect attachments, convert html to plaintext. - walker := p. + m = pmapi.NewMessage() + + if err = parseHeader(m, p.Header()); err != nil { + return + } + + if m.Attachments, atts, err = collectAttachments(p); err != nil { + return + } + + parts, plainParts, err := collectBodyParts(p) + if err != nil { + return + } + m.Body = strings.Join(parts, "\r\n") + plainBody = strings.Join(plainParts, "\r\n") + + if mimeBody, err = writeMimeBody(p); err != nil { + return + } + + return +} + +func collectAttachments(p *parser.Parser) (atts []*pmapi.Attachment, data []io.Reader, err error) { + w := p. NewWalker(). WithContentDispositionHandler("attachment", func(p *parser.Part, _ parser.PartHandler) (err error) { - atts = append(atts, bytes.NewReader(p.Body)) - return - }). - WithContentTypeHandler("text/html", func(p *parser.Part) (err error) { - plain, err := html2text.FromString(string(p.Body)) + att, err := parseAttachment(p.Header) if err != nil { - plain = string(p.Body) + return } - // TODO: Do we need newline here? - plainContents += plain + atts = append(atts, att) + data = append(data, bytes.NewReader(p.Body)) return }) - if err = walker.Walk(); err != nil { + if err = w.Walk(); err != nil { return } - // Write out a mime body that doesn't include attachments. + return +} + +func collectBodyParts(p *parser.Parser) (parts, plainParts []string, err error) { + w := p. + NewWalker(). + WithContentTypeHandler("text/plain", func(p *parser.Part) (err error) { + parts = append(parts, string(p.Body)) + plainParts = append(plainParts, string(p.Body)) + return + }). + WithContentTypeHandler("text/html", func(p *parser.Part) (err error) { + parts = append(parts, string(p.Body)) + + text, err := html2text.FromString(string(p.Body)) + if err != nil { + text = string(p.Body) + } + plainParts = append(plainParts, text) + + return + }) + + if err = w.Walk(); err != nil { + return + } + + return +} + +func writeMimeBody(p *parser.Parser) (mimeBody string, err error) { writer := p. NewWriter(). WithCondition(func(p *parser.Part) (keep bool) { - if disp, _, err := p.Header.ContentDisposition(); err == nil && disp == "attachment" { - return false - } - - return true + disp, _, err := p.Header.ContentDisposition() + return err != nil || disp != "attachment" }) buf := new(bytes.Buffer) @@ -487,72 +123,80 @@ func ParseGoMessage(r io.Reader) (m *pmapi.Message, mimeBody string, plainConten return } - mimeBody = buf.String() + return buf.String(), nil +} - // Parse the header to build a pmapi message. - if m, err = parseGoMessageHeader(p.Header()); err != nil { - return +func parseHeader(m *pmapi.Message, h message.Header) (err error) { + m.Header = make(mail.Header) + + fields := h.Fields() + + for fields.Next() { + var text string + + if text, err = fields.Text(); err != nil { + return + } + + switch strings.ToLower(fields.Key()) { + case "subject": + m.Subject = text + + case "from": + if m.Sender, err = mail.ParseAddress(text); err != nil { + return + } + + case "to": + if m.ToList, err = mail.ParseAddressList(text); err != nil { + return + } + + case "reply-to": + if m.ReplyTos, err = mail.ParseAddressList(text); err != nil { + return + } + + case "cc": + if m.CCList, err = mail.ParseAddressList(text); err != nil { + return + } + + case "bcc": + if m.BCCList, err = mail.ParseAddressList(text); err != nil { + return + } + + case "date": + // TODO + } } return } -func Parse(r io.Reader, attachedPublicKey, attachedPublicKeyName string) (m *pmapi.Message, mimeBody string, plainContents string, atts []io.Reader, err error) { - secondReader := new(bytes.Buffer) - _, _ = secondReader.ReadFrom(r) +func parseAttachment(h message.Header) (att *pmapi.Attachment, err error) { + att = &pmapi.Attachment{} - mimeBody = secondReader.String() - - mm, err := mail.ReadMessage(secondReader) - if err != nil { + if att.MIMEType, _, err = h.ContentType(); err != nil { return } - if m, err = parseHeader(mm.Header); err != nil { - return - } + if _, dispParams, dispErr := h.ContentDisposition(); dispErr != nil { + var ext []string - h := textproto.MIMEHeader(m.Header) - mmBodyData, err := ioutil.ReadAll(mm.Body) - if err != nil { - return - } + if ext, err = mime.ExtensionsByType(att.MIMEType); err != nil { + return + } - printAccepter := pmmime.NewMIMEPrinter() - - publicKeyAttacher := NewPublicKeyAttacher(printAccepter, attachedPublicKey, attachedPublicKeyName) - sevenBitFilter := NewSevenBitFilter(publicKeyAttacher) - - plainTextCollector := pmmime.NewPlainTextCollector(sevenBitFilter) - htmlOnlyConvertor := NewHTMLOnlyConvertor(plainTextCollector) - - visitor := pmmime.NewMimeVisitor(htmlOnlyConvertor) - err = pmmime.VisitAll(bytes.NewReader(mmBodyData), h, visitor) - /* - err = visitor.VisitAll(h, bytes.NewReader(mmBodyData)) - */ - if err != nil { - return - } - - mimeBody = printAccepter.String() - - plainContents = plainTextCollector.GetPlainText() - - parts, headers, err := pmmime.GetAllChildParts(bytes.NewReader(mmBodyData), h) - - if err != nil { - return - } - - convertPlainToHTML := checkHeaders(headers) - isHTML, err := combineParts(m, parts, headers, convertPlainToHTML, &atts) - - if isHTML { - m.MIMEType = "text/html" + if len(ext) > 0 { + att.Name = "attachment" + ext[0] + } } else { - m.MIMEType = "text/plain" + att.Name = dispParams["filename"] } - return m, mimeBody, plainContents, atts, err + // TODO: Set att.Header + + return } diff --git a/pkg/message/parser_test.go b/pkg/message/parser_test.go index 0826e63e..53bbf13b 100644 --- a/pkg/message/parser_test.go +++ b/pkg/message/parser_test.go @@ -22,7 +22,6 @@ import ( "io" "io/ioutil" "math/rand" - "net/mail" "os" "path/filepath" "testing" @@ -31,90 +30,6 @@ import ( "golang.org/x/text/encoding/charmap" ) -func TestRFC822AddressFormat(t *testing.T) { //nolint[funlen] - tests := []struct { - address string - expected []string - }{ - { - " normal name ", - []string{ - "\"normal name\" ", - }, - }, - { - " \"comma, name\" ", - []string{ - "\"comma, name\" ", - }, - }, - { - " name (ignore comment)", - []string{ - "\"name\" ", - }, - }, - { - " name (ignore comment) , (Comment as name) username2@server.com", - []string{ - "\"name\" ", - "", - }, - }, - { - " normal name , (comment)All.(around)address@(the)server.com", - []string{ - "\"normal name\" ", - "", - }, - }, - { - " normal name , All.(\"comma, in comment\")address@(the)server.com", - []string{ - "\"normal name\" ", - "", - }, - }, - { - " \"normal name\" , \"comma, name\" ", - []string{ - "\"normal name\" ", - "\"comma, name\" ", - }, - }, - { - " \"comma, one\" , \"comma, two\" ", - []string{ - "\"comma, one\" ", - "\"comma, two\" ", - }, - }, - { - " \"comma, name\" , another, name ", - []string{ - "\"comma, name\" ", - "\"another, name\" ", - }, - }, - } - - for _, data := range tests { - uncommented := parseAddressComment(data.address) - result, err := mail.ParseAddressList(uncommented) - if err != nil { - t.Errorf("Can not parse '%s' created from '%s': %v", uncommented, data.address, err) - } - if len(result) != len(data.expected) { - t.Errorf("Wrong parsing of '%s' created from '%s': expected '%s' but have '%+v'", uncommented, data.address, data.expected, result) - } - for i, result := range result { - if data.expected[i] != result.String() { - t.Errorf("Wrong parsing\nof %q\ncreated from %q:\nexpected %q\nbut have %q", uncommented, data.address, data.expected, result.String()) - } - } - } -} - func f(filename string) io.ReadCloser { f, err := os.Open(filepath.Join("testdata", filename)) @@ -149,7 +64,7 @@ func TestParseMessageTextPlain(t *testing.T) { f := f("text_plain.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -166,7 +81,7 @@ func TestParseMessageTextPlainUTF8(t *testing.T) { f := f("text_plain_utf8.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -183,7 +98,7 @@ func TestParseMessageTextPlainLatin1(t *testing.T) { f := f("text_plain_latin1.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -200,7 +115,7 @@ func TestParseMessageTextPlainUnknownCharsetIsActuallyLatin1(t *testing.T) { f := f("text_plain_unknown_latin1.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -217,7 +132,7 @@ func TestParseMessageTextPlainUnknownCharsetIsActuallyLatin2(t *testing.T) { f := f("text_plain_unknown_latin2.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -240,7 +155,7 @@ func TestParseMessageTextPlainAlready7Bit(t *testing.T) { f := f("text_plain_7bit.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -257,7 +172,7 @@ func TestParseMessageTextPlainWithOctetAttachment(t *testing.T) { f := f("text_plain_octet_attachment.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -313,7 +228,7 @@ func TestParseMessageTextPlainWithPlainAttachment(t *testing.T) { f := f("text_plain_plain_attachment.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -331,7 +246,7 @@ func TestParseMessageTextPlainWithImageInline(t *testing.T) { f := f("text_plain_image_inline.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -353,7 +268,7 @@ func TestParseMessageWithMultipleTextParts(t *testing.T) { f := f("multiple_text_parts.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -372,7 +287,7 @@ func TestParseMessageTextHTML(t *testing.T) { f := f("text_html.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -391,7 +306,7 @@ func TestParseMessageTextHTMLAlready7Bit(t *testing.T) { f := f("text_html_7bit.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -410,7 +325,7 @@ func TestParseMessageTextHTMLWithOctetAttachment(t *testing.T) { f := f("text_html_octet_attachment.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -431,7 +346,7 @@ func _TestParseMessageTextHTMLWithPlainAttachment(t *testing.T) { // nolint[dead f := f("text_html_plain_attachment.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -452,7 +367,7 @@ func TestParseMessageTextHTMLWithImageInline(t *testing.T) { f := f("text_html_image_inline.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String()) @@ -471,6 +386,7 @@ func TestParseMessageTextHTMLWithImageInline(t *testing.T) { } // NOTE: Enable when bug is fixed. +/* func _TestParseMessageWithAttachedPublicKey(t *testing.T) { // nolint[deadcode] f := f("text_plain.eml") defer func() { _ = f.Close() }() @@ -489,6 +405,7 @@ func _TestParseMessageWithAttachedPublicKey(t *testing.T) { // nolint[deadcode] // BAD: Public key not available as an attachment! assert.Len(t, atts, 1) } +*/ // NOTE: Enable when bug is fixed. func _TestParseMessageTextHTMLWithEmbeddedForeignEncoding(t *testing.T) { // nolint[deadcode] @@ -497,7 +414,7 @@ func _TestParseMessageTextHTMLWithEmbeddedForeignEncoding(t *testing.T) { // nol f := f("text_html_embedded_foreign_encoding.eml") defer func() { _ = f.Close() }() - m, mimeBody, plainContents, atts, err := Parse(f, "", "") + m, mimeBody, plainContents, atts, err := Parse(f) assert.NoError(t, err) assert.Equal(t, `"Sender" `, m.Sender.String())