diff --git a/appinfo/info.xml b/appinfo/info.xml
index ff85216a7b..e5d0a32329 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -29,7 +29,7 @@ The rating depends on the installed text processing backend. See [the rating ove
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]>
- 3.6.0-rc.2
+ 3.6.0-rc.3
agpl
Christoph Wurst
Nextcloud Groupware Team
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index bc346678ca..2dac019913 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -40,6 +40,7 @@
use OCA\Mail\Dashboard\UnreadMailWidget;
use OCA\Mail\Dashboard\UnreadMailWidgetV2;
use OCA\Mail\Events\BeforeImapClientCreated;
+use OCA\Mail\Events\BeforeMessageSentEvent;
use OCA\Mail\Events\DraftMessageCreatedEvent;
use OCA\Mail\Events\DraftSavedEvent;
use OCA\Mail\Events\MailboxesSynchronizedEvent;
@@ -54,7 +55,9 @@
use OCA\Mail\Http\Middleware\ProvisioningMiddleware;
use OCA\Mail\Listener\AccountSynchronizedThreadUpdaterListener;
use OCA\Mail\Listener\AddressCollectionListener;
+use OCA\Mail\Listener\AntiAbuseListener;
use OCA\Mail\Listener\DeleteDraftListener;
+use OCA\Mail\Listener\FlagRepliedMessageListener;
use OCA\Mail\Listener\HamReportListener;
use OCA\Mail\Listener\InteractionListener;
use OCA\Mail\Listener\MailboxesSynchronizedSpecialMailboxesUpdater;
@@ -65,6 +68,7 @@
use OCA\Mail\Listener\OauthTokenRefreshListener;
use OCA\Mail\Listener\OptionalIndicesListener;
use OCA\Mail\Listener\OutOfOfficeListener;
+use OCA\Mail\Listener\SaveSentMessageListener;
use OCA\Mail\Listener\SpamReportListener;
use OCA\Mail\Listener\UserDeletedListener;
use OCA\Mail\Notification\Notifier;
@@ -128,6 +132,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AddMissingIndicesEvent::class, OptionalIndicesListener::class);
$context->registerEventListener(BeforeImapClientCreated::class, OauthTokenRefreshListener::class);
+ $context->registerEventListener(BeforeMessageSentEvent::class, AntiAbuseListener::class);
$context->registerEventListener(DraftSavedEvent::class, DeleteDraftListener::class);
$context->registerEventListener(DraftMessageCreatedEvent::class, DeleteDraftListener::class);
$context->registerEventListener(OutboxMessageCreatedEvent::class, DeleteDraftListener::class);
@@ -138,7 +143,9 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(MessageFlaggedEvent::class, MoveJunkListener::class);
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
+ $context->registerEventListener(MessageSentEvent::class, FlagRepliedMessageListener::class);
$context->registerEventListener(MessageSentEvent::class, InteractionListener::class);
+ $context->registerEventListener(MessageSentEvent::class, SaveSentMessageListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, NewMessageClassificationListener::class);
$context->registerEventListener(NewMessagesSynchronized::class, MessageKnownSinceListener::class);
$context->registerEventListener(SynchronizationEvent::class, AccountSynchronizedThreadUpdaterListener::class);
diff --git a/lib/BackgroundJob/QuotaJob.php b/lib/BackgroundJob/QuotaJob.php
index c7ab218f39..05f394ea99 100644
--- a/lib/BackgroundJob/QuotaJob.php
+++ b/lib/BackgroundJob/QuotaJob.php
@@ -95,7 +95,7 @@ protected function run($argument): void {
}
$quota = $this->mailManager->getQuota($account);
- if ($quota === null) {
+ if($quota === null) {
$this->logger->debug('Could not get quota information for account <' . $account->getEmail() . '>', ['app' => 'mail']);
return;
}
diff --git a/lib/Contracts/IMailTransmission.php b/lib/Contracts/IMailTransmission.php
index c4f6004136..82732d1e63 100644
--- a/lib/Contracts/IMailTransmission.php
+++ b/lib/Contracts/IMailTransmission.php
@@ -24,6 +24,7 @@
namespace OCA\Mail\Contracts;
use OCA\Mail\Account;
+use OCA\Mail\Db\Alias;
use OCA\Mail\Db\LocalMessage;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message;
@@ -36,12 +37,27 @@ interface IMailTransmission {
/**
* Send a new message or reply to an existing one
*
- * @param Account $account
- * @param LocalMessage $localMessage
+ * @param NewMessageData $messageData
+ * @param string|null $repliedToMessageId
+ * @param Alias|null $alias
+ * @param Message|null $draft
+ *
* @throws SentMailboxNotSetException
* @throws ServiceException
*/
- public function sendMessage(Account $account, LocalMessage $localMessage): void;
+ public function sendMessage(NewMessageData $messageData,
+ ?string $repliedToMessageId = null,
+ ?Alias $alias = null,
+ ?Message $draft = null): void;
+
+ /**
+ * @param Account $account
+ * @param LocalMessage $message
+ * @throws ClientException
+ * @throws ServiceException
+ * @return void
+ */
+ public function sendLocalMessage(Account $account, LocalMessage $message): void;
/**
* @param Account $account
diff --git a/lib/Controller/DraftsController.php b/lib/Controller/DraftsController.php
index 7a96ee801f..47a7f558c7 100644
--- a/lib/Controller/DraftsController.php
+++ b/lib/Controller/DraftsController.php
@@ -172,6 +172,7 @@ public function update(int $id,
$message = $this->service->getMessage($id, $this->userId);
$account = $this->accountService->find($this->userId, $accountId);
+
$message->setType(LocalMessage::TYPE_DRAFT);
$message->setAccountId($accountId);
$message->setAliasId($aliasId);
diff --git a/lib/Controller/OutboxController.php b/lib/Controller/OutboxController.php
index 7613f24dea..e62fccb224 100644
--- a/lib/Controller/OutboxController.php
+++ b/lib/Controller/OutboxController.php
@@ -163,7 +163,7 @@ public function createFromDraft(DraftsService $draftsService, int $id, ?int $sen
$outboxMessage = $this->service->convertDraft($draftMessage, $sendAt);
- return JsonResponse::success(
+ return JsonResponse::success(
$outboxMessage,
Http::STATUS_CREATED,
);
@@ -209,9 +209,6 @@ public function update(
?int $sendAt = null
): JsonResponse {
$message = $this->service->getMessage($id, $this->userId);
- if ($message->getStatus() === LocalMessage::STATUS_PROCESSED) {
- return JsonResponse::error('Cannot modify already sent message', Http::STATUS_FORBIDDEN, [$message]);
- }
$account = $this->accountService->find($this->userId, $accountId);
$message->setAccountId($accountId);
@@ -220,6 +217,7 @@ public function update(
$message->setBody($body);
$message->setEditorBody($editorBody);
$message->setHtml($isHtml);
+ $message->setFailed($failed);
$message->setInReplyToMessageId($inReplyToMessageId);
$message->setSendAt($sendAt);
$message->setSmimeSign($smimeSign);
@@ -246,12 +244,8 @@ public function send(int $id): JsonResponse {
$message = $this->service->getMessage($id, $this->userId);
$account = $this->accountService->find($this->userId, $message->getAccountId());
- $message = $this->service->sendMessage($message, $account);
-
- if($message->getStatus() !== LocalMessage::STATUS_PROCESSED) {
- return JsonResponse::error('Could not send message', Http::STATUS_INTERNAL_SERVER_ERROR, [$message]);
- }
- return JsonResponse::success(
+ $this->service->sendMessage($message, $account);
+ return JsonResponse::success(
'Message sent', Http::STATUS_ACCEPTED
);
}
diff --git a/lib/Db/LocalMessage.php b/lib/Db/LocalMessage.php
index c71e910c25..db3e239eb2 100644
--- a/lib/Db/LocalMessage.php
+++ b/lib/Db/LocalMessage.php
@@ -60,30 +60,13 @@
* @method setSmimeCertificateId(?int $smimeCertificateId)
* @method bool|null getSmimeEncrypt()
* @method setSmimeEncrypt (bool $smimeEncryt)
- * @method int|null getStatus();
- * @method setStatus(?int $status);
- * @method string|null getRaw()
- * @method setRaw(string|null $raw)
*/
class LocalMessage extends Entity implements JsonSerializable {
public const TYPE_OUTGOING = 0;
public const TYPE_DRAFT = 1;
- public const STATUS_RAW = 0;
- public const STATUS_NO_SENT_MAILBOX = 1;
- public const STATUS_SMIME_SIGN_NO_CERT_ID = 2;
- public const STATUS_SMIME_SIGN_CERT = 3;
- public const STATUS_SMIME_SIGN_FAIL = 4;
- public const STATUS_SMIME_ENCRYPT_NO_CERT_ID = 5;
- public const STATUS_SMIME_ENCRYPT_CERT = 6;
- public const STATUS_SMIME_ENCRYT_FAIL = 7;
- public const STATUS_TOO_MANY_RECIPIENTS = 8;
- public const STATUS_RATELIMIT = 9;
- public const STATUS_SMPT_SEND_FAIL = 10;
- public const STATUS_IMAP_SENT_MAILBOX_FAIL = 11;
- public const STATUS_PROCESSED = 12;
/**
- * @var int<1,12>
+ * @var int
* @psalm-var self::TYPE_*
*/
protected $type;
@@ -133,15 +116,6 @@ class LocalMessage extends Entity implements JsonSerializable {
/** @var bool|null */
protected $smimeEncrypt;
- /**
- * @var int|null
- * @psalm-var int-mask-of
- */
- protected $status;
-
- /** @var string|null */
- protected $raw;
-
public function __construct() {
$this->addType('type', 'integer');
$this->addType('accountId', 'integer');
@@ -153,7 +127,6 @@ public function __construct() {
$this->addType('smimeSign', 'boolean');
$this->addType('smimeCertificateId', 'integer');
$this->addType('smimeEncrypt', 'boolean');
- $this->addType('status', 'integer');
}
#[ReturnTypeWillChange]
@@ -195,8 +168,6 @@ public function jsonSerialize() {
'smimeCertificateId' => $this->getSmimeCertificateId(),
'smimeSign' => $this->getSmimeSign() === true,
'smimeEncrypt' => $this->getSmimeEncrypt() === true,
- 'status' => $this->getStatus(),
- 'raw' => $this->getRaw(),
];
}
diff --git a/lib/Db/LocalMessageMapper.php b/lib/Db/LocalMessageMapper.php
index 0eaf469542..54fcc67ae5 100644
--- a/lib/Db/LocalMessageMapper.php
+++ b/lib/Db/LocalMessageMapper.php
@@ -64,8 +64,7 @@ public function getAllForUser(string $userId, int $type = LocalMessage::TYPE_OUT
->join('a', $this->getTableName(), 'm', $qb->expr()->eq('m.account_id', 'a.id'))
->where(
$qb->expr()->eq('a.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR),
- $qb->expr()->eq('m.type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
- $qb->expr()->neq('m.status', $qb->createNamedParameter(LocalMessage::STATUS_PROCESSED, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
+ $qb->expr()->eq('m.type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
);
$rows = $qb->executeQuery();
@@ -135,6 +134,10 @@ public function findDue(int $time, int $type = LocalMessage::TYPE_OUTGOING): arr
$qb->expr()->isNotNull('send_at'),
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
$qb->expr()->lte('send_at', $qb->createNamedParameter($time, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
+ $qb->expr()->orX(
+ $qb->expr()->isNull('failed'),
+ $qb->expr()->eq('failed', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL), IQueryBuilder::PARAM_BOOL),
+ )
)
->orderBy('send_at', 'asc');
$messages = $this->findEntities($select);
diff --git a/lib/Events/BeforeMessageSentEvent.php b/lib/Events/BeforeMessageSentEvent.php
new file mode 100644
index 0000000000..76d233baa6
--- /dev/null
+++ b/lib/Events/BeforeMessageSentEvent.php
@@ -0,0 +1,95 @@
+
+ *
+ * @author 2021 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Events;
+
+use Horde_Mime_Mail;
+use OCA\Mail\Account;
+use OCA\Mail\Db\Message;
+use OCA\Mail\Model\IMessage;
+use OCA\Mail\Model\NewMessageData;
+use OCP\EventDispatcher\Event;
+
+/**
+ * @psalm-immutable
+ */
+class BeforeMessageSentEvent extends Event {
+ /** @var Account */
+ private $account;
+
+ /** @var NewMessageData */
+ private $newMessageData;
+
+ /** @var Message|null */
+ private $draft;
+
+ /** @var IMessage */
+ private $message;
+
+ /** @var Horde_Mime_Mail */
+ private $mail;
+
+ /** @var string|null */
+ private $repliedToMessageId;
+
+ public function __construct(Account $account,
+ NewMessageData $newMessageData,
+ ?string $repliedToMessageId,
+ ?Message $draft,
+ IMessage $message,
+ Horde_Mime_Mail $mail) {
+ parent::__construct();
+ $this->account = $account;
+ $this->newMessageData = $newMessageData;
+ $this->repliedToMessageId = $repliedToMessageId;
+ $this->draft = $draft;
+ $this->message = $message;
+ $this->mail = $mail;
+ }
+
+ public function getAccount(): Account {
+ return $this->account;
+ }
+
+ public function getNewMessageData(): NewMessageData {
+ return $this->newMessageData;
+ }
+
+ public function getRepliedToMessageId(): ?string {
+ return $this->repliedToMessageId;
+ }
+
+ public function getDraft(): ?Message {
+ return $this->draft;
+ }
+
+ public function getMessage(): IMessage {
+ return $this->message;
+ }
+
+ public function getMail(): Horde_Mime_Mail {
+ return $this->mail;
+ }
+}
diff --git a/lib/Events/DraftSavedEvent.php b/lib/Events/DraftSavedEvent.php
index 53626508e8..ed0072b60a 100644
--- a/lib/Events/DraftSavedEvent.php
+++ b/lib/Events/DraftSavedEvent.php
@@ -34,15 +34,15 @@ class DraftSavedEvent extends Event {
/** @var Account */
private $account;
- /** @var NewMessageData|null */
+ /** @var NewMessageData */
private $newMessageData;
/** @var Message|null */
private $draft;
public function __construct(Account $account,
- ?NewMessageData $newMessageData = null,
- ?Message $draft = null) {
+ NewMessageData $newMessageData,
+ ?Message $draft) {
parent::__construct();
$this->account = $account;
$this->newMessageData = $newMessageData;
@@ -53,7 +53,7 @@ public function getAccount(): Account {
return $this->account;
}
- public function getNewMessageData(): ?NewMessageData {
+ public function getNewMessageData(): NewMessageData {
return $this->newMessageData;
}
diff --git a/lib/Events/MessageSentEvent.php b/lib/Events/MessageSentEvent.php
index 2bd8e73404..05f0d82a6f 100644
--- a/lib/Events/MessageSentEvent.php
+++ b/lib/Events/MessageSentEvent.php
@@ -25,8 +25,11 @@
namespace OCA\Mail\Events;
+use Horde_Mime_Mail;
use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
+use OCA\Mail\Db\Message;
+use OCA\Mail\Model\IMessage;
+use OCA\Mail\Model\NewMessageData;
use OCP\EventDispatcher\Event;
/**
@@ -36,17 +39,57 @@ class MessageSentEvent extends Event {
/** @var Account */
private $account;
+ /** @var NewMessageData */
+ private $newMessageData;
+
+ /** @var null|string */
+ private $repliedToMessageId;
+
+ /** @var Message|null */
+ private $draft;
+
+ /** @var IMessage */
+ private $message;
+
+ /** @var Horde_Mime_Mail */
+ private $mail;
+
public function __construct(Account $account,
- private LocalMessage $localMessage) {
+ NewMessageData $newMessageData,
+ ?string $repliedToMessageId,
+ ?Message $draft,
+ IMessage $message,
+ Horde_Mime_Mail $mail) {
parent::__construct();
$this->account = $account;
+ $this->newMessageData = $newMessageData;
+ $this->repliedToMessageId = $repliedToMessageId;
+ $this->draft = $draft;
+ $this->message = $message;
+ $this->mail = $mail;
}
public function getAccount(): Account {
return $this->account;
}
- public function getLocalMessage(): LocalMessage {
- return $this->localMessage;
+ public function getNewMessageData(): NewMessageData {
+ return $this->newMessageData;
+ }
+
+ public function getRepliedToMessageId(): ?string {
+ return $this->repliedToMessageId;
+ }
+
+ public function getDraft(): ?Message {
+ return $this->draft;
+ }
+
+ public function getMessage(): IMessage {
+ return $this->message;
+ }
+
+ public function getMail(): Horde_Mime_Mail {
+ return $this->mail;
}
}
diff --git a/lib/IMAP/MessageMapper.php b/lib/IMAP/MessageMapper.php
index 8503ed2738..e685399591 100644
--- a/lib/IMAP/MessageMapper.php
+++ b/lib/IMAP/MessageMapper.php
@@ -35,6 +35,7 @@
use Horde_Imap_Client_Socket;
use Horde_Mime_Exception;
use Horde_Mime_Headers;
+use Horde_Mime_Mail;
use Horde_Mime_Part;
use Html2Text\Html2Text;
use OCA\Mail\Attachment;
@@ -397,7 +398,7 @@ public function expunge(Horde_Imap_Client_Base $client,
*/
public function save(Horde_Imap_Client_Socket $client,
Mailbox $mailbox,
- string $mail,
+ Horde_Mime_Mail $mail,
array $flags = []): int {
$flags = array_merge([
Horde_Imap_Client::FLAG_SEEN,
@@ -407,7 +408,7 @@ public function save(Horde_Imap_Client_Socket $client,
$mailbox->getName(),
[
[
- 'data' => $mail,
+ 'data' => $mail->getRaw(),
'flags' => $flags,
]
]
diff --git a/lib/Listener/AddressCollectionListener.php b/lib/Listener/AddressCollectionListener.php
index 950f745bde..a6d2a0a373 100644
--- a/lib/Listener/AddressCollectionListener.php
+++ b/lib/Listener/AddressCollectionListener.php
@@ -26,10 +26,8 @@
namespace OCA\Mail\Listener;
use OCA\Mail\Contracts\IUserPreferences;
-use OCA\Mail\Db\Recipient;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Service\AutoCompletion\AddressCollector;
-use OCA\Mail\Service\TransmissionService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use Psr\Log\LoggerInterface;
@@ -50,8 +48,7 @@ class AddressCollectionListener implements IEventListener {
public function __construct(IUserPreferences $preferences,
AddressCollector $collector,
- LoggerInterface $logger,
- private TransmissionService $transmissionService) {
+ LoggerInterface $logger) {
$this->collector = $collector;
$this->logger = $logger;
$this->preferences = $preferences;
@@ -68,12 +65,10 @@ public function handle(Event $event): void {
// Non-essential feature, hence we catch all possible errors
try {
- $message = $event->getLocalMessage();
- $to = $this->transmissionService->getAddressList($message, Recipient::TYPE_TO);
- $cc = $this->transmissionService->getAddressList($message, Recipient::TYPE_CC);
- $bcc = $this->transmissionService->getAddressList($message, Recipient::TYPE_BCC);
-
- $addresses = $to->merge($cc)->merge($bcc);
+ $message = $event->getMessage();
+ $addresses = $message->getTo()
+ ->merge($message->getCC())
+ ->merge($message->getBCC());
$this->collector->addAddresses($event->getAccount()->getUserId(), $addresses);
} catch (Throwable $e) {
diff --git a/lib/Listener/AntiAbuseListener.php b/lib/Listener/AntiAbuseListener.php
new file mode 100644
index 0000000000..a06d0f7f00
--- /dev/null
+++ b/lib/Listener/AntiAbuseListener.php
@@ -0,0 +1,75 @@
+
+ *
+ * @author 2021 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Listener;
+
+use OCA\Mail\Events\BeforeMessageSentEvent;
+use OCA\Mail\Service\AntiAbuseService;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @template-implements IEventListener
+ */
+class AntiAbuseListener implements IEventListener {
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var AntiAbuseService */
+ private $service;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ public function __construct(IUserManager $userManager,
+ AntiAbuseService $service,
+ LoggerInterface $logger) {
+ $this->service = $service;
+ $this->userManager = $userManager;
+ $this->logger = $logger;
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof BeforeMessageSentEvent)) {
+ return;
+ }
+
+ $user = $this->userManager->get($event->getAccount()->getUserId());
+ if ($user === null) {
+ $this->logger->error('User {user} for mail account {id} does not exist', [
+ 'user' => $event->getAccount()->getUserId(),
+ 'id' => $event->getAccount()->getId(),
+ ]);
+ return;
+ }
+
+ $this->service->onBeforeMessageSent(
+ $user,
+ $event->getNewMessageData(),
+ );
+ }
+}
diff --git a/lib/Listener/FlagRepliedMessageListener.php b/lib/Listener/FlagRepliedMessageListener.php
new file mode 100644
index 0000000000..f53d07408b
--- /dev/null
+++ b/lib/Listener/FlagRepliedMessageListener.php
@@ -0,0 +1,114 @@
+
+ *
+ * @author 2019 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Listener;
+
+use Horde_Imap_Client;
+use Horde_Imap_Client_Exception;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Db\MessageMapper as DbMessageMapper;
+use OCA\Mail\Events\MessageSentEvent;
+use OCA\Mail\IMAP\IMAPClientFactory;
+use OCA\Mail\IMAP\MessageMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @template-implements IEventListener
+ */
+class FlagRepliedMessageListener implements IEventListener {
+ /** @var IMAPClientFactory */
+ private $imapClientFactory;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var MessageMapper */
+ private $messageMapper;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var DbMessageMapper */
+ private $dbMessageMapper;
+
+ public function __construct(IMAPClientFactory $imapClientFactory,
+ MailboxMapper $mailboxMapper,
+ DbMessageMapper $dbMessageMapper,
+ MessageMapper $mapper,
+ LoggerInterface $logger) {
+ $this->imapClientFactory = $imapClientFactory;
+ $this->mailboxMapper = $mailboxMapper;
+ $this->dbMessageMapper = $dbMessageMapper;
+ $this->messageMapper = $mapper;
+ $this->logger = $logger;
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof MessageSentEvent) || $event->getRepliedToMessageId() === null) {
+ return;
+ }
+
+ $messages = $this->dbMessageMapper->findByMessageId($event->getAccount(), $event->getRepliedToMessageId());
+ if ($messages === []) {
+ return;
+ }
+
+ try {
+ $client = $this->imapClientFactory->getClient($event->getAccount());
+ foreach ($messages as $message) {
+ try {
+ $mailbox = $this->mailboxMapper->findById($message->getMailboxId());
+ //ignore read-only mailboxes
+ if ($mailbox->getMyAcls() !== null && !strpos($mailbox->getMyAcls(), "w")) {
+ continue;
+ }
+ // ignore drafts and sent
+ if ($mailbox->isSpecialUse('sent') || $mailbox->isSpecialUse('drafts')) {
+ continue;
+ }
+ // Mark all other mailboxes that contain the message with the same imap message id as replied
+ $this->messageMapper->addFlag(
+ $client,
+ $mailbox,
+ [$message->getUid()],
+ Horde_Imap_Client::FLAG_ANSWERED
+ );
+ } catch (DoesNotExistException | Horde_Imap_Client_Exception $e) {
+ $this->logger->warning('Could not flag replied message: ' . $e, [
+ 'exception' => $e,
+ ]);
+ }
+
+ $message->setFlagAnswered(true);
+ $this->dbMessageMapper->update($message);
+ }
+ } finally {
+ $client->logout();
+ }
+ }
+}
diff --git a/lib/Listener/InteractionListener.php b/lib/Listener/InteractionListener.php
index 8f09586f8d..66c3ab0049 100644
--- a/lib/Listener/InteractionListener.php
+++ b/lib/Listener/InteractionListener.php
@@ -70,19 +70,16 @@ public function handle(Event $event): void {
$this->logger->debug('no user object found');
return;
}
- $message = $event->getLocalMessage();
- $emails = [];
- foreach ($message->getRecipients() as $recipient) {
- if (in_array($recipient->getEmail(), $emails)) {
- continue;
- }
+ $recipients = $event->getMessage()->getTo()
+ ->merge($event->getMessage()->getCC())
+ ->merge($event->getMessage()->getBCC());
+ foreach ($recipients->iterate() as $recipient) {
$interactionEvent = new ContactInteractedWithEvent($user);
$email = $recipient->getEmail();
if ($email === null) {
// Weird, bot ok
continue;
}
- $emails[] = $email;
$interactionEvent->setEmail($email);
$this->dispatcher->dispatch(ContactInteractedWithEvent::class, $interactionEvent);
}
diff --git a/lib/Listener/SaveSentMessageListener.php b/lib/Listener/SaveSentMessageListener.php
new file mode 100644
index 0000000000..95df328b63
--- /dev/null
+++ b/lib/Listener/SaveSentMessageListener.php
@@ -0,0 +1,101 @@
+
+ *
+ * @author 2019 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Listener;
+
+use Horde_Imap_Client_Exception;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Events\MessageSentEvent;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\IMAP\IMAPClientFactory;
+use OCA\Mail\IMAP\MessageMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @template-implements IEventListener
+ */
+class SaveSentMessageListener implements IEventListener {
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var IMAPClientFactory */
+ private $imapClientFactory;
+
+ /** @var MessageMapper */
+ private $messageMapper;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ public function __construct(MailboxMapper $mailboxMapper,
+ IMAPClientFactory $imapClientFactory,
+ MessageMapper $messageMapper,
+ LoggerInterface $logger) {
+ $this->mailboxMapper = $mailboxMapper;
+ $this->imapClientFactory = $imapClientFactory;
+ $this->messageMapper = $messageMapper;
+ $this->logger = $logger;
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof MessageSentEvent)) {
+ return;
+ }
+
+ $sentMailboxId = $event->getAccount()->getMailAccount()->getSentMailboxId();
+ if ($sentMailboxId === null) {
+ $this->logger->warning("No sent mailbox exists, can't save sent message");
+ return;
+ }
+
+ // Save the message in the sent mailbox
+ try {
+ $sentMailbox = $this->mailboxMapper->findById(
+ $sentMailboxId
+ );
+ } catch (DoesNotExistException $e) {
+ $this->logger->error("Sent mailbox could not be found", [
+ 'exception' => $e,
+ ]);
+ return;
+ }
+
+ $client = $this->imapClientFactory->getClient($event->getAccount());
+ try {
+ $this->messageMapper->save(
+ $client,
+ $sentMailbox,
+ $event->getMail()
+ );
+ } catch (Horde_Imap_Client_Exception $e) {
+ throw new ServiceException('Could not save sent message on IMAP', 0, $e);
+ } finally {
+ $client->logout();
+ }
+ }
+}
diff --git a/lib/Migration/Version3600Date20240220134813.php b/lib/Migration/Version3600Date20240220134813.php
index 7fcb91d366..c0d9b39c4a 100644
--- a/lib/Migration/Version3600Date20240220134813.php
+++ b/lib/Migration/Version3600Date20240220134813.php
@@ -44,6 +44,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
+ /* REVERTED in Version3600Date20240220134814
$localMessagesTable = $schema->getTable('mail_local_messages');
if (!$localMessagesTable->hasColumn('status')) {
$localMessagesTable->addColumn('status', Types::INTEGER, [
@@ -57,6 +58,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
'default' => null,
]);
}
+ */
return $schema;
}
diff --git a/lib/Migration/Version3600Date20240220134814.php b/lib/Migration/Version3600Date20240220134814.php
new file mode 100644
index 0000000000..0a45560c37
--- /dev/null
+++ b/lib/Migration/Version3600Date20240220134814.php
@@ -0,0 +1,57 @@
+
+ *
+ * @author Anna Larch
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Mail\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version3600Date20240220134814 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $localMessagesTable = $schema->getTable('mail_local_messages');
+ if ($localMessagesTable->hasColumn('status')) {
+ $localMessagesTable->dropColumn('status');
+ }
+ if ($localMessagesTable->hasColumn('raw')) {
+ $localMessagesTable->dropColumn('raw');
+ }
+
+ return $schema;
+ }
+
+}
diff --git a/lib/Send/AHandler.php b/lib/Send/AHandler.php
deleted file mode 100644
index 1de4254c40..0000000000
--- a/lib/Send/AHandler.php
+++ /dev/null
@@ -1,43 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-namespace OCA\Mail\Send;
-
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-
-abstract class AHandler {
-
- protected AHandler|null $next = null;
- public function setNext(AHandler $next): AHandler {
- $this->next = $next;
- return $next;
- }
-
- abstract public function process(Account $account, LocalMessage $localMessage): LocalMessage;
-
- protected function processNext(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($this->next !== null) {
- return $this->next->process($account, $localMessage);
- }
- return $localMessage;
- }
-}
diff --git a/lib/Send/AntiAbuseHandler.php b/lib/Send/AntiAbuseHandler.php
deleted file mode 100644
index 72f0f80dce..0000000000
--- a/lib/Send/AntiAbuseHandler.php
+++ /dev/null
@@ -1,63 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-use OCA\Mail\Service\AntiAbuseService;
-use OCP\IUserManager;
-use Psr\Log\LoggerInterface;
-
-class AntiAbuseHandler extends AHandler {
-
- public function __construct(private IUserManager $userManager,
- private AntiAbuseService $service,
- private LoggerInterface $logger) {
- }
- public function process(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($localMessage->getStatus() === LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL
- || $localMessage->getStatus() === LocalMessage::STATUS_PROCESSED) {
- return $this->processNext($account, $localMessage);
- }
-
- $user = $this->userManager->get($account->getUserId());
- if ($user === null) {
- $this->logger->error('User {user} for mail account {id} does not exist', [
- 'user' => $account->getUserId(),
- 'id' => $account->getId(),
- ]);
- // What to do here?
- return $localMessage;
- }
-
- $this->service->onBeforeMessageSent(
- $user,
- $localMessage,
- );
- // We don't react to a ratelimited message / a message that has too many recipients
- // at this point.
- // Any future improvement from https://github.com/nextcloud/mail/issues/6461
- // should refactor the chain to stop at this point unless the force send option is true
- return $this->processNext($account, $localMessage);
- }
-}
diff --git a/lib/Send/Chain.php b/lib/Send/Chain.php
deleted file mode 100644
index 94e122efab..0000000000
--- a/lib/Send/Chain.php
+++ /dev/null
@@ -1,56 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-use OCA\Mail\Db\LocalMessageMapper;
-use OCA\Mail\Service\Attachment\AttachmentService;
-
-class Chain {
- public function __construct(private SentMailboxHandler $sentMailboxHandler,
- private AntiAbuseHandler $antiAbuseHandler,
- private SendHandler $sendHandler,
- private CopySentMessageHandler $copySentMessageHandler,
- private FlagRepliedMessageHandler $flagRepliedMessageHandler,
- private AttachmentService $attachmentService,
- private LocalMessageMapper $localMessageMapper,
- ) {
- }
-
- public function process(Account $account, LocalMessage $localMessage): void {
- $handlers = $this->sentMailboxHandler;
- $handlers->setNext($this->antiAbuseHandler)
- ->setNext($this->sendHandler)
- ->setNext($this->copySentMessageHandler)
- ->setNext($this->flagRepliedMessageHandler);
-
- $result = $handlers->process($account, $localMessage);
- if ($result->getStatus() === LocalMessage::STATUS_PROCESSED) {
- $this->attachmentService->deleteLocalMessageAttachments($account->getUserId(), $result->getId());
- $this->localMessageMapper->deleteWithRecipients($result);
- return;
- }
- $this->localMessageMapper->update($result);
- }
-}
diff --git a/lib/Send/CopySentMessageHandler.php b/lib/Send/CopySentMessageHandler.php
deleted file mode 100644
index ba98dddaaa..0000000000
--- a/lib/Send/CopySentMessageHandler.php
+++ /dev/null
@@ -1,91 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use Horde_Imap_Client_Exception;
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-use OCA\Mail\Db\MailboxMapper;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MessageMapper;
-use OCP\AppFramework\Db\DoesNotExistException;
-use Psr\Log\LoggerInterface;
-
-class CopySentMessageHandler extends AHandler {
- public function __construct(private IMAPClientFactory $imapClientFactory,
- private MailboxMapper $mailboxMapper,
- private LoggerInterface $logger,
- private MessageMapper $messageMapper
- ) {
- }
- public function process(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($localMessage->getStatus() === LocalMessage::STATUS_PROCESSED) {
- return $this->processNext($account, $localMessage);
- }
-
- $sentMailboxId = $account->getMailAccount()->getSentMailboxId();
- if ($sentMailboxId === null) {
- // We can't write the "sent mailbox" status here bc that would trigger an additional send.
- // Thus, we leave the "imap copy to sent mailbox" status.
- $localMessage->setStatus(LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL);
- $this->logger->warning("No sent mailbox exists, can't save sent message");
- return $localMessage;
- }
-
- // Save the message in the sent mailbox
- try {
- $sentMailbox = $this->mailboxMapper->findById(
- $sentMailboxId
- );
- } catch (DoesNotExistException $e) {
- // We can't write the "sent mailbox" status here bc that would trigger an additional send.
- // Thus, we leave the "imap copy to sent mailbox" status.
- $localMessage->setStatus(LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL);
- $this->logger->error('Sent mailbox could not be found', [
- 'exception' => $e,
- ]);
-
- return $localMessage;
- }
-
- $client = $this->imapClientFactory->getClient($account);
- try {
- $this->messageMapper->save(
- $client,
- $sentMailbox,
- $localMessage->getRaw()
- );
- $localMessage->setStatus(LocalMessage::STATUS_PROCESSED);
- } catch (Horde_Imap_Client_Exception $e) {
- $localMessage->setStatus(LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL);
- $this->logger->error('Could not copy message to sent mailbox', [
- 'exception' => $e,
- ]);
- return $localMessage;
- } finally {
- $client->logout();
- }
-
- return $this->processNext($account, $localMessage);
- }
-}
diff --git a/lib/Send/FlagRepliedMessageHandler.php b/lib/Send/FlagRepliedMessageHandler.php
deleted file mode 100644
index 9745f65db2..0000000000
--- a/lib/Send/FlagRepliedMessageHandler.php
+++ /dev/null
@@ -1,94 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use Horde_Imap_Client;
-use Horde_Imap_Client_Exception;
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-use OCA\Mail\Db\MailboxMapper;
-use OCA\Mail\Db\MessageMapper as DbMessageMapper;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MessageMapper;
-use OCP\AppFramework\Db\DoesNotExistException;
-use Psr\Log\LoggerInterface;
-
-class FlagRepliedMessageHandler extends AHandler {
- public function __construct(private IMAPClientFactory $imapClientFactory,
- private MailboxMapper $mailboxMapper,
- private LoggerInterface $logger,
- private MessageMapper $messageMapper,
- private DbMessageMapper $dbMessageMapper,
- ) {
- }
-
- public function process(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($localMessage->getStatus() !== LocalMessage::STATUS_PROCESSED) {
- return $localMessage;
- }
-
- if ($localMessage->getInReplyToMessageId() === null) {
- return $this->processNext($account, $localMessage);
- }
-
- $messages = $this->dbMessageMapper->findByMessageId($account, $localMessage->getInReplyToMessageId());
- if ($messages === []) {
- return $this->processNext($account, $localMessage);
- }
-
- try {
- $client = $this->imapClientFactory->getClient($account);
- foreach ($messages as $message) {
- try {
- $mailbox = $this->mailboxMapper->findById($message->getMailboxId());
- //ignore read-only mailboxes
- if ($mailbox->getMyAcls() !== null && !strpos($mailbox->getMyAcls(), 'w')) {
- continue;
- }
- // ignore drafts and sent
- if ($mailbox->isSpecialUse('sent') || $mailbox->isSpecialUse('drafts')) {
- continue;
- }
- // Mark all other mailboxes that contain the message with the same imap message id as replied
- $this->messageMapper->addFlag(
- $client,
- $mailbox,
- [$message->getUid()],
- Horde_Imap_Client::FLAG_ANSWERED
- );
- $message->setFlagAnswered(true);
- $this->dbMessageMapper->update($message);
- } catch (DoesNotExistException|Horde_Imap_Client_Exception $e) {
- $this->logger->warning('Could not flag replied message: ' . $e, [
- 'exception' => $e,
- ]);
- }
-
- }
- } finally {
- $client->logout();
- }
-
- return $this->processNext($account, $localMessage);
- }
-}
diff --git a/lib/Send/SendHandler.php b/lib/Send/SendHandler.php
deleted file mode 100644
index c000830bd7..0000000000
--- a/lib/Send/SendHandler.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use OCA\Mail\Account;
-use OCA\Mail\Contracts\IMailTransmission;
-use OCA\Mail\Db\LocalMessage;
-
-class SendHandler extends AHandler {
- public function __construct(private IMailTransmission $transmission,
- ) {
- }
-
- public function process(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($localMessage->getStatus() === LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL
- || $localMessage->getStatus() === LocalMessage::STATUS_PROCESSED) {
- return $this->processNext($account, $localMessage);
- }
-
- $this->transmission->sendMessage($account, $localMessage);
-
- if ($localMessage->getStatus() === LocalMessage::STATUS_RAW || $localMessage->getStatus() === null) {
- return $this->processNext($account, $localMessage);
- }
- // Something went wrong during the sending
- return $localMessage;
- }
-}
diff --git a/lib/Send/SentMailboxHandler.php b/lib/Send/SentMailboxHandler.php
deleted file mode 100644
index 7b5ca8de10..0000000000
--- a/lib/Send/SentMailboxHandler.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Send;
-
-use OCA\Mail\Account;
-use OCA\Mail\Db\LocalMessage;
-
-class SentMailboxHandler extends AHandler {
- public function process(Account $account, LocalMessage $localMessage): LocalMessage {
- if ($account->getMailAccount()->getSentMailboxId() === null) {
- $localMessage->setStatus(LocalMessage::STATUS_NO_SENT_MAILBOX);
- return $localMessage;
- }
- return $this->processNext($account, $localMessage);
- }
-}
diff --git a/lib/Service/AntiAbuseService.php b/lib/Service/AntiAbuseService.php
index 22368bf358..c470325bef 100644
--- a/lib/Service/AntiAbuseService.php
+++ b/lib/Service/AntiAbuseService.php
@@ -26,7 +26,7 @@
namespace OCA\Mail\Service;
use OCA\Mail\AppInfo\Application;
-use OCA\Mail\Db\LocalMessage;
+use OCA\Mail\Model\NewMessageData;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ICacheFactory;
use OCP\IConfig;
@@ -58,7 +58,8 @@ public function __construct(IConfig $config,
$this->logger = $logger;
}
- public function onBeforeMessageSent(IUser $user, LocalMessage $localMessage): void {
+ public function onBeforeMessageSent(IUser $user,
+ NewMessageData $messageData): void {
$abuseDetection = $this->config->getAppValue(
Application::APP_ID,
'abuse_detection',
@@ -69,11 +70,12 @@ public function onBeforeMessageSent(IUser $user, LocalMessage $localMessage): vo
return;
}
- $this->checkNumberOfRecipients($user, $localMessage);
- $this->checkRateLimits($user, $localMessage);
+ $this->checkNumberOfRecipients($user, $messageData);
+ $this->checkRateLimits($user, $messageData);
}
- private function checkNumberOfRecipients(IUser $user, LocalMessage $message): void {
+ private function checkNumberOfRecipients(IUser $user,
+ NewMessageData $messageData): void {
$numberOfRecipientsThreshold = (int)$this->config->getAppValue(
Application::APP_ID,
'abuse_number_of_recipients_per_message_threshold',
@@ -83,10 +85,11 @@ private function checkNumberOfRecipients(IUser $user, LocalMessage $message): vo
return;
}
- $actualNumberOfRecipients = count($message->getRecipients());
+ $actualNumberOfRecipients = count($messageData->getTo())
+ + count($messageData->getCc())
+ + count($messageData->getBcc());
if ($actualNumberOfRecipients >= $numberOfRecipientsThreshold) {
- $message->setStatus(LocalMessage::STATUS_TOO_MANY_RECIPIENTS);
$this->logger->alert('User {user} sends to a suspicious number of recipients. {expected} are allowed. {actual} are used', [
'user' => $user->getUID(),
'expected' => $numberOfRecipientsThreshold,
@@ -95,7 +98,8 @@ private function checkNumberOfRecipients(IUser $user, LocalMessage $message): vo
}
}
- private function checkRateLimits(IUser $user, LocalMessage $message): void {
+ private function checkRateLimits(IUser $user,
+ NewMessageData $messageData): void {
if (!$this->cacheFactory->isAvailable()) {
// No cache, no rate limits
return;
@@ -106,21 +110,16 @@ private function checkRateLimits(IUser $user, LocalMessage $message): void {
return;
}
- $ratelimited = (
- $this->checkRateLimitsForPeriod($user, $cache, '15m', 15 * 60, $message) ||
- $this->checkRateLimitsForPeriod($user, $cache, '1h', 60 * 60, $message) ||
- $this->checkRateLimitsForPeriod($user, $cache, '1d', 24 * 60 * 60, $message)
- );
- if ($ratelimited) {
- $message->setStatus(LocalMessage::STATUS_RATELIMIT);
- }
+ $this->checkRateLimitsForPeriod($user, $messageData, $cache, '15m', 15 * 60);
+ $this->checkRateLimitsForPeriod($user, $messageData, $cache, '1h', 60 * 60);
+ $this->checkRateLimitsForPeriod($user, $messageData, $cache, '1d', 24 * 60 * 60);
}
private function checkRateLimitsForPeriod(IUser $user,
+ NewMessageData $messageData,
IMemcache $cache,
string $id,
- int $period,
- LocalMessage $message): bool {
+ int $period): void {
$maxNumberOfMessages = (int)$this->config->getAppValue(
Application::APP_ID,
'abuse_number_of_messages_per_' . $id,
@@ -128,7 +127,7 @@ private function checkRateLimitsForPeriod(IUser $user,
);
if ($maxNumberOfMessages === 0) {
// No limit set
- return false;
+ return;
}
$now = $this->timeFactory->getTime();
@@ -137,7 +136,7 @@ private function checkRateLimitsForPeriod(IUser $user,
$periodStart = ((int)($now / $period)) * $period;
$cacheKey = implode('_', ['counter', $id, $periodStart]);
$cache->add($cacheKey, 0);
- $counter = $cache->inc($cacheKey, count($message->getRecipients()));
+ $counter = $cache->inc($cacheKey, count($messageData->getTo()) + count($messageData->getCc()) + count($messageData->getBcc()));
if ($counter >= $maxNumberOfMessages) {
$this->logger->alert('User {user} sends a supcious number of messages within {period}. {expected} are allowed. {actual} have been sent', [
@@ -146,8 +145,6 @@ private function checkRateLimitsForPeriod(IUser $user,
'expected' => $maxNumberOfMessages,
'actual' => $counter,
]);
- return true;
}
- return false;
}
}
diff --git a/lib/Service/AntiSpamService.php b/lib/Service/AntiSpamService.php
index 47a0e50de8..e3f1b665ea 100644
--- a/lib/Service/AntiSpamService.php
+++ b/lib/Service/AntiSpamService.php
@@ -25,36 +25,34 @@
namespace OCA\Mail\Service;
-use Horde_Imap_Client_Exception;
-use Horde_Mime_Exception;
-use Horde_Mime_Mail;
use OCA\Mail\Account;
-use OCA\Mail\Address;
-use OCA\Mail\AddressList;
+use OCA\Mail\Contracts\IMailTransmission;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MessageMapper;
-use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\SentMailboxNotSetException;
use OCA\Mail\Exception\ServiceException;
-use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper;
-use OCA\Mail\Service\DataUri\DataUriParser;
-use OCA\Mail\SMTP\SmtpClientFactory;
-use OCP\AppFramework\Db\DoesNotExistException;
+use OCA\Mail\Model\NewMessageData;
use OCP\IConfig;
-use Psr\Log\LoggerInterface;
class AntiSpamService {
private const NAME = 'antispam_reporting';
private const MESSAGE_TYPE = 'message/rfc822';
- public function __construct(private IConfig $config,
- private MessageMapper $dbMessageMapper,
- private MailManager $mailManager,
- private IMAPClientFactory $imapClientFactory,
- private SmtpClientFactory $smtpClientFactory,
- private ImapMessageMapper $messageMapper,
- private LoggerInterface $logger,
- ) {
+ /** @var IConfig */
+ private $config;
+
+ /** @var MessageMapper */
+ private $messageMapper;
+
+ /** @var IMailTransmission */
+ private $transmission;
+
+ public function __construct(IConfig $config,
+ MessageMapper $messageMapper,
+ IMailTransmission $transmission) {
+ $this->config = $config;
+ $this->messageMapper = $messageMapper;
+ $this->transmission = $transmission;
}
public function getSpamEmail(): string {
@@ -101,126 +99,25 @@ public function sendReportEmail(Account $account, Mailbox $mailbox, int $uid, st
$subject = ($flag === '$junk') ? $this->getSpamSubject() : $this->getHamSubject();
// Message to attach not found
- $messageId = $this->dbMessageMapper->getIdForUid($mailbox, $uid);
+ $messageId = $this->messageMapper->getIdForUid($mailbox, $uid);
if ($messageId === null) {
throw new ServiceException('Could not find reported message');
}
- if ($account->getMailAccount()->getSentMailboxId() === null) {
- throw new ServiceException('Could not find sent mailbox');
- }
-
- $message = $account->newMessage();
- $from = new AddressList([
- Address::fromRaw($account->getName(), $account->getEMailAddress()),
- ]);
- $to = new AddressList([
- Address::fromRaw($reportEmail, $reportEmail),
- ]);
- $message->setTo($to);
- $message->setSubject($subject);
- $message->setFrom($from);
- $message->setContent($subject);
-
- // Gets original of other message
- $userId = $account->getMailAccount()->getUserId();
- try {
- $attachmentMessage = $this->mailManager->getMessage($userId, $messageId);
- } catch (DoesNotExistException $e) {
- $this->logger->error('Could not find reported email with message ID #' . $messageId, ['exception' => $e]);
- return;
- }
-
- $mailbox = $this->mailManager->getMailbox($userId, $attachmentMessage->getMailboxId());
-
- $client = $this->imapClientFactory->getClient($account);
- try {
- $fullText = $this->messageMapper->getFullText(
- $client,
- $mailbox->getName(),
- $attachmentMessage->getUid(),
- $userId
- );
- } finally {
- $client->logout();
- }
-
- $message->addEmbeddedMessageAttachment(
- $attachmentMessage->getSubject() . '.eml',
- $fullText
+ $messageData = NewMessageData::fromRequest(
+ $account,
+ $reportEmail,
+ null,
+ null,
+ $subject,
+ $subject, // add any message body - not all IMAP servers accept empty emails
+ [['id' => $messageId, 'type' => self::MESSAGE_TYPE]]
);
- $transport = $this->smtpClientFactory->create($account);
- // build mime body
- $headers = [
- 'From' => $message->getFrom()->first()->toHorde(),
- 'To' => $message->getTo()->toHorde(),
- 'Cc' => $message->getCC()->toHorde(),
- 'Bcc' => $message->getBCC()->toHorde(),
- 'Subject' => $message->getSubject(),
- ];
-
- if (($inReplyTo = $message->getInReplyTo()) !== null) {
- $headers['References'] = $inReplyTo;
- $headers['In-Reply-To'] = $inReplyTo;
- }
-
- $mail = new Horde_Mime_Mail();
- $mail->addHeaders($headers);
-
- $mimeMessage = new MimeMessage(
- new DataUriParser()
- );
- $mimePart = $mimeMessage->build(
- true,
- $message->getContent(),
- $message->getAttachments()
- );
-
- $mail->setBasePart($mimePart);
-
- // Send the message
try {
- $mail->send($transport, false, false);
- } catch (Horde_Mime_Exception $e) {
- throw new ServiceException(
- 'Could not send message: ' . $e->getMessage(),
- $e->getCode(),
- $e
- );
- }
-
- $sentMailboxId = $account->getMailAccount()->getSentMailboxId();
- if ($sentMailboxId === null) {
- $this->logger->warning("No sent mailbox exists, can't save sent message");
- return;
- }
-
- // Save the message in the sent mailbox
- try {
- $sentMailbox = $this->mailManager->getMailbox(
- $account->getUserId(),
- $sentMailboxId
- );
- } catch (ClientException $e) {
- $this->logger->error('Sent mailbox could not be found', [
- 'exception' => $e,
- ]);
- return;
- }
-
- $client = $this->imapClientFactory->getClient($account);
- try {
- $this->messageMapper->save(
- $client,
- $sentMailbox,
- $mail->getRaw(false)
- );
- } catch (Horde_Imap_Client_Exception $e) {
- $this->logger->error('Could not move report email to sent mailbox, but the report email was sent. Reported email was id: #' . $messageId, ['exception' => $e]);
- } finally {
- $client->logout();
+ $this->transmission->sendMessage($messageData);
+ } catch (SentMailboxNotSetException | ServiceException $e) {
+ throw new ServiceException('Could not send report email from anti spam email service', 0, $e);
}
}
-
}
diff --git a/lib/Service/DraftsService.php b/lib/Service/DraftsService.php
index 9fb4eee8a5..f3c9d596b4 100644
--- a/lib/Service/DraftsService.php
+++ b/lib/Service/DraftsService.php
@@ -119,10 +119,6 @@ public function saveMessage(Account $account, LocalMessage $message, array $to,
throw new ClientException('Cannot convert message to outbox message without at least one recipient');
}
- // Explicitly reset the status, so we can try sending from scratch again
- // in case the user has updated a failing component
- $message->setStatus(LocalMessage::STATUS_RAW);
-
$message = $this->mapper->saveWithRecipients($message, $toRecipients, $ccRecipients, $bccRecipients);
if ($attachments === []) {
diff --git a/lib/Service/MailTransmission.php b/lib/Service/MailTransmission.php
index c3122e9a97..ca7d09d913 100644
--- a/lib/Service/MailTransmission.php
+++ b/lib/Service/MailTransmission.php
@@ -41,79 +41,156 @@
use OCA\Mail\Account;
use OCA\Mail\Address;
use OCA\Mail\AddressList;
+use OCA\Mail\Contracts\IAttachmentService;
+use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Contracts\IMailTransmission;
+use OCA\Mail\Db\Alias;
+use OCA\Mail\Db\LocalAttachment;
use OCA\Mail\Db\LocalMessage;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\Recipient;
+use OCA\Mail\Events\BeforeMessageSentEvent;
use OCA\Mail\Events\DraftSavedEvent;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Events\SaveDraftEvent;
+use OCA\Mail\Exception\AttachmentNotFoundException;
use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\SentMailboxNotSetException;
use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Exception\SmimeEncryptException;
+use OCA\Mail\Exception\SmimeSignException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MessageMapper;
+use OCA\Mail\Model\IMessage;
use OCA\Mail\Model\NewMessageData;
use OCA\Mail\Service\DataUri\DataUriParser;
use OCA\Mail\SMTP\SmtpClientFactory;
use OCA\Mail\Support\PerformanceLogger;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\File;
+use OCP\Files\Folder;
use Psr\Log\LoggerInterface;
+use function array_filter;
+use function array_map;
class MailTransmission implements IMailTransmission {
- public function __construct(
- private IMAPClientFactory $imapClientFactory,
- private SmtpClientFactory $smtpClientFactory,
- private IEventDispatcher $eventDispatcher,
- private MailboxMapper $mailboxMapper,
- private MessageMapper $messageMapper,
- private LoggerInterface $logger,
- private PerformanceLogger $performanceLogger,
- private AliasesService $aliasesService,
- private TransmissionService $transmissionService
- ) {
+ private SmimeService $smimeService;
+
+ /** @var Folder */
+ private $userFolder;
+
+ /** @var IAttachmentService */
+ private $attachmentService;
+
+ /** @var IMailManager */
+ private $mailManager;
+
+ /** @var IMAPClientFactory */
+ private $imapClientFactory;
+
+ /** @var SmtpClientFactory */
+ private $smtpClientFactory;
+
+ /** @var IEventDispatcher */
+ private $eventDispatcher;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var MessageMapper */
+ private $messageMapper;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var PerformanceLogger */
+ private $performanceLogger;
+
+ /** @var AliasesService */
+ private $aliasesService;
+
+ /** @var GroupsIntegration */
+ private $groupsIntegration;
+
+ /**
+ * @param Folder $userFolder
+ */
+ public function __construct($userFolder,
+ IAttachmentService $attachmentService,
+ IMailManager $mailManager,
+ IMAPClientFactory $imapClientFactory,
+ SmtpClientFactory $smtpClientFactory,
+ IEventDispatcher $eventDispatcher,
+ MailboxMapper $mailboxMapper,
+ MessageMapper $messageMapper,
+ LoggerInterface $logger,
+ PerformanceLogger $performanceLogger,
+ AliasesService $aliasesService,
+ GroupsIntegration $groupsIntegration,
+ SmimeService $smimeService) {
+ $this->userFolder = $userFolder;
+ $this->attachmentService = $attachmentService;
+ $this->mailManager = $mailManager;
+ $this->imapClientFactory = $imapClientFactory;
+ $this->smtpClientFactory = $smtpClientFactory;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->mailboxMapper = $mailboxMapper;
+ $this->messageMapper = $messageMapper;
+ $this->logger = $logger;
+ $this->performanceLogger = $performanceLogger;
+ $this->aliasesService = $aliasesService;
+ $this->groupsIntegration = $groupsIntegration;
+ $this->smimeService = $smimeService;
}
- public function sendMessage(Account $account, LocalMessage $localMessage): void {
- $to = $this->transmissionService->getAddressList($localMessage, Recipient::TYPE_TO);
- $cc = $this->transmissionService->getAddressList($localMessage, Recipient::TYPE_CC);
- $bcc = $this->transmissionService->getAddressList($localMessage, Recipient::TYPE_BCC);
- $attachments = $this->transmissionService->getAttachments($localMessage);
+ public function sendMessage(NewMessageData $messageData,
+ ?string $repliedToMessageId = null,
+ ?Alias $alias = null,
+ ?Message $draft = null): void {
+ $account = $messageData->getAccount();
+ if ($account->getMailAccount()->getSentMailboxId() === null) {
+ throw new SentMailboxNotSetException();
+ }
- $alias = null;
- if ($localMessage->getAliasId() !== null) {
- $alias = $this->aliasesService->find($localMessage->getAliasId(), $account->getUserId());
+ if ($repliedToMessageId !== null) {
+ $message = $this->buildReplyMessage($account, $messageData, $repliedToMessageId);
+ } else {
+ $message = $this->buildNewMessage($account, $messageData);
}
+
+ $account->setAlias($alias);
$fromEmail = $alias ? $alias->getAlias() : $account->getEMailAddress();
$from = new AddressList([
Address::fromRaw($account->getName(), $fromEmail),
]);
-
- $attachmentParts = [];
- foreach ($attachments as $attachment) {
- $part = $this->transmissionService->handleAttachment($account, $attachment);
- if ($part !== null) {
- $attachmentParts[] = $part;
- }
- }
+ $message->setFrom($from);
+ $message->setCC($messageData->getCc());
+ $message->setBcc($messageData->getBcc());
+ $message->setContent($messageData->getBody());
+ $this->handleAttachments($account, $messageData, $message); // only ever going to be local attachments
$transport = $this->smtpClientFactory->create($account);
// build mime body
$headers = [
- 'From' => $from->first()->toHorde(),
- 'To' => $to->toHorde(),
- 'Cc' => $cc->toHorde(),
- 'Bcc' => $bcc->toHorde(),
- 'Subject' => $localMessage->getSubject(),
+ 'From' => $message->getFrom()->first()->toHorde(),
+ 'To' => $message->getTo()->toHorde(),
+ 'Cc' => $message->getCC()->toHorde(),
+ 'Bcc' => $message->getBCC()->toHorde(),
+ 'Subject' => $message->getSubject(),
];
- if (($inReplyTo = $localMessage->getInReplyToMessageId()) !== null) {
+ if (($inReplyTo = $message->getInReplyTo()) !== null) {
$headers['References'] = $inReplyTo;
$headers['In-Reply-To'] = $inReplyTo;
}
+ if ($messageData->isMdnRequested()) {
+ $headers[Horde_Mime_Mdn::MDN_HEADER] = $message->getFrom()->first()->toHorde();
+ }
+
$mail = new Horde_Mime_Mail();
$mail->addHeaders($headers);
@@ -121,59 +198,170 @@ public function sendMessage(Account $account, LocalMessage $localMessage): void
new DataUriParser()
);
$mimePart = $mimeMessage->build(
- $localMessage->isHtml(),
- $localMessage->getBody(),
- $attachmentParts
+ $messageData->isHtml(),
+ $message->getContent(),
+ $message->getAttachments()
);
// TODO: add smimeEncrypt check if implemented
- try {
- $mimePart = $this->transmissionService->getSignMimePart($localMessage, $account, $mimePart);
- $mimePart = $this->transmissionService->getEncryptMimePart($localMessage, $to, $cc, $bcc, $account, $mimePart);
- } catch (ServiceException $e) {
- $this->logger->error($e->getMessage(), ['exception' => $e]);
- return;
+ if ($messageData->getSmimeSign()) {
+ if ($messageData->getSmimeCertificateId() === null) {
+ throw new ServiceException('Could not send message: Requested S/MIME signature without certificate id');
+ }
+
+ try {
+ $certificate = $this->smimeService->findCertificate(
+ $messageData->getSmimeCertificateId(),
+ $account->getUserId(),
+ );
+ $mimePart = $this->smimeService->signMimePart($mimePart, $certificate);
+ } catch (DoesNotExistException $e) {
+ throw new ServiceException(
+ 'Could not send message: Certificate does not exist: ' . $e->getMessage(),
+ $e->getCode(),
+ $e,
+ );
+ } catch (SmimeSignException | ServiceException $e) {
+ throw new ServiceException(
+ 'Could not send message: Failed to sign MIME part: ' . $e->getMessage(),
+ $e->getCode(),
+ $e,
+ );
+ }
+ }
+
+ if ($messageData->getSmimeEncrypt()) {
+ if ($messageData->getSmimeCertificateId() === null) {
+ throw new ServiceException('Could not send message: Requested S/MIME signature without certificate id');
+ }
+
+ try {
+ $addressList = $messageData->getTo()
+ ->merge($messageData->getCc())
+ ->merge($messageData->getBcc());
+ $certificates = $this->smimeService->findCertificatesByAddressList($addressList, $account->getUserId());
+
+ $senderCertificate = $this->smimeService->findCertificate($messageData->getSmimeCertificateId(), $account->getUserId());
+ $certificates[] = $senderCertificate;
+
+ $mimePart = $this->smimeService->encryptMimePart($mimePart, $certificates);
+ } catch (DoesNotExistException $e) {
+ throw new ServiceException(
+ 'Could not send message: Certificate does not exist: ' . $e->getMessage(),
+ $e->getCode(),
+ $e,
+ );
+ } catch (SmimeEncryptException | ServiceException $e) {
+ throw new ServiceException(
+ 'Could not send message: Failed to encrypt MIME part: ' . $e->getMessage(),
+ $e->getCode(),
+ $e,
+ );
+ }
}
$mail->setBasePart($mimePart);
+ $this->eventDispatcher->dispatchTyped(
+ new BeforeMessageSentEvent($account, $messageData, $repliedToMessageId, $draft, $message, $mail)
+ );
+
// Send the message
try {
$mail->send($transport, false, false);
- $localMessage->setRaw($mail->getRaw(false));
} catch (Horde_Mime_Exception $e) {
- $localMessage->setStatus(LocalMessage::STATUS_SMPT_SEND_FAIL);
- $this->logger->error($e->getMessage(), ['exception' => $e]);
- return;
+ throw new ServiceException(
+ 'Could not send message: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ );
}
$this->eventDispatcher->dispatchTyped(
- new MessageSentEvent($account, $localMessage)
+ new MessageSentEvent($account, $messageData, $repliedToMessageId, $draft, $message, $mail)
+ );
+ }
+
+ public function sendLocalMessage(Account $account, LocalMessage $message): void {
+ $to = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_TO;
+ }))
+ )
+ );
+ $cc = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_CC;
+ }))
+ )
);
+ $bcc = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_BCC;
+ }))
+ )
+ );
+ $attachments = array_map(static function (LocalAttachment $attachment) {
+ // Convert to the untyped nested array used in \OCA\Mail\Controller\AccountsController::send
+ return [
+ 'type' => 'local',
+ 'id' => $attachment->getId(),
+ ];
+ }, $message->getAttachments());
+ $messageData = new NewMessageData(
+ $account,
+ $to,
+ $cc,
+ $bcc,
+ $message->getSubject(),
+ $message->getBody(),
+ $attachments,
+ $message->isHtml(),
+ false,
+ $message->getSmimeCertificateId(),
+ $message->getSmimeSign() ?? false,
+ $message->getSmimeEncrypt() ?? false,
+ );
+
+ if ($message->getAliasId() !== null) {
+ $alias = $this->aliasesService->find($message->getAliasId(), $account->getUserId());
+ }
+
+ try {
+ $this->sendMessage($messageData, $message->getInReplyToMessageId(), $alias ?? null);
+ } catch (SentMailboxNotSetException $e) {
+ throw new ClientException('Could not send message: ' . $e->getMessage(), $e->getCode(), $e);
+ }
}
public function saveLocalDraft(Account $account, LocalMessage $message): void {
- $to = $this->transmissionService->getAddressList($message, Recipient::TYPE_TO);
- $cc = $this->transmissionService->getAddressList($message, Recipient::TYPE_CC);
- $bcc = $this->transmissionService->getAddressList($message, Recipient::TYPE_BCC);
- $attachments = $this->transmissionService->getAttachments($message);
+ $messageData = $this->getNewMessageData($message, $account);
$perfLogger = $this->performanceLogger->start('save local draft');
+ $account = $messageData->getAccount();
$imapMessage = $account->newMessage();
- $imapMessage->setTo($to);
- $imapMessage->setSubject($message->getSubject());
+ $imapMessage->setTo($messageData->getTo());
+ $imapMessage->setSubject($messageData->getSubject());
$from = new AddressList([
Address::fromRaw($account->getName(), $account->getEMailAddress()),
]);
$imapMessage->setFrom($from);
- $imapMessage->setCC($cc);
- $imapMessage->setBcc($bcc);
- $imapMessage->setContent($message->getBody());
-
- foreach ($attachments as $attachment) {
- $this->transmissionService->handleAttachment($account, $attachment);
- }
+ $imapMessage->setCC($messageData->getCc());
+ $imapMessage->setBcc($messageData->getBcc());
+ $imapMessage->setContent($messageData->getBody());
// build mime body
$headers = [
@@ -210,7 +398,7 @@ public function saveLocalDraft(Account $account, LocalMessage $message): void {
$this->messageMapper->save(
$client,
$draftsMailbox,
- $mail->getRaw(false),
+ $mail,
[Horde_Imap_Client::FLAG_DRAFT]
);
$perfLogger->step('save local draft message on IMAP');
@@ -222,7 +410,7 @@ public function saveLocalDraft(Account $account, LocalMessage $message): void {
$client->logout();
}
- $this->eventDispatcher->dispatchTyped(new DraftSavedEvent($account, null));
+ $this->eventDispatcher->dispatchTyped(new DraftSavedEvent($account, $messageData, null));
$perfLogger->step('emit post local draft save event');
$perfLogger->end();
@@ -292,7 +480,7 @@ public function saveDraft(NewMessageData $message, ?Message $previousDraft = nul
$newUid = $this->messageMapper->save(
$client,
$draftsMailbox,
- $mail->getRaw(false),
+ $mail,
[Horde_Imap_Client::FLAG_DRAFT]
);
$perfLogger->step('save message on IMAP');
@@ -314,6 +502,201 @@ public function saveDraft(NewMessageData $message, ?Message $previousDraft = nul
return [$account, $draftsMailbox, $newUid];
}
+ private function buildReplyMessage(Account $account,
+ NewMessageData $messageData,
+ string $repliedToMessageId): IMessage {
+ // Reply
+ $message = $account->newMessage();
+ $message->setSubject($messageData->getSubject());
+ $message->setTo($messageData->getTo());
+ $message->setInReplyTo($repliedToMessageId);
+
+ return $message;
+ }
+
+ private function buildNewMessage(Account $account, NewMessageData $messageData): IMessage {
+ // New message
+ $message = $account->newMessage();
+ $message->setTo($messageData->getTo());
+ $message->setSubject($messageData->getSubject());
+
+ return $message;
+ }
+
+ /**
+ * @param Account $account
+ * @param NewMessageData $messageData
+ * @param IMessage $message
+ *
+ * @return void
+ */
+ private function handleAttachments(Account $account, NewMessageData $messageData, IMessage $message): void {
+ foreach ($messageData->getAttachments() as $attachment) {
+ if (isset($attachment['type']) && $attachment['type'] === 'local') {
+ // Adds an uploaded attachment
+ $this->handleLocalAttachment($account, $attachment, $message);
+ } elseif (isset($attachment['type']) && $attachment['type'] === 'message') {
+ // Adds another message as attachment
+ $this->handleForwardedMessageAttachment($account, $attachment, $message);
+ } elseif (isset($attachment['type']) && $attachment['type'] === 'message/rfc822') {
+ // Adds another message as attachment with mime type 'message/rfc822
+ $this->handleEmbeddedMessageAttachments($account, $attachment, $message);
+ } elseif (isset($attachment['type']) && $attachment['type'] === 'message-attachment') {
+ // Adds an attachment from another email (use case is, eg., a mail forward)
+ $this->handleForwardedAttachment($account, $attachment, $message);
+ } else {
+ // Adds an attachment from Files
+ $this->handleCloudAttachment($attachment, $message);
+ }
+ }
+ }
+
+ /**
+ * @param Account $account
+ * @param array $attachment
+ * @param IMessage $message
+ *
+ * @return int|null
+ */
+ private function handleLocalAttachment(Account $account, array $attachment, IMessage $message) {
+ if (!isset($attachment['id'])) {
+ $this->logger->warning('ignoring local attachment because its id is unknown');
+ return null;
+ }
+
+ $id = (int)$attachment['id'];
+
+ try {
+ [$localAttachment, $file] = $this->attachmentService->getAttachment($account->getMailAccount()->getUserId(), $id);
+ $message->addLocalAttachment($localAttachment, $file);
+ } catch (AttachmentNotFoundException $ex) {
+ $this->logger->warning('ignoring local attachment because it does not exist');
+ // TODO: rethrow?
+ return null;
+ }
+ }
+
+ /**
+ * Adds an attachment that's coming from another message's attachment (typical use case: email forwarding)
+ *
+ * @param Account $account
+ * @param mixed[] $attachment
+ * @param IMessage $message
+ */
+ private function handleForwardedMessageAttachment(Account $account, array $attachment, IMessage $message): void {
+ // Gets original of other message
+ $userId = $account->getMailAccount()->getUserId();
+ $attachmentMessage = $this->mailManager->getMessage($userId, (int)$attachment['id']);
+ $mailbox = $this->mailManager->getMailbox($userId, $attachmentMessage->getMailboxId());
+
+ $client = $this->imapClientFactory->getClient($account);
+ try {
+ $fullText = $this->messageMapper->getFullText(
+ $client,
+ $mailbox->getName(),
+ $attachmentMessage->getUid(),
+ $userId
+ );
+ } finally {
+ $client->logout();
+ }
+
+ $message->addRawAttachment(
+ $attachment['displayName'] ?? $attachmentMessage->getSubject() . '.eml',
+ $fullText
+ );
+ }
+
+ /**
+ * Adds an email as attachment
+ *
+ * @param Account $account
+ * @param mixed[] $attachment
+ * @param IMessage $message
+ */
+ private function handleEmbeddedMessageAttachments(Account $account, array $attachment, IMessage $message): void {
+ // Gets original of other message
+ $userId = $account->getMailAccount()->getUserId();
+ $attachmentMessage = $this->mailManager->getMessage($userId, (int)$attachment['id']);
+ $mailbox = $this->mailManager->getMailbox($userId, $attachmentMessage->getMailboxId());
+
+ $client = $this->imapClientFactory->getClient($account);
+ try {
+ $fullText = $this->messageMapper->getFullText(
+ $client,
+ $mailbox->getName(),
+ $attachmentMessage->getUid(),
+ $userId
+ );
+ } finally {
+ $client->logout();
+ }
+
+ $message->addEmbeddedMessageAttachment(
+ $attachment['displayName'] ?? $attachmentMessage->getSubject() . '.eml',
+ $fullText
+ );
+ }
+
+
+ /**
+ * Adds an attachment that's coming from another message's attachment (typical use case: email forwarding)
+ *
+ * @param Account $account
+ * @param mixed[] $attachment
+ * @param IMessage $message
+ */
+ private function handleForwardedAttachment(Account $account, array $attachment, IMessage $message): void {
+ // Gets attachment from other message
+ $userId = $account->getMailAccount()->getUserId();
+ $attachmentMessage = $this->mailManager->getMessage($userId, (int)$attachment['messageId']);
+ $mailbox = $this->mailManager->getMailbox($userId, $attachmentMessage->getMailboxId());
+ $client = $this->imapClientFactory->getClient($account);
+ try {
+ $attachments = $this->messageMapper->getRawAttachments(
+ $client,
+ $mailbox->getName(),
+ $attachmentMessage->getUid(),
+ $userId,
+ [
+ $attachment['id']
+ ]
+ );
+ } finally {
+ $client->logout();
+ }
+
+ // Attaches attachment to new message
+ $message->addRawAttachment($attachment['fileName'], $attachments[0]);
+ }
+
+ /**
+ * @param array $attachment
+ * @param IMessage $message
+ *
+ * @return File|null
+ */
+ private function handleCloudAttachment(array $attachment, IMessage $message) {
+ if (!isset($attachment['fileName'])) {
+ $this->logger->warning('ignoring cloud attachment because its fileName is unknown');
+ return null;
+ }
+
+ $fileName = $attachment['fileName'];
+ if (!$this->userFolder->nodeExists($fileName)) {
+ $this->logger->warning('ignoring cloud attachment because the node does not exist');
+ return null;
+ }
+
+ $file = $this->userFolder->get($fileName);
+ if (!$file instanceof File) {
+ $this->logger->warning('ignoring cloud attachment because the node is not a file');
+ return null;
+ }
+
+ $message->addAttachmentFromFiles($file);
+ }
+
public function sendMdn(Account $account, Mailbox $mailbox, Message $message): void {
$query = new Horde_Imap_Client_Fetch_Query();
$query->flags();
@@ -383,4 +766,59 @@ public function sendMdn(Account $account, Mailbox $mailbox, Message $message): v
}
}
+ /**
+ * @param LocalMessage $message
+ * @param Account $account
+ * @return NewMessageData
+ */
+ private function getNewMessageData(LocalMessage $message, Account $account): NewMessageData {
+ $to = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_TO;
+ }))
+ )
+ );
+
+ $cc = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_CC;
+ }))
+ )
+ );
+ $bcc = new AddressList(
+ array_map(
+ static function ($recipient) {
+ return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
+ },
+ $this->groupsIntegration->expand(array_filter($message->getRecipients(), static function (Recipient $recipient) {
+ return $recipient->getType() === Recipient::TYPE_BCC;
+ }))
+ )
+ );
+ $attachments = array_map(function (LocalAttachment $attachment) {
+ // Convert to the untyped nested array used in \OCA\Mail\Controller\AccountsController::send
+ return [
+ 'type' => 'local',
+ 'id' => $attachment->getId(),
+ ];
+ }, $message->getAttachments());
+ return new NewMessageData(
+ $account,
+ $to,
+ $cc,
+ $bcc,
+ $message->getSubject(),
+ $message->getBody(),
+ $attachments,
+ $message->isHtml()
+ );
+ }
}
diff --git a/lib/Service/OutboxService.php b/lib/Service/OutboxService.php
index a39b6d761e..8afc6b1309 100644
--- a/lib/Service/OutboxService.php
+++ b/lib/Service/OutboxService.php
@@ -34,8 +34,8 @@
use OCA\Mail\Db\Recipient;
use OCA\Mail\Events\OutboxMessageCreatedEvent;
use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
-use OCA\Mail\Send\Chain;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -79,9 +79,7 @@ public function __construct(IMailTransmission $transmission,
IMailManager $mailManager,
AccountService $accountService,
ITimeFactory $timeFactory,
- LoggerInterface $logger,
- private Chain $sendChain,
- ) {
+ LoggerInterface $logger) {
$this->transmission = $transmission;
$this->mapper = $mapper;
$this->attachmentService = $attachmentService;
@@ -132,9 +130,24 @@ public function deleteMessage(string $userId, LocalMessage $message): void {
$this->mapper->deleteWithRecipients($message);
}
- public function sendMessage(LocalMessage $message, Account $account): LocalMessage {
- $this->sendChain->process($account, $message);
- return $message;
+ /**
+ * @param LocalMessage $message
+ * @param Account $account
+ * @return void
+ * @throws ClientException
+ * @throws ServiceException
+ */
+ public function sendMessage(LocalMessage $message, Account $account): void {
+ try {
+ $this->transmission->sendLocalMessage($account, $message);
+ } catch (ClientException|ServiceException $e) {
+ // Mark as failed so the message is not sent repeatedly in background
+ $message->setFailed(true);
+ $this->mapper->update($message);
+ throw $e;
+ }
+ $this->attachmentService->deleteLocalMessageAttachments($account->getUserId(), $message->getId());
+ $this->mapper->deleteWithRecipients($message);
}
/**
@@ -181,7 +194,6 @@ public function updateMessage(Account $account, LocalMessage $message, array $to
$toRecipients = self::convertToRecipient($to, Recipient::TYPE_TO);
$ccRecipients = self::convertToRecipient($cc, Recipient::TYPE_CC);
$bccRecipients = self::convertToRecipient($bcc, Recipient::TYPE_BCC);
-
$message = $this->mapper->updateWithRecipients($message, $toRecipients, $ccRecipients, $bccRecipients);
if ($attachments === []) {
@@ -239,13 +251,16 @@ public function flush(): void {
}, $accountIds));
foreach ($messages as $message) {
- $account = $accounts[$message->getAccountId()];
- if ($account === null) {
- // Ignore message of non-existent account
- continue;
- }
try {
- $this->sendChain->process($account, $message);
+ $account = $accounts[$message->getAccountId()];
+ if ($account === null) {
+ // Ignore message of non-existent account
+ continue;
+ }
+ $this->sendMessage(
+ $message,
+ $account,
+ );
$this->logger->debug('Outbox message {id} sent', [
'id' => $message->getId(),
]);
diff --git a/lib/Service/TransmissionService.php b/lib/Service/TransmissionService.php
deleted file mode 100644
index 4f3a95b9af..0000000000
--- a/lib/Service/TransmissionService.php
+++ /dev/null
@@ -1,196 +0,0 @@
-
- *
- * @author Anna Larch
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
- */
-
-namespace OCA\Mail\Service;
-
-use OCA\Mail\Account;
-use OCA\Mail\Address;
-use OCA\Mail\AddressList;
-use OCA\Mail\Db\LocalAttachment;
-use OCA\Mail\Db\LocalMessage;
-use OCA\Mail\Db\Recipient;
-use OCA\Mail\Exception\AttachmentNotFoundException;
-use OCA\Mail\Exception\ServiceException;
-use OCA\Mail\Exception\SmimeEncryptException;
-use OCA\Mail\Exception\SmimeSignException;
-use OCA\Mail\Service\Attachment\AttachmentService;
-use OCP\AppFramework\Db\DoesNotExistException;
-use Psr\Log\LoggerInterface;
-
-class TransmissionService {
-
- public function __construct(private GroupsIntegration $groupsIntegration,
- private AttachmentService $attachmentService,
- private LoggerInterface $logger,
- private SmimeService $smimeService,
- ) {
- }
-
- /**
- * @param LocalMessage $message
- * @param int $type
- * @return AddressList
- */
- public function getAddressList(LocalMessage $message, int $type): AddressList {
- return new AddressList(
- array_map(
- static function ($recipient) use ($type) {
- return Address::fromRaw($recipient->getLabel() ?? $recipient->getEmail(), $recipient->getEmail());
- },
- $this->groupsIntegration->expand(
- array_filter($message->getRecipients(), static function (Recipient $recipient) use ($type) {
- return $recipient->getType() === $type;
- })
- )
- )
- );
- }
-
- /**
- * @param LocalMessage $message
- * @return array|array[]
- */
- public function getAttachments(LocalMessage $message): array {
- if(empty($message->getAttachments())) {
- return [];
- }
- return array_map(static function (LocalAttachment $attachment) {
- // Convert to the untyped nested array used in \OCA\Mail\Controller\AccountsController::send
- return [
- 'type' => 'local',
- 'id' => $attachment->getId(),
- ];
- }, $message->getAttachments());
- }
-
- /**
- * @param Account $account
- * @param array $attachment
- * @return \Horde_Mime_Part|null
- */
- public function handleAttachment(Account $account, array $attachment): ?\Horde_Mime_Part {
- if (!isset($attachment['id'])) {
- $this->logger->warning('ignoring local attachment because its id is unknown');
- return null;
- }
-
- try {
- [$localAttachment, $file] = $this->attachmentService->getAttachment($account->getMailAccount()->getUserId(), (int)$attachment['id']);
- $part = new \Horde_Mime_Part();
- $part->setCharset('us-ascii');
- $part->setDisposition('attachment');
- $part->setName($localAttachment->getFileName());
- $part->setContents($file->getContent());
- $part->setType($localAttachment->getMimeType());
- return $part;
- } catch (AttachmentNotFoundException $e) {
- $this->logger->warning('Ignoring local attachment because it does not exist', ['exception' => $e]);
- return null;
- }
- }
-
- /**
- * @param LocalMessage $localMessage
- * @param Account $account
- * @param \Horde_Mime_Part $mimePart
- * @return \Horde_Mime_Part
- * @throws ServiceException
- */
- public function getSignMimePart(LocalMessage $localMessage, Account $account, \Horde_Mime_Part $mimePart): \Horde_Mime_Part {
- if ($localMessage->getSmimeSign()) {
- if ($localMessage->getSmimeCertificateId() === null) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_SIGN_NO_CERT_ID);
- throw new ServiceException('Could not send message: Requested S/MIME signature without certificate id');
- }
-
- try {
- $certificate = $this->smimeService->findCertificate(
- $localMessage->getSmimeCertificateId(),
- $account->getUserId(),
- );
- $mimePart = $this->smimeService->signMimePart($mimePart, $certificate);
- } catch (DoesNotExistException $e) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_SIGN_CERT);
- throw new ServiceException(
- 'Could not send message: Certificate does not exist: ' . $e->getMessage(),
- $e->getCode(),
- $e,
- );
- } catch (SmimeSignException|ServiceException $e) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_SIGN_FAIL);
- throw new ServiceException(
- 'Could not send message: Failed to sign MIME part: ' . $e->getMessage(),
- $e->getCode(),
- $e,
- );
- }
- }
- return $mimePart;
- }
-
- /**
- * @param LocalMessage $localMessage
- * @param AddressList $to
- * @param AddressList $cc
- * @param AddressList $bcc
- * @param Account $account
- * @param \Horde_Mime_Part $mimePart
- * @return \Horde_Mime_Part
- * @throws ServiceException
- */
- public function getEncryptMimePart(LocalMessage $localMessage, AddressList $to, AddressList $cc, AddressList $bcc, Account $account, \Horde_Mime_Part $mimePart): \Horde_Mime_Part {
- if ($localMessage->getSmimeEncrypt()) {
- if ($localMessage->getSmimeCertificateId() === null) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_ENCRYPT_NO_CERT_ID);
- throw new ServiceException('Could not send message: Requested S/MIME signature without certificate id');
- }
-
- try {
- $addressList = $to
- ->merge($cc)
- ->merge($bcc);
- $certificates = $this->smimeService->findCertificatesByAddressList($addressList, $account->getUserId());
-
- $senderCertificate = $this->smimeService->findCertificate($localMessage->getSmimeCertificateId(), $account->getUserId());
- $certificates[] = $senderCertificate;
-
- $mimePart = $this->smimeService->encryptMimePart($mimePart, $certificates);
- } catch (DoesNotExistException $e) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_ENCRYPT_CERT);
- throw new ServiceException(
- 'Could not send message: Certificate does not exist: ' . $e->getMessage(),
- $e->getCode(),
- $e,
- );
- } catch (SmimeEncryptException|ServiceException $e) {
- $localMessage->setStatus(LocalMessage::STATUS_SMIME_ENCRYT_FAIL);
- throw new ServiceException(
- 'Could not send message: Failed to encrypt MIME part: ' . $e->getMessage(),
- $e->getCode(),
- $e,
- );
- }
- }
- return $mimePart;
- }
-
-}
diff --git a/package-lock.json b/package-lock.json
index cc0580b35b..efa2734e9c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "nextcloud-mail",
- "version": "3.6.0-rc2",
+ "version": "3.6.0-rc3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nextcloud-mail",
- "version": "3.6.0-rc2",
+ "version": "3.6.0-rc3",
"license": "agpl",
"dependencies": {
"@ckeditor/ckeditor5-alignment": "37.1.0",
diff --git a/package.json b/package.json
index 4c1ac626ed..a81bed5fa5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "nextcloud-mail",
"description": "Nextcloud Mail",
- "version": "3.6.0-rc2",
+ "version": "3.6.0-rc3",
"author": "Christoph Wurst ",
"license": "agpl",
"private": true,
diff --git a/src/components/NewMessageModal.vue b/src/components/NewMessageModal.vue
index 50dde5e7e9..c304e9a295 100644
--- a/src/components/NewMessageModal.vue
+++ b/src/components/NewMessageModal.vue
@@ -396,9 +396,7 @@ export default {
if (!data.sendAt || data.sendAt < Math.floor((now + UNDO_DELAY) / 1000)) {
// Awaiting here would keep the modal open for a long time and thus block the user
- this.$store.dispatch('outbox/sendMessageWithUndo', { id: dataForServer.id }).catch((error) => {
- logger.debug('Could not send message', { error })
- })
+ this.$store.dispatch('outbox/sendMessageWithUndo', { id: dataForServer.id })
}
if (dataForServer.id) {
// Remove old draft envelope
diff --git a/src/components/OutboxMessageListItem.vue b/src/components/OutboxMessageListItem.vue
index 839c8f6607..e614b69591 100644
--- a/src/components/OutboxMessageListItem.vue
+++ b/src/components/OutboxMessageListItem.vue
@@ -21,8 +21,7 @@
-->
-
-
-
-
-
-
- {{ subjectForSubtitle }}
-
-
-
- {{ t('mail', 'Copy to "Sent" Mailbox') }}
-
-
-
-
-
-
-
-
- {{ t('mail', 'Delete') }}
-
-
-