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)
- }
- }
-}