diff --git a/appinfo/info.xml b/appinfo/info.xml
index ec82f5f1a5..4a50ddb465 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.5.0-beta.3
+ 3.5.0-beta.4
agpl
Christoph Wurst
Nextcloud Groupware Team
diff --git a/lib/Db/Mailbox.php b/lib/Db/Mailbox.php
index bf3c24cf13..37862c6118 100644
--- a/lib/Db/Mailbox.php
+++ b/lib/Db/Mailbox.php
@@ -70,6 +70,8 @@
* @method void setMyAcls(string|null $acls)
* @method bool|null isShared()
* @method void setShared(bool $shared)
+ * @method string getNameHash()
+ * @method void setNameHash(string $nameHash)
*/
class Mailbox extends Entity implements JsonSerializable {
protected $name;
@@ -89,6 +91,7 @@ class Mailbox extends Entity implements JsonSerializable {
protected $syncInBackground;
protected $myAcls;
protected $shared;
+ protected $nameHash;
/**
* @var int
diff --git a/lib/Db/MailboxMapper.php b/lib/Db/MailboxMapper.php
index f7464beaf0..820d2b4d42 100644
--- a/lib/Db/MailboxMapper.php
+++ b/lib/Db/MailboxMapper.php
@@ -92,7 +92,7 @@ public function find(Account $account, string $name): Mailbox {
->from($this->getTableName())
->where(
$qb->expr()->eq('account_id', $qb->createNamedParameter($account->getId())),
- $qb->expr()->eq('name', $qb->createNamedParameter($name))
+ $qb->expr()->eq('name_hash', $qb->createNamedParameter(md5($name)))
);
try {
diff --git a/lib/IMAP/MailboxSync.php b/lib/IMAP/MailboxSync.php
index 3ff6375e70..9e41946fda 100644
--- a/lib/IMAP/MailboxSync.php
+++ b/lib/IMAP/MailboxSync.php
@@ -254,6 +254,7 @@ private function createMailboxFromFolder(Account $account, Folder $folder, ?Hord
$mailbox->setSpecialUse(json_encode($folder->getSpecialUse()));
$mailbox->setMyAcls($folder->getMyAcls());
$mailbox->setShared($this->isMailboxShared($namespaces, $mailbox));
+ $mailbox->setNameHash(md5($folder->getMailbox()));
return $this->mailboxMapper->insert($mailbox);
}
diff --git a/lib/Migration/Version3500Date20231115182612.php b/lib/Migration/Version3500Date20231115182612.php
new file mode 100644
index 0000000000..238a4b5563
--- /dev/null
+++ b/lib/Migration/Version3500Date20231115182612.php
@@ -0,0 +1,115 @@
+
+ *
+ * @author Daniel Kesselberg
+ *
+ * @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\DB\Types;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version3500Date20231115182612 extends SimpleMigrationStep {
+
+ public function __construct(
+ private IDBConnection $connection
+ ) {
+ }
+
+ /**
+ * @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();
+
+ $mailboxesTable = $schema->getTable('mail_mailboxes');
+ if (!$mailboxesTable->hasColumn('name_hash')) {
+ $mailboxesTable->addColumn('name_hash', Types::STRING);
+ }
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ // Round 1: Hash common mailbox names
+
+ $this->connection->beginTransaction();
+
+ foreach (['INBOX', 'Drafts', 'Sent', 'Trash', 'Junk', 'Spam', 'Archive', 'Archives'] as $name) {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->update('mail_mailboxes')
+ ->set('name_hash', $qb->createNamedParameter(md5($name)))
+ ->where($qb->expr()->like('name', $qb->createNamedParameter($name), Types::STRING))
+ ->executeStatement();
+ }
+
+ $this->connection->commit();
+
+ // Round 2: Hash everything else
+
+ $qb = $this->connection->getQueryBuilder();
+ $qb->select(['id', 'name'])
+ ->from('mail_mailboxes')
+ ->where($qb->expr()->emptyString('name_hash'));
+ $mailboxes = $qb->executeQuery();
+
+ $updateQb = $this->connection->getQueryBuilder();
+ $updateQb->update('mail_mailboxes')
+ ->set('name_hash', $updateQb->createParameter('name_hash'))
+ ->where($updateQb->expr()->eq('id', $updateQb->createParameter('id')));
+
+ $this->connection->beginTransaction();
+
+ $queryCount = 0;
+ while (($row = $mailboxes->fetch()) !== false) {
+ $queryCount++;
+
+ $updateQb->setParameter('id', $row['id']);
+ $updateQb->setParameter('name_hash', md5($row['name']));
+ $updateQb->executeStatement();
+
+ if ($queryCount === 50000) {
+ $this->connection->commit();
+ $this->connection->beginTransaction();
+ $queryCount = 0;
+ }
+ }
+
+ $mailboxes->closeCursor();
+
+ $this->connection->commit();
+ }
+}
diff --git a/lib/Migration/Version3500Date20231115184458.php b/lib/Migration/Version3500Date20231115184458.php
new file mode 100644
index 0000000000..cb5c82187e
--- /dev/null
+++ b/lib/Migration/Version3500Date20231115184458.php
@@ -0,0 +1,61 @@
+
+ *
+ * @author Daniel Kesselberg
+ *
+ * @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 Version3500Date20231115184458 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();
+
+ $mailboxesTable = $schema->getTable('mail_mailboxes');
+
+ $indexOld = 'UNIQ_22DEBD839B6B5FBA5E237E06';
+ $indexNew = 'mail_mb_account_id_name_hash';
+
+ if ($mailboxesTable->hasIndex($indexOld)) {
+ $mailboxesTable->dropIndex($indexOld);
+ }
+
+ if (!$mailboxesTable->hasIndex($indexNew)) {
+ $mailboxesTable->addUniqueIndex(['account_id', 'name_hash'], $indexNew);
+ }
+
+ return $schema;
+ }
+}
diff --git a/tests/Integration/Db/MailboxMapperTest.php b/tests/Integration/Db/MailboxMapperTest.php
index d78a2517dc..b5f45bf71b 100644
--- a/tests/Integration/Db/MailboxMapperTest.php
+++ b/tests/Integration/Db/MailboxMapperTest.php
@@ -88,6 +88,7 @@ public function testFindAll() {
'messages' => $qb->createNamedParameter($i * 100, IQueryBuilder::PARAM_INT),
'unseen' => $qb->createNamedParameter($i, IQueryBuilder::PARAM_INT),
'selectable' => $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL),
+ 'name_hash' => $qb->createNamedParameter(md5("folder$i")),
]);
$insert->executeStatement();
}
@@ -122,6 +123,7 @@ public function testFindInbox() {
'messages' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
'unseen' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
'selectable' => $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL),
+ 'name_hash' => $qb->createNamedParameter(md5('INBOX')),
]);
$insert->executeStatement();
@@ -129,4 +131,48 @@ public function testFindInbox() {
$this->assertSame('INBOX', $result->getName());
}
+
+ public function testMailboxesWithTrailingSpace() {
+ /** @var Account|MockObject $account */
+ $account = $this->createMock(Account::class);
+ $account->method('getId')->willReturn(13);
+
+ $qb = $this->db->getQueryBuilder();
+ $insert = $qb->insert($this->mapper->getTableName())
+ ->values([
+ 'name' => $qb->createNamedParameter('Test'),
+ 'account_id' => $qb->createNamedParameter(13, IQueryBuilder::PARAM_INT),
+ 'sync_new_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'sync_changed_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'sync_vanished_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'delimiter' => $qb->createNamedParameter('.'),
+ 'messages' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
+ 'unseen' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
+ 'selectable' => $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL),
+ 'name_hash' => $qb->createNamedParameter(md5('Test')),
+ ]);
+ $insert->executeStatement();
+
+ $qb = $this->db->getQueryBuilder();
+ $insert = $qb->insert($this->mapper->getTableName())
+ ->values([
+ 'name' => $qb->createNamedParameter('Test '),
+ 'account_id' => $qb->createNamedParameter(13, IQueryBuilder::PARAM_INT),
+ 'sync_new_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'sync_changed_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'sync_vanished_token' => $qb->createNamedParameter('VTEsVjE0Mjg1OTkxNDk='),
+ 'delimiter' => $qb->createNamedParameter('.'),
+ 'messages' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
+ 'unseen' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
+ 'selectable' => $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL),
+ 'name_hash' => $qb->createNamedParameter(md5('Test ')),
+ ]);
+ $insert->executeStatement();
+
+ $resultA = $this->mapper->find($account, 'Test');
+ $this->assertSame('Test', $resultA->getName());
+
+ $resultB = $this->mapper->find($account, 'Test ');
+ $this->assertSame('Test ', $resultB->getName());
+ }
}