From 7e1af9ff4e599850bfe51f01432c78e4c6299f97 Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Thu, 6 Aug 2020 10:01:00 +0200 Subject: [PATCH] fix: linter issues --- pkg/message/parser.go | 45 ++-- pkg/message/parser/writer.go | 2 +- pkg/mime/parser.go | 479 ----------------------------------- pkg/mime/parser_test.go | 231 ----------------- 4 files changed, 27 insertions(+), 730 deletions(-) delete mode 100644 pkg/mime/parser.go delete mode 100644 pkg/mime/parser_test.go diff --git a/pkg/message/parser.go b/pkg/message/parser.go index a89f7624..d52b83cf 100644 --- a/pkg/message/parser.go +++ b/pkg/message/parser.go @@ -66,10 +66,16 @@ func Parse(r io.Reader, key, keyName string) (m *pmapi.Message, mimeMessage, pla return } - return + return m, mimeMessage, plainBody, attReaders, nil } -func collectAttachments(p *parser.Parser) (atts []*pmapi.Attachment, data []io.Reader, err error) { +func collectAttachments(p *parser.Parser) ([]*pmapi.Attachment, []io.Reader, error) { + var ( + atts []*pmapi.Attachment + data []io.Reader + err error + ) + w := p.NewWalker(). RegisterContentDispositionHandler("attachment", func(p *parser.Part) error { att, err := parseAttachment(p.Header) @@ -113,10 +119,10 @@ func collectAttachments(p *parser.Parser) (atts []*pmapi.Attachment, data []io.R }) if err = w.Walk(); err != nil { - return + return nil, nil, err } - return + return atts, data, nil } func buildBodies(p *parser.Parser) (richBody, plainBody string, err error) { @@ -162,7 +168,7 @@ func collectBodyParts(p *parser.Parser, preferredContentType string) (parser.Par return nil, err } - return bestChoice(childParts, preferredContentType) + return bestChoice(childParts, preferredContentType), nil }). RegisterRule("text/plain", func(p *parser.Part, visit parser.Visit) (interface{}, error) { return parser.Parts{p}, nil @@ -204,16 +210,16 @@ func joinChildParts(childParts []parser.Parts) parser.Parts { return res } -func bestChoice(childParts []parser.Parts, preferredContentType string) (parser.Parts, error) { +func bestChoice(childParts []parser.Parts, preferredContentType string) parser.Parts { // If one of the parts has preferred content type, use that. for i := len(childParts) - 1; i >= 0; i-- { if allPartsHaveContentType(childParts[i], preferredContentType) { - return childParts[i], nil + return childParts[i] } } // Otherwise, choose the last one. - return childParts[len(childParts)-1], nil + return childParts[len(childParts)-1] } func allPartsHaveContentType(parts parser.Parts, contentType string) bool { @@ -368,25 +374,26 @@ func parseMessageHeader(m *pmapi.Message, h message.Header) error { return nil } -func parseAttachment(h message.Header) (att *pmapi.Attachment, err error) { - att = &pmapi.Attachment{} +func parseAttachment(h message.Header) (*pmapi.Attachment, error) { + att := &pmapi.Attachment{} mimeHeader, err := toMIMEHeader(h) if err != nil { return nil, err } - att.Header = mimeHeader - if att.MIMEType, _, err = h.ContentType(); err != nil { - return + mimeType, _, err := h.ContentType() + if err != nil { + return nil, err } + att.MIMEType = mimeType - if _, dispParams, dispErr := h.ContentDisposition(); dispErr != nil { - var ext []string - - if ext, err = mime.ExtensionsByType(att.MIMEType); err != nil { - return + _, dispParams, dispErr := h.ContentDisposition() + if dispErr != nil { + ext, err := mime.ExtensionsByType(att.MIMEType) + if err != nil { + return nil, err } if len(ext) > 0 { @@ -398,7 +405,7 @@ func parseAttachment(h message.Header) (att *pmapi.Attachment, err error) { att.ContentID = strings.Trim(h.Get("Content-Id"), " <>") - return + return att, nil } func toMailHeader(h message.Header) (mail.Header, error) { diff --git a/pkg/message/parser/writer.go b/pkg/message/parser/writer.go index 0dc70ee1..90994c8c 100644 --- a/pkg/message/parser/writer.go +++ b/pkg/message/parser/writer.go @@ -63,7 +63,7 @@ func (w *Writer) shouldFilter(p *Part) bool { } for _, b := range p.Body { - if uint8(b) > 1<<7 { + if b > 1<<7 { return true } } diff --git a/pkg/mime/parser.go b/pkg/mime/parser.go deleted file mode 100644 index eafa9de9..00000000 --- a/pkg/mime/parser.go +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright (c) 2020 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package pmmime - -import ( - "bytes" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/mail" - "net/textproto" - "regexp" - "strings" - - log "github.com/sirupsen/logrus" -) - -// VisitAcceptor decides what to do with part which is processed. -// It is used by MIMEVisitor. -type VisitAcceptor interface { - Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) -} - -func VisitAll(part io.Reader, h textproto.MIMEHeader, accepter VisitAcceptor) (err error) { - mediaType, _, err := getContentType(h) - if err != nil { - return - } - return accepter.Accept(part, h, mediaType == "text/plain", true, true) -} - -func IsLeaf(h textproto.MIMEHeader) bool { - return !strings.HasPrefix(h.Get("Content-Type"), "multipart/") -} - -// MIMEVisitor is main object to parse (visit) and process (accept) all parts of MIME message. -type MimeVisitor struct { - target VisitAcceptor -} - -// Accept reads part recursively if needed. -// hasPlainSibling is there when acceptor want to check alternatives. -func (mv *MimeVisitor) Accept(part io.Reader, h textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - if !isFirst { - return - } - - parentMediaType, params, err := getContentType(h) - if err != nil { - return - } - - if err = mv.target.Accept(part, h, hasPlainSibling, true, false); err != nil { - return - } - - if !IsLeaf(h) { - var multiparts []io.Reader - var multipartHeaders []textproto.MIMEHeader - if multiparts, multipartHeaders, err = GetMultipartParts(part, params); err != nil { - return - } - hasPlainChild := false - for _, header := range multipartHeaders { - mediaType, _, _ := getContentType(header) - if mediaType == "text/plain" { - hasPlainChild = true - } - } - if hasPlainSibling && parentMediaType == "multipart/related" { - hasPlainChild = true - } - - for i, p := range multiparts { - if err = mv.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil { - return - } - if err = mv.target.Accept(part, h, hasPlainSibling, false, i == (len(multiparts)-1)); err != nil { - return - } - } - } - return -} - -// NewMIMEVisitor returns a new mime visitor initialised with an acceptor. -func NewMimeVisitor(targetAccepter VisitAcceptor) *MimeVisitor { - return &MimeVisitor{targetAccepter} -} - -func GetAllChildParts(part io.Reader, h textproto.MIMEHeader) (parts []io.Reader, headers []textproto.MIMEHeader, err error) { - mediaType, params, err := getContentType(h) - if err != nil { - return - } - if strings.HasPrefix(mediaType, "multipart/") { - var multiparts []io.Reader - var multipartHeaders []textproto.MIMEHeader - if multiparts, multipartHeaders, err = GetMultipartParts(part, params); err != nil { - return - } - if strings.Contains(mediaType, "alternative") { - var chosenPart io.Reader - var chosenHeader textproto.MIMEHeader - if chosenPart, chosenHeader, err = pickAlternativePart(multiparts, multipartHeaders); err != nil { - return - } - var childParts []io.Reader - var childHeaders []textproto.MIMEHeader - if childParts, childHeaders, err = GetAllChildParts(chosenPart, chosenHeader); err != nil { - return - } - parts = append(parts, childParts...) - headers = append(headers, childHeaders...) - } else { - for i, p := range multiparts { - var childParts []io.Reader - var childHeaders []textproto.MIMEHeader - if childParts, childHeaders, err = GetAllChildParts(p, multipartHeaders[i]); err != nil { - return - } - parts = append(parts, childParts...) - headers = append(headers, childHeaders...) - } - } - } else { - parts = append(parts, part) - headers = append(headers, h) - } - return -} - -func GetMultipartParts(r io.Reader, params map[string]string) (parts []io.Reader, headers []textproto.MIMEHeader, err error) { - mr := multipart.NewReader(r, params["boundary"]) - parts = []io.Reader{} - headers = []textproto.MIMEHeader{} - var p *multipart.Part - for { - p, err = mr.NextPart() - if err == io.EOF { - err = nil - break - } - if err != nil { - return - } - b, _ := ioutil.ReadAll(p) - buffer := bytes.NewBuffer(b) - - parts = append(parts, buffer) - headers = append(headers, p.Header) - } - return -} - -func pickAlternativePart(parts []io.Reader, headers []textproto.MIMEHeader) (part io.Reader, h textproto.MIMEHeader, err error) { - - for i, h := range headers { - mediaType, _, err := getContentType(h) - if err != nil { - continue - } - if strings.HasPrefix(mediaType, "multipart/") { - return parts[i], headers[i], nil - } - } - for i, h := range headers { - mediaType, _, err := getContentType(h) - if err != nil { - continue - } - if mediaType == "text/html" { - return parts[i], headers[i], nil - } - } - for i, h := range headers { - mediaType, _, err := getContentType(h) - if err != nil { - continue - } - if mediaType == "text/plain" { - return parts[i], headers[i], nil - } - } - - // If we get all the way here, part will be nil. - return -} - -// "Parse address comment" as defined in http://tools.wordtothewise.com/rfc/822 -// FIXME: Does not work for address groups. -// NOTE: This should be removed for go>1.10 (please check). -func parseAddressComment(raw string) string { - parsed := []string{} - for _, item := range regexp.MustCompile("[,;]").Split(raw, -1) { - re := regexp.MustCompile("[(][^)]*[)]") - comments := strings.Join(re.FindAllString(item, -1), " ") - comments = strings.Replace(comments, "(", "", -1) - comments = strings.Replace(comments, ")", "", -1) - withoutComments := re.ReplaceAllString(item, "") - addr, err := mail.ParseAddress(withoutComments) - if err != nil { - continue - } - if addr.Name == "" { - addr.Name = comments - } - parsed = append(parsed, addr.String()) - } - return strings.Join(parsed, ", ") -} - -func decodePart(partReader io.Reader, header textproto.MIMEHeader) (decodedPart io.Reader) { - decodedPart = 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 -} - -// Assume 'text/plain' if missing. -func getContentType(header textproto.MIMEHeader) (mediatype string, params map[string]string, err error) { - contentType := header.Get("Content-Type") - if contentType == "" { - contentType = "text/plain" - } - - return ParseMediaType(contentType) -} - -// ===================== MIME Printer =================================== -// Simply print resulting MIME tree into text form. -// TODO move this to file mime_printer.go. - -type stack []string - -func (s stack) Push(v string) stack { - return append(s, v) -} -func (s stack) Pop() (stack, string) { - l := len(s) - return s[:l-1], s[l-1] -} -func (s stack) Peek() string { - return s[len(s)-1] -} - -type MIMEPrinter struct { - result *bytes.Buffer - boundaryStack stack -} - -func NewMIMEPrinter() (pd *MIMEPrinter) { - return &MIMEPrinter{ - result: bytes.NewBuffer([]byte("")), - boundaryStack: stack{}, - } -} - -func (pd *MIMEPrinter) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - if isFirst { - http.Header(header).Write(pd.result) - pd.result.Write([]byte("\n")) - if IsLeaf(header) { - pd.result.ReadFrom(partReader) - } else { - _, params, _ := getContentType(header) - boundary := params["boundary"] - pd.boundaryStack = pd.boundaryStack.Push(boundary) - pd.result.Write([]byte("\nThis is a multi-part message in MIME format.\n--" + boundary + "\n")) - } - } else { - if !isLast { - pd.result.Write([]byte("\n--" + pd.boundaryStack.Peek() + "\n")) - } else { - var boundary string - pd.boundaryStack, boundary = pd.boundaryStack.Pop() - pd.result.Write([]byte("\n--" + boundary + "--\n.\n")) - } - } - return nil -} - -func (pd *MIMEPrinter) String() string { - return pd.result.String() -} - -// ======================== PlainText Collector ========================= -// Collect contents of all non-attachment text/plain parts and return it as a string. -// TODO move this to file collector_plaintext.go. - -type PlainTextCollector struct { - target VisitAcceptor - plainTextContents *bytes.Buffer -} - -func NewPlainTextCollector(targetAccepter VisitAcceptor) *PlainTextCollector { - return &PlainTextCollector{ - target: targetAccepter, - plainTextContents: bytes.NewBuffer([]byte("")), - } -} - -func (ptc *PlainTextCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - if isFirst { - if IsLeaf(header) { - mediaType, _, _ := getContentType(header) - disp, _, _ := ParseMediaType(header.Get("Content-Disposition")) - if mediaType == "text/plain" && disp != "attachment" { - partData, _ := ioutil.ReadAll(partReader) - decodedPart := decodePart(bytes.NewReader(partData), header) - - if buffer, err := ioutil.ReadAll(decodedPart); err == nil { - buffer, err = DecodeCharset(buffer, header.Get("Content-Type")) - if err != nil { - log.Warnln("Decode charset error:", err) - return err - } - ptc.plainTextContents.Write(buffer) - } - - err = ptc.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast) - return - } - } - } - err = ptc.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast) - return -} - -func (ptc PlainTextCollector) GetPlainText() string { - return ptc.plainTextContents.String() -} - -// ======================== Body Collector ============== -// Collect contents of all non-attachment parts and return it as a string. -// TODO move this to file collector_body.go. - -type BodyCollector struct { - target VisitAcceptor - htmlBodyBuffer *bytes.Buffer - plainBodyBuffer *bytes.Buffer - htmlHeaderBuffer *bytes.Buffer - plainHeaderBuffer *bytes.Buffer - hasHtml bool -} - -func NewBodyCollector(targetAccepter VisitAcceptor) *BodyCollector { - return &BodyCollector{ - target: targetAccepter, - htmlBodyBuffer: bytes.NewBuffer([]byte("")), - plainBodyBuffer: bytes.NewBuffer([]byte("")), - htmlHeaderBuffer: bytes.NewBuffer([]byte("")), - plainHeaderBuffer: bytes.NewBuffer([]byte("")), - } -} - -func (bc *BodyCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - // TODO: Collect html and plaintext - if there's html with plain sibling don't include plain/text. - if isFirst { - if IsLeaf(header) { - mediaType, _, _ := getContentType(header) - disp, _, _ := ParseMediaType(header.Get("Content-Disposition")) - if disp != "attachment" { - partData, _ := ioutil.ReadAll(partReader) - decodedPart := decodePart(bytes.NewReader(partData), header) - if buffer, err := ioutil.ReadAll(decodedPart); err == nil { - buffer, err = DecodeCharset(buffer, header.Get("Content-Type")) - if err != nil { - log.Warnln("Decode charset error:", err) - return err - } - if mediaType == "text/html" { - bc.hasHtml = true - http.Header(header).Write(bc.htmlHeaderBuffer) - bc.htmlBodyBuffer.Write(buffer) - } else if mediaType == "text/plain" { - http.Header(header).Write(bc.plainHeaderBuffer) - bc.plainBodyBuffer.Write(buffer) - } - } - - err = bc.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast) - return - } - } - } - err = bc.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast) - return -} - -func (bc *BodyCollector) GetBody() (string, string) { - if bc.hasHtml { - return bc.htmlBodyBuffer.String(), "text/html" - } else { - return bc.plainBodyBuffer.String(), "text/plain" - } -} - -func (bc *BodyCollector) GetHeaders() string { - if bc.hasHtml { - return bc.htmlHeaderBuffer.String() - } else { - return bc.plainHeaderBuffer.String() - } -} - -// ======================== Attachments Collector ============== -// Collect contents of all attachment parts and return them as a string. -// TODO move this to file collector_attachment.go. - -type AttachmentsCollector struct { - target VisitAcceptor - attBuffers []string - attHeaders []string -} - -func NewAttachmentsCollector(targetAccepter VisitAcceptor) *AttachmentsCollector { - return &AttachmentsCollector{ - target: targetAccepter, - attBuffers: []string{}, - attHeaders: []string{}, - } -} - -func (ac *AttachmentsCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - if isFirst { - if IsLeaf(header) { - mediaType, _, _ := getContentType(header) - disp, _, _ := ParseMediaType(header.Get("Content-Disposition")) - if (mediaType != "text/html" && mediaType != "text/plain") || disp == "attachment" { - partData, _ := ioutil.ReadAll(partReader) - decodedPart := decodePart(bytes.NewReader(partData), header) - - if buffer, err := ioutil.ReadAll(decodedPart); err == nil { - buffer, err = DecodeCharset(buffer, header.Get("Content-Type")) - if err != nil { - log.Warnln("Decode charset error:", err) - return err - } - headerBuf := new(bytes.Buffer) - http.Header(header).Write(headerBuf) - ac.attHeaders = append(ac.attHeaders, headerBuf.String()) - ac.attBuffers = append(ac.attBuffers, string(buffer)) - } - - err = ac.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast) - return - } - } - } - err = ac.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast) - return -} - -func (ac AttachmentsCollector) GetAttachments() []string { - return ac.attBuffers -} - -func (ac AttachmentsCollector) GetAttHeaders() []string { - return ac.attHeaders -} diff --git a/pkg/mime/parser_test.go b/pkg/mime/parser_test.go deleted file mode 100644 index 8bff3d4f..00000000 --- a/pkg/mime/parser_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2020 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package pmmime - -import ( - "bytes" - "fmt" - - "io/ioutil" - "net/mail" - - "net/textproto" - "strings" - "testing" -) - -func minimalParse(mimeBody string) (readBody string, plainContents string, err error) { - mm, err := mail.ReadMessage(strings.NewReader(mimeBody)) - if err != nil { - return - } - - h := textproto.MIMEHeader(mm.Header) - mmBodyData, err := ioutil.ReadAll(mm.Body) - if err != nil { - return - } - - printAccepter := NewMIMEPrinter() - plainTextCollector := NewPlainTextCollector(printAccepter) - visitor := NewMimeVisitor(plainTextCollector) - err = VisitAll(bytes.NewReader(mmBodyData), h, visitor) - - readBody = printAccepter.String() - plainContents = plainTextCollector.GetPlainText() - - return readBody, plainContents, err -} - -func androidParse(mimeBody string) (body, headers string, atts, attHeaders []string, err error) { - mm, err := mail.ReadMessage(strings.NewReader(mimeBody)) - if err != nil { - return - } - - h := textproto.MIMEHeader(mm.Header) - mmBodyData, err := ioutil.ReadAll(mm.Body) - if err != nil { - return - } - - printAccepter := NewMIMEPrinter() - bodyCollector := NewBodyCollector(printAccepter) - attachmentsCollector := NewAttachmentsCollector(bodyCollector) - mimeVisitor := NewMimeVisitor(attachmentsCollector) - err = VisitAll(bytes.NewReader(mmBodyData), h, mimeVisitor) - - body, _ = bodyCollector.GetBody() - headers = bodyCollector.GetHeaders() - atts = attachmentsCollector.GetAttachments() - attHeaders = attachmentsCollector.GetAttHeaders() - - return -} - -func TestParseBoundaryIsEmpty(t *testing.T) { - testMessage := - `Date: Sun, 10 Mar 2019 11:10:06 -0600 -In-Reply-To: -X-Original-To: enterprise@protonmail.com -References: -To: "ProtonMail" -X-Pm-Origin: external -Delivered-To: enterprise@protonmail.com -Content-Type: multipart/mixed; boundary=ac7e36bd45425e70b4dab2128f34172e4dc3f9ff2eeb47e909267d4252794ec7 -Reply-To: XYZ -Mime-Version: 1.0 -Subject: Encrypted Message -Return-Path: -From: XYZ -X-Pm-ConversationID-Id: gNX9bDPLmBgFZ-C3Tdlb628cas1Xl0m4dql5nsWzQAEI-WQv0ytfwPR4-PWELEK0_87XuFOgetc239Y0pjPYHQ== -X-Pm-Date: Sun, 10 Mar 2019 18:10:06 +0100 -Message-Id: <68c11e46-e611-d9e4-edc1-5ec96bac77cc@unicoderns.com> -X-Pm-Transfer-Encryption: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) -X-Pm-External-Id: <68c11e46-e611-d9e4-edc1-5ec96bac77cc@unicoderns.com> -X-Pm-Internal-Id: _iJ8ETxcqXTSK8IzCn0qFpMUTwvRf-xJUtldRA1f6yHdmXjXzKleG3F_NLjZL3FvIWVHoItTxOuuVXcukwwW3g== -Openpgp: preference=signencrypt -User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.4.0 -X-Pm-Content-Encryption: end-to-end - ---ac7e36bd45425e70b4dab2128f34172e4dc3f9ff2eeb47e909267d4252794ec7 -Content-Disposition: inline -Content-Transfer-Encoding: quoted-printable -Content-Type: multipart/mixed; charset=utf-8 - -Content-Type: multipart/mixed; boundary="xnAIW3Turb9YQZ2rXc2ZGZH45WepHIZyy"; - protected-headers="v1" -From: XYZ -To: "ProtonMail" -Subject: Encrypted Message -Message-ID: <68c11e46-e611-d9e4-edc1-5ec96bac77cc@unicoderns.com> -References: -In-Reply-To: - ---xnAIW3Turb9YQZ2rXc2ZGZH45WepHIZyy -Content-Type: text/rfc822-headers; protected-headers="v1" -Content-Disposition: inline - -From: XYZ -To: ProtonMail -Subject: Re: Encrypted Message - ---xnAIW3Turb9YQZ2rXc2ZGZH45WepHIZyy -Content-Type: multipart/alternative; - boundary="------------F9E5AA6D49692F51484075E3" -Content-Language: en-US - -This is a multi-part message in MIME format. ---------------F9E5AA6D49692F51484075E3 -Content-Type: text/plain; charset=utf-8 -Content-Transfer-Encoding: quoted-printable - -Hi ... - ---------------F9E5AA6D49692F51484075E3 -Content-Type: text/html; charset=utf-8 -Content-Transfer-Encoding: quoted-printable - - - - - -

Hi ..

- - - ---------------F9E5AA6D49692F51484075E3-- - ---xnAIW3Turb9YQZ2rXc2ZGZH45WepHIZyy-- - ---ac7e36bd45425e70b4dab2128f34172e4dc3f9ff2eeb47e909267d4252794ec7-- - - -` - - body, content, err := minimalParse(testMessage) - if err == nil { - t.Fatal("should have error but is", err) - } - t.Log("==BODY==") - t.Log(body) - t.Log("==CONTENT==") - t.Log(content) -} - -func TestParse(t *testing.T) { - testMessage := - `From: John Doe -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="XXXXboundary text" - -This is a multipart message in MIME format. - ---XXXXboundary text -Content-Type: text/plain; charset=utf-8 - -this is the body text - ---XXXXboundary text -Content-Type: text/html; charset=utf-8 - -this is the html body text - ---XXXXboundary text -Content-Type: text/plain; charset=utf-8 -Content-Disposition: attachment; - filename="test.txt" - -this is the attachment text - ---XXXXboundary text-- - - -` - body, heads, att, attHeads, err := androidParse(testMessage) - if err != nil { - t.Error("parse error", err) - } - - fmt.Println("==BODY:") - fmt.Println(body) - fmt.Println("==BODY HEADERS:") - fmt.Println(heads) - - fmt.Println("==ATTACHMENTS:") - fmt.Println(att) - fmt.Println("==ATTACHMENT HEADERS:") - fmt.Println(attHeads) -} - -func TestParseAddressComment(t *testing.T) { - parsingExamples := map[string]string{ - "": "", - "(Only Comment) here@pm.me": "\"Only Comment\" ", - "Normal Name (With Comment) ": "\"Normal Name\" ", - "": "\"I am the greatest the\" ", - } - - for raw, expected := range parsingExamples { - parsed := parseAddressComment(raw) - if expected != parsed { - t.Errorf("When parsing %q expected %q but have %q", raw, expected, parsed) - } - } -}