forked from Silverfish/proton-bridge
fix(GODT-770): handle extraneous end-of-mail
This commit is contained in:
@ -4,6 +4,9 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
* GODT-770 Better handling of extraneous end-of-mail indicator.
|
||||
|
||||
### Changed
|
||||
* Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ type Parser struct {
|
||||
func New(r io.Reader) (*Parser, error) {
|
||||
p := new(Parser)
|
||||
|
||||
entity, err := message.Read(r)
|
||||
entity, err := message.Read(newEndOfMailTrimmer(r))
|
||||
if err != nil && !message.IsUnknownCharset(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
56
pkg/message/parser/trimmer.go
Normal file
56
pkg/message/parser/trimmer.go
Normal file
@ -0,0 +1,56 @@
|
||||
// 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 parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
const endOfMail = "\r\n.\r\n"
|
||||
|
||||
// endOfMailTrimmer wraps a reader to trim the End-Of-Mail indicator at the end
|
||||
// of the input, if present.
|
||||
//
|
||||
// During SMTP sending of a message, the DATA command indicates that you are
|
||||
// about to send the text (or body) of the message. The message text must end
|
||||
// with "\r\n.\r\n." I'm 99% sure that these 5 bytes should not be considered
|
||||
// part of the message body. However, some mail servers keep them as part of
|
||||
// the message, which our parser sometimes doesn't like. Therefore, we strip
|
||||
// them if we find them.
|
||||
type endOfMailTrimmer struct {
|
||||
r io.Reader
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func newEndOfMailTrimmer(r io.Reader) *endOfMailTrimmer {
|
||||
return &endOfMailTrimmer{r: r}
|
||||
}
|
||||
|
||||
func (r *endOfMailTrimmer) Read(p []byte) (int, error) {
|
||||
_, err := io.CopyN(&r.buf, r.r, int64(len(p)+len(endOfMail)-r.buf.Len()))
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err == io.EOF && bytes.HasSuffix(r.buf.Bytes(), []byte(endOfMail)) {
|
||||
r.buf.Truncate(r.buf.Len() - len(endOfMail))
|
||||
}
|
||||
|
||||
return r.buf.Read(p)
|
||||
}
|
||||
55
pkg/message/parser/trimmer_test.go
Normal file
55
pkg/message/parser/trimmer_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 parser
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEndOfMailTrimmer(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"string without eom", "string without eom"},
|
||||
{"string with eom\r\n.\r\n", "string with eom"},
|
||||
{"string with eom\r\n.\r\nin the middle", "string with eom\r\n.\r\nin the middle"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.in, func(t *testing.T) {
|
||||
res := dumbRead(newEndOfMailTrimmer(strings.NewReader(tt.in)))
|
||||
assert.Equal(t, tt.out, string(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func dumbRead(r io.Reader) []byte {
|
||||
out := []byte{}
|
||||
|
||||
b := make([]byte, 1)
|
||||
for _, err := r.Read(b); err == nil; _, err = r.Read(b) {
|
||||
out = append(out, b...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@ -467,6 +467,19 @@ func TestParseMultipartAlternativeLatin1(t *testing.T) {
|
||||
assert.Equal(t, "*aoeuaoeu*\n\n", plainBody)
|
||||
}
|
||||
|
||||
func TestParseWithTrailingEndOfMailIndicator(t *testing.T) {
|
||||
f := getFileReader("text_html_trailing_end_of_mail.eml")
|
||||
|
||||
m, _, plainBody, _, err := Parse(f, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `"Sender" <sender@sender.com>`, m.Sender.String())
|
||||
assert.Equal(t, `"Receiver" <receiver@receiver.com>`, m.ToList[0].String())
|
||||
|
||||
assert.Equal(t, "<!DOCTYPE html><html><head></head><body>boo!</body></html>", m.Body)
|
||||
assert.Equal(t, "boo!", plainBody)
|
||||
}
|
||||
|
||||
func getFileReader(filename string) io.Reader {
|
||||
f, err := os.Open(filepath.Join("testdata", filename))
|
||||
if err != nil {
|
||||
|
||||
8
pkg/message/testdata/text_html_trailing_end_of_mail.eml
vendored
Normal file
8
pkg/message/testdata/text_html_trailing_end_of_mail.eml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
From: "Sender" <sender@sender.com>
|
||||
To: "Receiver" <receiver@receiver.com>
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
Content-Transfer-Encoding: base64
|
||||
MIME-Version: 1.0
|
||||
|
||||
PCFET0NUWVBFIEhUTUw+CjxodG1sPjxib2R5PmJvbyE8L2JvZHk+PC9odG1sPg==
|
||||
.
|
||||
@ -5,11 +5,12 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
crypto "github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface
|
||||
|
||||
Reference in New Issue
Block a user