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