mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 20:56:51 +00:00
fix(GODT-2576): Correctly handle Forwarded messages from Thunderbird
Thunderbird uses `In-Reply-To` with `X-Forwarded-Message-Id` to signal to the SMTP server that it is forwarding a message.
This commit is contained in:
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/Masterminds/semver/v3 v3.2.0
|
github.com/Masterminds/semver/v3 v3.2.0
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20231114153341-2ecbdd2739f7
|
github.com/ProtonMail/gluon v0.17.1-0.20231114153341-2ecbdd2739f7
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116074655-c9bc6f71eef0
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116144214-8a47c8d92fbc
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -36,8 +36,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/
|
|||||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116074655-c9bc6f71eef0 h1:tUK7x2Vm2bnCdij/gKyXxgV9v1A680BdDOEQfBm1Rz0=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116144214-8a47c8d92fbc h1:GBRKoFAldApEMkMrsFN1ZxG0eG797w6LTv/dFMDcsqQ=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116074655-c9bc6f71eef0/go.mod h1:WEXJqj5DSc2YI77SgXdpMY0nk33Qy92Vu2r4tOEazA8=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231116144214-8a47c8d92fbc/go.mod h1:WEXJqj5DSc2YI77SgXdpMY0nk33Qy92Vu2r4tOEazA8=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||||
|
|||||||
@ -217,7 +217,7 @@ func (s *Service) sendWithKey(
|
|||||||
return proton.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
return proton.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
||||||
}
|
}
|
||||||
|
|
||||||
draft, err := s.createDraft(ctx, addrKR, emails, from, to, parentID, message.InReplyTo, proton.DraftTemplate{
|
draft, err := s.createDraft(ctx, addrKR, emails, from, to, parentID, message.InReplyTo, message.XForward, proton.DraftTemplate{
|
||||||
Subject: message.Subject,
|
Subject: message.Subject,
|
||||||
Body: decBody,
|
Body: decBody,
|
||||||
MIMEType: message.MIMEType,
|
MIMEType: message.MIMEType,
|
||||||
@ -353,6 +353,7 @@ func (s *Service) createDraft(
|
|||||||
to []string,
|
to []string,
|
||||||
parentID string,
|
parentID string,
|
||||||
replyToID string,
|
replyToID string,
|
||||||
|
xForwardID string,
|
||||||
template proton.DraftTemplate,
|
template proton.DraftTemplate,
|
||||||
) (proton.Message, error) {
|
) (proton.Message, error) {
|
||||||
// Check sender: set the sender if it's missing.
|
// Check sender: set the sender if it's missing.
|
||||||
@ -388,7 +389,12 @@ func (s *Service) createDraft(
|
|||||||
var action proton.CreateDraftAction
|
var action proton.CreateDraftAction
|
||||||
|
|
||||||
if len(replyToID) > 0 {
|
if len(replyToID) > 0 {
|
||||||
|
// Thunderbird fills both ReplyTo and adds an X-Forwarded-Message-Id header when forwarding.
|
||||||
|
if replyToID == xForwardID {
|
||||||
|
action = proton.ForwardAction
|
||||||
|
} else {
|
||||||
action = proton.ReplyAction
|
action = proton.ReplyAction
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
action = proton.ForwardAction
|
action = proton.ForwardAction
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,7 @@ type Message struct {
|
|||||||
References []string
|
References []string
|
||||||
ExternalID string
|
ExternalID string
|
||||||
InReplyTo string
|
InReplyTo string
|
||||||
|
XForward string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
@ -520,6 +521,9 @@ func parseMessageHeader(h message.Header, allowInvalidAddressLists bool) (Messag
|
|||||||
case "in-reply-to":
|
case "in-reply-to":
|
||||||
m.InReplyTo = regexp.MustCompile("<(.*)>").ReplaceAllString(fields.Value(), "$1")
|
m.InReplyTo = regexp.MustCompile("<(.*)>").ReplaceAllString(fields.Value(), "$1")
|
||||||
|
|
||||||
|
case "x-forwarded-message-id":
|
||||||
|
m.XForward = regexp.MustCompile("<(.*)>").ReplaceAllString(fields.Value(), "$1")
|
||||||
|
|
||||||
case "references":
|
case "references":
|
||||||
for _, ref := range strings.Fields(fields.Value()) {
|
for _, ref := range strings.Fields(fields.Value()) {
|
||||||
for _, ref := range strings.Split(ref, ",") {
|
for _, ref := range strings.Split(ref, ",") {
|
||||||
|
|||||||
@ -786,6 +786,16 @@ func TestParseTextPlainWithDocxAttachmentCyrillic(t *testing.T) {
|
|||||||
assert.Equal(t, "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.docx", m.Attachments[0].Name)
|
assert.Equal(t, "АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЧЏЗШ.docx", m.Attachments[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseInReplyToAndXForward(t *testing.T) {
|
||||||
|
f := getFileReader("text_plain_utf8_reply_to_and_x_forward.eml")
|
||||||
|
|
||||||
|
m, err := Parse(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "00000@protonmail.com", m.XForward)
|
||||||
|
require.Equal(t, "00000@protonmail.com", m.InReplyTo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPatchNewLineWithHtmlBreaks(t *testing.T) {
|
func TestPatchNewLineWithHtmlBreaks(t *testing.T) {
|
||||||
{
|
{
|
||||||
input := []byte("\nfoo\nbar\n\n\nzz\nddd")
|
input := []byte("\nfoo\nbar\n\n\nzz\nddd")
|
||||||
|
|||||||
7
pkg/message/testdata/text_plain_utf8_reply_to_and_x_forward.eml
vendored
Normal file
7
pkg/message/testdata/text_plain_utf8_reply_to_and_x_forward.eml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
From: Sender <sender@pm.me>
|
||||||
|
To: Receiver <receiver@pm.me>
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
X-Forwarded-Message-Id: <00000@protonmail.com>
|
||||||
|
In-Reply-To: <00000@protonmail.com>
|
||||||
|
|
||||||
|
body
|
||||||
@ -278,3 +278,112 @@ Feature: SMTP send reply
|
|||||||
| from | subject | in-reply-to | references |
|
| from | subject | in-reply-to | references |
|
||||||
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
||||||
| [user:user2]@[domain] | FW - Please Reply Again | <something@external.com> | <something@external.com> |
|
| [user:user2]@[domain] | FW - Please Reply Again | <something@external.com> | <something@external.com> |
|
||||||
|
|
||||||
|
@long-black
|
||||||
|
Scenario: Reply with In-Reply-To and X-Forwarded-Message-Id sets forwarded flag
|
||||||
|
# User1 send the initial message.
|
||||||
|
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
|
||||||
|
"""
|
||||||
|
From: Bridge Test <[user:user1]@[domain]>
|
||||||
|
To: Internal Bridge <[user:user2]@[domain]>
|
||||||
|
Subject: Please Reply
|
||||||
|
Message-ID: <something@external.com>
|
||||||
|
|
||||||
|
hello
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then it succeeds
|
||||||
|
Then IMAP client "1" eventually sees the following messages in "Sent":
|
||||||
|
| from | to | subject | message-id |
|
||||||
|
| [user:user1]@[domain] | [user:user2]@[domain] | Please Reply | <something@external.com> |
|
||||||
|
# login user2.
|
||||||
|
And the user logs in with username "[user:user2]" and password "password"
|
||||||
|
And user "[user:user2]" connects and authenticates IMAP client "2"
|
||||||
|
And user "[user:user2]" connects and authenticates SMTP client "2"
|
||||||
|
And user "[user:user2]" finishes syncing
|
||||||
|
# User2 receive the message.
|
||||||
|
Then IMAP client "2" eventually sees the following messages in "INBOX":
|
||||||
|
| from | subject | message-id | reply-to |
|
||||||
|
| [user:user1]@[domain] | Please Reply | <something@external.com> | [user:user1]@[domain] |
|
||||||
|
# User2 reply to it.
|
||||||
|
When SMTP client "2" sends the following message from "[user:user2]@[domain]" to "[user:user1]@[domain]":
|
||||||
|
"""
|
||||||
|
From: Internal Bridge <[user:user2]@[domain]>
|
||||||
|
To: Bridge Test <[user:user1]@[domain]>
|
||||||
|
Content-Type: text/plain
|
||||||
|
Subject: FW - Please Reply
|
||||||
|
In-Reply-To: <something@external.com>
|
||||||
|
Message-ID: <something@external.com>
|
||||||
|
X-Forwarded-Message-Id: <something@external.com>
|
||||||
|
|
||||||
|
Heya
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then it succeeds
|
||||||
|
Then IMAP client "2" eventually sees the following messages in "Sent":
|
||||||
|
| from | to | subject | in-reply-to | references |
|
||||||
|
| [user:user2]@[domain] | [user:user1]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
||||||
|
When IMAP client "2" selects "INBOX"
|
||||||
|
And it succeeds
|
||||||
|
Then IMAP client "2" eventually sees that message at row 1 has the flag "forwarded"
|
||||||
|
And it succeeds
|
||||||
|
Then IMAP client "2" eventually sees that message at row 1 does not have the flag "\Answered"
|
||||||
|
And it succeeds
|
||||||
|
# User1 receive the reply.|
|
||||||
|
And IMAP client "1" eventually sees the following messages in "INBOX":
|
||||||
|
| from | subject | in-reply-to | references |
|
||||||
|
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
||||||
|
|
||||||
|
@long-black
|
||||||
|
Scenario: Reply with In-Reply-To sets answered flag
|
||||||
|
# User1 send the initial message.
|
||||||
|
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
|
||||||
|
"""
|
||||||
|
From: Bridge Test <[user:user1]@[domain]>
|
||||||
|
To: Internal Bridge <[user:user2]@[domain]>
|
||||||
|
Subject: Please Reply
|
||||||
|
Message-ID: <something@external.com>
|
||||||
|
|
||||||
|
hello
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then it succeeds
|
||||||
|
Then IMAP client "1" eventually sees the following messages in "Sent":
|
||||||
|
| from | to | subject | message-id |
|
||||||
|
| [user:user1]@[domain] | [user:user2]@[domain] | Please Reply | <something@external.com> |
|
||||||
|
# login user2.
|
||||||
|
And the user logs in with username "[user:user2]" and password "password"
|
||||||
|
And user "[user:user2]" connects and authenticates IMAP client "2"
|
||||||
|
And user "[user:user2]" connects and authenticates SMTP client "2"
|
||||||
|
And user "[user:user2]" finishes syncing
|
||||||
|
# User2 receive the message.
|
||||||
|
Then IMAP client "2" eventually sees the following messages in "INBOX":
|
||||||
|
| from | subject | message-id | reply-to |
|
||||||
|
| [user:user1]@[domain] | Please Reply | <something@external.com> | [user:user1]@[domain] |
|
||||||
|
# User2 reply to it.
|
||||||
|
When SMTP client "2" sends the following message from "[user:user2]@[domain]" to "[user:user1]@[domain]":
|
||||||
|
"""
|
||||||
|
From: Internal Bridge <[user:user2]@[domain]>
|
||||||
|
To: Bridge Test <[user:user1]@[domain]>
|
||||||
|
Content-Type: text/plain
|
||||||
|
Subject: FW - Please Reply
|
||||||
|
In-Reply-To: <something@external.com>
|
||||||
|
Message-ID: <something@external.com>
|
||||||
|
|
||||||
|
Heya
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then it succeeds
|
||||||
|
Then IMAP client "2" eventually sees the following messages in "Sent":
|
||||||
|
| from | to | subject | in-reply-to | references |
|
||||||
|
| [user:user2]@[domain] | [user:user1]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
||||||
|
When IMAP client "2" selects "INBOX"
|
||||||
|
And it succeeds
|
||||||
|
Then IMAP client "2" eventually sees that message at row 1 has the flag "\Answered"
|
||||||
|
And it succeeds
|
||||||
|
Then IMAP client "2" eventually sees that message at row 1 does not have the flag "forwarded"
|
||||||
|
And it succeeds
|
||||||
|
# User1 receive the reply.|
|
||||||
|
And IMAP client "1" eventually sees the following messages in "INBOX":
|
||||||
|
| from | subject | in-reply-to | references |
|
||||||
|
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
|
||||||
Reference in New Issue
Block a user