diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php index b8097c377c..2a5d99ff79 100644 --- a/lib/Model/IMAPMessage.php +++ b/lib/Model/IMAPMessage.php @@ -310,6 +310,7 @@ public function jsonSerialize() { 'messageId' => $this->getMessageId(), 'from' => $this->getFrom()->jsonSerialize(), 'to' => $this->getTo()->jsonSerialize(), + 'replyTo' => $this->getReplyTo()->jsonSerialize(), 'cc' => $this->getCC()->jsonSerialize(), 'bcc' => $this->getBCC()->jsonSerialize(), 'subject' => $this->getSubject(), @@ -426,17 +427,22 @@ public function setInReplyTo(string $id) { throw new Exception('not implemented'); } + /** + * @return AddressList + */ public function getReplyTo(): AddressList { return $this->replyTo; } /** - * @param string $id + * @param AddressList $replyTo + * + * @throws Exception * * @return void */ - public function setReplyTo(string $id) { - throw new Exception('not implemented'); + public function setReplyTo(AddressList $replyTo) { + throw new Exception('IMAP message is immutable'); } public function isEncrypted(): bool { diff --git a/lib/Model/IMessage.php b/lib/Model/IMessage.php index 6ced883caa..fbc099e089 100644 --- a/lib/Model/IMessage.php +++ b/lib/Model/IMessage.php @@ -69,6 +69,16 @@ public function getTo(): AddressList; */ public function setTo(AddressList $to); + /** + * @return AddressList + */ + public function getReplyTo(): AddressList; + + /** + * @param AddressList $replyTo + */ + public function setReplyTo(AddressList $replyTo); + /** * @return AddressList */ diff --git a/lib/Model/Message.php b/lib/Model/Message.php index 92ff0e18c1..4568bd6bee 100644 --- a/lib/Model/Message.php +++ b/lib/Model/Message.php @@ -42,6 +42,9 @@ class Message implements IMessage { /** @var AddressList */ private $to; + /** @var AddressList */ + private $replyTo; + /** @var AddressList */ private $cc; @@ -63,6 +66,7 @@ class Message implements IMessage { public function __construct() { $this->from = new AddressList(); $this->to = new AddressList(); + $this->replyTo = new AddressList(); $this->cc = new AddressList(); $this->bcc = new AddressList(); } @@ -126,6 +130,22 @@ public function setTo(AddressList $to) { $this->to = $to; } + /** + * @return AddressList + */ + public function getReplyTo(): AddressList { + return $this->replyTo; + } + + /** + * @param AddressList $replyTo + * + * @return void + */ + public function setReplyTo(AddressList $replyTo) { + $this->replyTo = $replyTo; + } + /** * @return AddressList */ diff --git a/src/ReplyBuilder.js b/src/ReplyBuilder.js index 25bbff7389..479f8eeea1 100644 --- a/src/ReplyBuilder.js +++ b/src/ReplyBuilder.js @@ -73,11 +73,15 @@ const RecipientType = Object.seal({ Cc: 2, }) -export const buildRecipients = (envelope, ownAddress) => { +export const buildRecipients = (envelope, ownAddress, replyTo) => { let recipientType = RecipientType.None const isOwnAddress = (a) => a.email === ownAddress.email const isNotOwnAddress = negate(isOwnAddress) + // The Reply-To header has higher precedence than the From header. + // This re-uses Horde's handling of the reply_to field directly. + const from = replyTo !== undefined ? replyTo : envelope.from + // Locate why we received this envelope // Can be in 'to', 'cc' or unknown let replyingAddress = envelope.to.find(isOwnAddress) @@ -97,20 +101,20 @@ export const buildRecipients = (envelope, ownAddress) => { if (recipientType === RecipientType.To) { // Send to everyone except yourself, plus the original sender if not ourself to = envelope.to.filter(isNotOwnAddress) - to = to.concat(envelope.from.filter(isNotOwnAddress)) + to = to.concat(from.filter(isNotOwnAddress)) // CC remains the same cc = envelope.cc } else if (recipientType === RecipientType.Cc) { // Send to the same people, plus the sender if not ourself - to = envelope.to.concat(envelope.from.filter(isNotOwnAddress)) + to = envelope.to.concat(from.filter(isNotOwnAddress)) // All CC values are being kept except the replying address cc = envelope.cc.filter(isNotOwnAddress) } else { // Send to the same recipient and the sender (if not ourself) -> answer all to = envelope.to - to = to.concat(envelope.from.filter(isNotOwnAddress)) + to = to.concat(from.filter(isNotOwnAddress)) // Keep CC values cc = envelope.cc @@ -118,7 +122,7 @@ export const buildRecipients = (envelope, ownAddress) => { // edge case: pure self-sent email if (to.length === 0) { - to = envelope.from + to = from } return { diff --git a/src/store/actions.js b/src/store/actions.js index 7a3f5d7bce..c65d65ed63 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -400,7 +400,7 @@ export default { commit('startComposerSession', { data: { accountId: reply.data.accountId, - to: reply.data.from, + to: original.replyTo !== undefined ? original.replyTo : reply.data.from, cc: [], subject: buildReplySubject(reply.data.subject), body: data.body, @@ -416,7 +416,7 @@ export default { const recipients = buildReplyRecipients(reply.data, { email: account.emailAddress, label: account.name, - }) + }, original.replyTo) commit('startComposerSession', { data: { accountId: reply.data.accountId, diff --git a/tests/Unit/Model/IMAPMessageTest.php b/tests/Unit/Model/IMAPMessageTest.php index 29f9c7ecd5..1390a38318 100644 --- a/tests/Unit/Model/IMAPMessageTest.php +++ b/tests/Unit/Model/IMAPMessageTest.php @@ -147,6 +147,7 @@ public function testSerialize() { 'to' => [ [ 'label' => 'to@mail.com', 'email' => 'to@mail.com' ] ], 'cc' => [ [ 'label' => 'cc@mail.com', 'email' => 'cc@mail.com' ] ], 'bcc' => [ [ 'label' => 'bcc@mail.com', 'email' => 'bcc@mail.com' ] ], + 'replyTo' => [ [ 'label' => 'reply-to@mail.com', 'email' => 'reply-to@mail.com' ] ], 'subject' => 'subject', 'dateInt' => 1451606400, 'flags' => [