mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 13:16:53 +00:00
fix: linter issues
This commit is contained in:
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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: <abcbase64@protonmail.com>
|
||||
X-Original-To: enterprise@protonmail.com
|
||||
References: <abc64@unicoderns.com> <abc63@protonmail.com> <abc64@protonmail.com> <abc65@mail.gmail.com> <abc66@protonmail.com>
|
||||
To: "ProtonMail" <enterprise@protonmail.com>
|
||||
X-Pm-Origin: external
|
||||
Delivered-To: enterprise@protonmail.com
|
||||
Content-Type: multipart/mixed; boundary=ac7e36bd45425e70b4dab2128f34172e4dc3f9ff2eeb47e909267d4252794ec7
|
||||
Reply-To: XYZ <xyz@xyz.com>
|
||||
Mime-Version: 1.0
|
||||
Subject: Encrypted Message
|
||||
Return-Path: <xyz@xyz.com>
|
||||
From: XYZ <xyz@xyz.com>
|
||||
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 <xyz@xyz.com>
|
||||
To: "ProtonMail" <enterprise@protonmail.com>
|
||||
Subject: Encrypted Message
|
||||
Message-ID: <68c11e46-e611-d9e4-edc1-5ec96bac77cc@unicoderns.com>
|
||||
References: <abc64@unicoderns.com> <abc63@protonmail.com> <abc64@protonmail.com> <abc65@mail.gmail.com> <abc66@protonmail.com>
|
||||
In-Reply-To: <abcbase64@protonmail.com>
|
||||
|
||||
--xnAIW3Turb9YQZ2rXc2ZGZH45WepHIZyy
|
||||
Content-Type: text/rfc822-headers; protected-headers="v1"
|
||||
Content-Disposition: inline
|
||||
|
||||
From: XYZ <xyz@xyz.com>
|
||||
To: ProtonMail <enterprise@protonmail.com>
|
||||
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
|
||||
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body text=3D"#000000" bgcolor=3D"#FFFFFF">
|
||||
<p>Hi .. </p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--------------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 <example@example.com>
|
||||
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
|
||||
|
||||
<html><body>this is the html body text</body></html>
|
||||
|
||||
--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\" <here@pm.me>",
|
||||
"Normal Name (With Comment) <here@pm.me>": "\"Normal Name\" <here@pm.me>",
|
||||
"<Muhammed.(I am the greatest)Ali@(the)Vegas.WBA>": "\"I am the greatest the\" <Muhammed.Ali@Vegas.WBA>",
|
||||
}
|
||||
|
||||
for raw, expected := range parsingExamples {
|
||||
parsed := parseAddressComment(raw)
|
||||
if expected != parsed {
|
||||
t.Errorf("When parsing %q expected %q but have %q", raw, expected, parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user