Properly set skip_deleted and threading on Kolab storage IMAP operations (T1145)

Summary:
Fixes race-conditions between Kolab folders and Roundcube core
where skip_deleted/threading could be set for operations outside
of Kolab plugins, causing e.g. inability to see \Deleted messages.

Fixes T1145.

Reviewers: #roundcube_kolab_plugins_developers, vanmeeuwen

Reviewed By: #roundcube_kolab_plugins_developers, vanmeeuwen

Maniphest Tasks: T1145

Differential Revision: https://git.kolab.org/D112
This commit is contained in:
Aleksander Machniak 2016-04-14 11:42:00 +02:00 committed by Jeroen van Meeuwen (Kolab Systems)
parent ba78c23364
commit 9d883ed07d
3 changed files with 51 additions and 28 deletions

View file

@ -86,11 +86,7 @@ class kolab_storage
(self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
if (self::$ready) {
// set imap options
self::$imap->set_options(array(
'skip_deleted' => true,
'threading' => false,
));
// do nothing
}
else if (!class_exists('kolabformat')) {
rcube::raise_error(array(

View file

@ -186,7 +186,9 @@ class kolab_storage_cache
if (!$this->ready) {
// kolab cache is disabled, synchronize IMAP mailbox cache only
$this->imap_mode(true);
$this->imap->folder_sync($this->folder->name);
$this->imap_mode(false);
}
else {
// read cached folder metadata
@ -204,7 +206,7 @@ class kolab_storage_cache
$this->_sync_lock();
// disable messages cache if configured to do so
$this->bypass(true);
$this->imap_mode(true);
// synchronize IMAP mailbox cache
$this->imap->folder_sync($this->folder->name);
@ -212,6 +214,8 @@ class kolab_storage_cache
// compare IMAP index with object cache index
$imap_index = $this->imap->index($this->folder->name, null, null, true, true);
$this->imap_mode(false);
// determine objects to fetch or to invalidate
if (!$imap_index->is_error()) {
$imap_index = $imap_index->get();
@ -261,8 +265,6 @@ class kolab_storage_cache
}
}
$this->bypass(false);
// remove lock
$this->_sync_unlock();
}
@ -603,6 +605,8 @@ class kolab_storage_cache
else {
$filter = $this->_query2assoc($query);
$this->imap_mode(true);
if ($filter['type']) {
$search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_format::KTYPE_PREFIX . $filter['type'];
$index = $this->imap->search_once($this->folder->name, $search);
@ -611,6 +615,8 @@ class kolab_storage_cache
$index = $this->imap->index($this->folder->name, null, null, true, true);
}
$this->imap_mode(false);
if ($index->is_error()) {
$this->check_error();
if ($uids) {
@ -670,6 +676,8 @@ class kolab_storage_cache
else {
$filter = $this->_query2assoc($query);
$this->imap_mode(true);
if ($filter['type']) {
$search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_format::KTYPE_PREFIX . $filter['type'];
$index = $this->imap->search_once($this->folder->name, $search);
@ -678,6 +686,8 @@ class kolab_storage_cache
$index = $this->imap->index($this->folder->name, null, null, true, true);
}
$this->imap_mode(false);
if ($index->is_error()) {
$this->check_error();
return null;
@ -1136,13 +1146,31 @@ class kolab_storage_cache
}
/**
* Bypass Roundcube messages cache.
* Roundcube cache duplicates information already stored in kolab_cache.
* Set Roundcube storage options and bypass messages cache.
*
* @param bool $disable True disables, False enables messages cache
* We use skip_deleted and threading settings specific to Kolab,
* we have to change these global settings only temporarily.
* Roundcube cache duplicates information already stored in kolab_cache,
* that's why we can disable it for better performance.
*
* @param bool $force True to start Kolab mode, False to stop it.
*/
public function bypass($disable = false)
public function imap_mode($force = false)
{
// remember current IMAP settings
if ($force) {
$this->imap_options = array(
'skip_deleted' => $this->imap->get_option('skip_deleted'),
'threading' => $this->imap->get_threading(),
);
}
// re-set IMAP settings
$this->imap->set_threading($force ? false : $this->imap_options['threading']);
$this->imap->set_options(array(
'skip_deleted' => $force ? true : $this->imap_options['skip_deleted'],
));
// if kolab cache is disabled do nothing
if (!$this->enabled) {
return;
@ -1158,7 +1186,7 @@ class kolab_storage_cache
if ($messages_cache) {
// handle recurrent (multilevel) bypass() calls
if ($disable) {
if ($force) {
$this->cache_bypassed += 1;
if ($this->cache_bypassed > 1) {
return;
@ -1174,7 +1202,7 @@ class kolab_storage_cache
switch ($cache_bypass) {
case 2:
// Disable messages cache completely
$this->imap->set_messages_caching(!$disable);
$this->imap->set_messages_caching(!$force);
break;
case 1:
@ -1182,7 +1210,7 @@ class kolab_storage_cache
// Default mode is both (MODE_INDEX | MODE_MESSAGE)
$mode = rcube_imap_cache::MODE_INDEX;
if (!$disable) {
if (!$force) {
$mode |= rcube_imap_cache::MODE_MESSAGE;
}

View file

@ -49,7 +49,6 @@ class kolab_storage_folder extends kolab_storage_folder_api
function __construct($name, $type = null, $type_annotation = null)
{
parent::__construct($name);
$this->imap->set_options(array('skip_deleted' => true));
$this->set_folder($name, $type, $type_annotation);
}
@ -448,9 +447,9 @@ class kolab_storage_folder extends kolab_storage_folder_api
$this->imap->set_folder($folder);
$this->cache->bypass(true);
$this->cache->imap_mode(true);
$message = new rcube_message($msguid);
$this->cache->bypass(false);
$this->cache->imap_mode(false);
// Message doesn't exist?
if (empty($message->headers)) {
@ -698,9 +697,9 @@ class kolab_storage_folder extends kolab_storage_folder_api
if ($old_uid) {
// delete old message
$this->cache->bypass(true);
$this->cache->imap_mode(true);
$this->imap->delete_message($old_uid, $object['_mailbox']);
$this->cache->bypass(false);
$this->cache->imap_mode(false);
}
// insert/update message in cache
@ -796,7 +795,7 @@ class kolab_storage_folder extends kolab_storage_folder_api
$msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
$success = false;
$this->cache->bypass(true);
$this->cache->imap_mode(true);
if ($msguid && $expunge) {
$success = $this->imap->delete_message($msguid, $this->name);
@ -805,7 +804,7 @@ class kolab_storage_folder extends kolab_storage_folder_api
$success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
}
$this->cache->bypass(false);
$this->cache->imap_mode(false);
if ($success) {
$this->cache->set($msguid, false);
@ -824,9 +823,9 @@ class kolab_storage_folder extends kolab_storage_folder_api
}
$this->cache->purge();
$this->cache->bypass(true);
$this->cache->imap_mode(true);
$result = $this->imap->clear_folder($this->name);
$this->cache->bypass(false);
$this->cache->imap_mode(false);
return $result;
}
@ -844,9 +843,9 @@ class kolab_storage_folder extends kolab_storage_folder_api
}
if ($msguid = $this->cache->uid2msguid($uid, true)) {
$this->cache->bypass(true);
$this->cache->imap_mode(true);
$result = $this->imap->set_flag($msguid, 'UNDELETED', $this->name);
$this->cache->bypass(false);
$this->cache->imap_mode(false);
if ($result) {
return $msguid;
@ -873,9 +872,9 @@ class kolab_storage_folder extends kolab_storage_folder_api
$target_folder = kolab_storage::get_folder($target_folder);
if ($msguid = $this->cache->uid2msguid($uid)) {
$this->cache->bypass(true);
$this->cache->imap_mode(true);
$result = $this->imap->move_message($msguid, $target_folder->name, $this->name);
$this->cache->bypass(false);
$this->cache->imap_mode(false);
if ($result) {
$new_uid = ($copyuid = $this->imap->conn->data['COPYUID']) ? $copyuid[1] : null;