Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 43 additions & 19 deletions lib/Service/MailTransmission.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Horde_Mime_Headers_Subject;
use Horde_Mime_Mail;
use Horde_Mime_Mdn;
use Horde_Mime_Part;
use Horde_Smtp_Exception;
use OCA\Mail\Account;
use OCA\Mail\Address;
Expand Down Expand Up @@ -108,29 +109,46 @@ public function sendMessage(Account $account, LocalMessage $localMessage): void
}

$transport = $this->smtpClientFactory->create($account);
// build mime body
$headers = $this->buildHeaders(
$from,
$to,
$cc,
$bcc,
$localMessage->getSubject()
);

// Build full headers for the Sent-folder copy (FCC), including Bcc so the
// sender can see who was blind-copied when reviewing sent mail — the same
// approach used by Horde IMP and other clients (Evolution, Thunderbird).
$fccHeaders = new Horde_Mime_Headers();
$fccHeaders->addHeaderOb(Horde_Mime_Headers_Date::create());
$fccHeaders->addHeaderOb(Horde_Mime_Headers_MessageId::create());
$fccHeaders->addHeaderOb(new Horde_Mime_Headers_Addresses('From', $from->toHorde()));
$fccHeaders->addHeaderOb(new Horde_Mime_Headers_Addresses('To', $to->toHorde()));
if (count($cc) > 0) {
$fccHeaders->addHeaderOb(new Horde_Mime_Headers_Addresses('Cc', $cc->toHorde()));
}
if (count($bcc) > 0) {
$fccHeaders->addHeaderOb(new Horde_Mime_Headers_Addresses('Bcc', $bcc->toHorde()));
}
if ($localMessage->getSubject() !== null) {
$fccHeaders->addHeader('Subject', $localMessage->getSubject());
}
// The table (oc_local_messages) currently only allows for a single reply to message id
// but we already set the 'references' header for an email so we could support multiple references
// Get the previous message and then concatenate all its "References" message ids with this one
if (($inReplyTo = $localMessage->getInReplyToMessageId()) !== null) {
$headers['References'] = $inReplyTo;
$headers['In-Reply-To'] = $inReplyTo;
$fccHeaders->addHeader('References', $inReplyTo);
$fccHeaders->addHeader('In-Reply-To', $inReplyTo);
}

if ($localMessage->getRequestMdn()) {
$headers[Horde_Mime_Mdn::MDN_HEADER] = $from->toHorde();
$fccHeaders->addHeaderOb(new Horde_Mime_Headers_Addresses(Horde_Mime_Mdn::MDN_HEADER, $from->toHorde()));
}

$mail = new Horde_Mime_Mail();
$mail->addHeaders($headers);
// For SMTP delivery: strip Bcc so it never appears in the transmitted
// message (RFC 5321). All three recipient lists are passed as SMTP
// envelope recipients so every addressee still receives the mail.
$sendHeaders = clone $fccHeaders;
$sendHeaders->removeHeader('Bcc');

$smtpRecipients = new Horde_Mail_Rfc822_List();
$smtpRecipients->add($to->toHorde());
$smtpRecipients->add($cc->toHorde());
$smtpRecipients->add($bcc->toHorde());
$smtpRecipients->unique();

$mimeMessage = new MimeMessage(
new DataUriParser()
Expand All @@ -151,12 +169,14 @@ public function sendMessage(Account $account, LocalMessage $localMessage): void
return;
}

$mail->setBasePart($mimePart);

// Send the message
try {
$mail->send($transport, false, false);
$localMessage->setRaw($mail->getRaw(false));
$mimePart->send($smtpRecipients->writeAddress(), $sendHeaders, $transport);
$localMessage->setRaw($mimePart->toString([
'encode' => Horde_Mime_Part::ENCODE_7BIT | Horde_Mime_Part::ENCODE_8BIT | Horde_Mime_Part::ENCODE_BINARY,
'headers' => $fccHeaders,
'stream' => false,
]));
$localMessage->setStatus(LocalMessage::STATUS_RAW);
} catch (Horde_Mime_Exception $e) {
if ($e->getPrevious() instanceof Horde_Smtp_Exception) {
Expand All @@ -175,7 +195,11 @@ public function sendMessage(Account $account, LocalMessage $localMessage): void
}

try {
$localMessage->setRaw($mail->getRaw(false));
$localMessage->setRaw($mimePart->toString([
'encode' => Horde_Mime_Part::ENCODE_7BIT | Horde_Mime_Part::ENCODE_8BIT | Horde_Mime_Part::ENCODE_BINARY,
'headers' => $fccHeaders,
'stream' => false,
]));
} catch (Throwable) {
// Having the raw message is nice for troubleshooting, but should not fail hard.
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Service/MailTransmissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ public function testSendMessageCc() {
$this->assertEquals(LocalMessage::STATUS_RAW, $localMessage->getStatus());
$this->assertStringContainsString('From: Bob <bob@mail.example>', $localMessage->getRaw());
$this->assertStringContainsString('Cc: Alice <alice@mail.example>', $localMessage->getRaw());
$this->assertStringNotContainsString('Bcc:', $localMessage->getRaw());
$this->assertStringContainsString('Bcc: Jane <jane@mail.example>', $localMessage->getRaw());
}

public function testSendMessageOmitCc() {
Expand Down
Loading