Projects
Kolab:16
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 75
View file
kolab-syncroton.spec
Changed
@@ -43,7 +43,7 @@ %global upstream_version 2.4.2 Name: kolab-syncroton -Version: 2.4.2.39 +Version: 2.4.2.43 Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware
View file
debian.changelog
Changed
@@ -1,4 +1,4 @@ -kolab-syncroton (2.4.2.39-0~kolab1) unstable; urgency=low +kolab-syncroton (2.4.2.43-0~kolab1) unstable; urgency=low * Release version 2.4.2
View file
kolab-syncroton-2.4.2.tar.gz/bin/analyzelogs.php
Changed
@@ -45,7 +45,7 @@ $content = file_get_contents($filename); // Split up the log files into chunks that hopefully match the commands -$parts = preg_split("/\.*\: " . preg_quote("DEBUG Syncroton_Server::handle::65 REQUEST METHOD: POST", '/') . "/", $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); +$parts = preg_split("/\.*\: " . preg_quote("DEBUG", '/') . " Syncroton_Server::handle::\d+ REQUEST METHOD: POST/", $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); function getStatusConstants($classname) {
View file
kolab-syncroton-2.4.2.tar.gz/bin/delete-device.php
Changed
@@ -74,11 +74,15 @@ } $owner = $opts'owner'; +$host = $rcube->config->get('imap_host', $rcube->config->get('default_host')); +$host = parse_url($host, PHP_URL_HOST) ?? $host; $select = $db->query( "SELECT `user_id` FROM `users`" . " WHERE `username` = ?" + . " AND `mail_host` = ?" . " ORDER BY `user_id` DESC", - \strtolower($owner) + \strtolower($owner), + \strtolower($host) ); if ($data = $db->fetch_assoc($select)) {
View file
kolab-syncroton-2.4.2.tar.gz/bin/inspect.php
Changed
@@ -78,6 +78,7 @@ 'e' => 'email', 'p' => 'adminpassword', 'd' => 'debug', + 'k' => 'dump', ); if (empty($opts'email')) { @@ -99,14 +100,35 @@ $rcube = rcube::get_instance(); $default_port = $rcube->config->get('default_port', 143); -$default_host = $rcube->config->get('default_host'); + +$host = $rcube->config->get('imap_host', $rcube->config->get('default_host')); + +// parse $host +$a_host = parse_url($host); +$port = null; +$ssl = null; +if (!empty($a_host'host')) { + $host = $a_host'host'; + $ssl = (isset($a_host'scheme') && in_array($a_host'scheme', 'ssl','imaps','tls')) ? $a_host'scheme' : null; + if (!empty($a_host'port')) { + $port = $a_host'port'; + } elseif ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) { + $port = 993; + } +} + +if (!$port) { + $port = $default_port; +} + $imap = new \rcube_imap_generic(); if ($proxyAuth) { $options'auth_cid' = $user; $options'auth_pw' = $password; } $options'auth_type' = 'PLAIN'; -$options'port' = $default_port; +$options'port' = $port; + $options'socket_options' = 'ssl' => 'verify_peer_name' => false, @@ -121,7 +143,7 @@ $debug = !empty($opts'debug'); $imap->setDebug($debug); -if (!$imap->connect($default_host, $email, $password, $options)) { +if (!$imap->connect($host, $email, $password, $options)) { rcube::raise_error("Failed to connect to imap.", false, true); } @@ -131,8 +153,10 @@ $select = $db->query( "SELECT `user_id` FROM `users`" . " WHERE `username` = ?" + . " AND `mail_host` = ?" . " ORDER BY `user_id` DESC", - \strtolower($email) + \strtolower($email), + \strtolower($host) ); if ($data = $db->fetch_assoc($select)) { @@ -260,6 +284,23 @@ return "Unknown value: $value"; } + +function getContentIds($db, $device_id, $folder_id) +{ + $contentSelect = $db->query( + "SELECT contentid FROM `syncroton_content`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0", + $device_id, + $folder_id + ); + + $contentUids = ; + while ($content = $db->fetch_assoc($contentSelect)) { + $contentUids = $content'contentid'; + } + return $contentUids; +} + function getContentUids($db, $device_id, $folder_id) { $contentSelect = $db->query( @@ -311,6 +352,41 @@ println(" Number of syncs: " . ($folder'counter' ?? "None")); println(" Filter type: " . filterType($folder'lastfiltertype' ?? null)); + + if (!empty($opts'dump') && $opts'dump' == $folder'name') { + $contentUids = getContentUids($db, $deviceId, $folderId); + $imapUids = getImapUids($imap, $folder'name', $folder'lastfiltertype' ?? null); + + $entries = array_diff($imapUids, $contentUids); + if (!empty($entries)) { + println(" The following messages are on the server, but not the device:"); + foreach ($entries as $uid) { + println(" $uid"); + //TODO get details from imap? + } + } + + $entries = array_diff($contentUids, $imapUids); + if (!empty($entries)) { + println(" The following messages are on the device, but not the server:"); + foreach ($entries as $uid) { + println(" $uid"); + //TODO get details from the content part? + //TODO display creation_synckey? + } + } + + $contentUids = getContentUids($db, $deviceId, $folderId); + foreach (array_slice($contentUids, -10, 10) as $uid) { + println($uid); + } + + $contentIds = getContentIds($db, $deviceId, $folderId); + foreach (array_slice($contentIds, -10, 10) as $uid) { + println($uid); + } + } + if (($folder'class' == "Email") && ($folder'counter' ?? false) && $messageCount != $totalCount && ($modseq == "none" || $modseq == $imapModseq)) { if (($folder'lastfiltertype' ?? false) && $messageCount > $totalCount) { // This doesn't have to indicate an issue, since the timewindow of the filter wanders, so some messages that have been synchronized may no longer match the window.
View file
kolab-syncroton-2.4.2.tar.gz/config/config.inc.php.dist
Changed
@@ -10,11 +10,9 @@ $config'activesync_log_file' = null; // Type of ActiveSync cache. Supported values: 'db', 'apc' and 'memcache'. -// Note: This is only for some additional data like timezones mapping. $config'activesync_cache' = 'db'; -// lifetime of ActiveSync cache -// possible units: s, m, h, d, w +// Lifetime of the ActiveSync cache. Supported units: s, m, h, d, w $config'activesync_cache_ttl' = '1d'; // Type of ActiveSync Auth cache. Supported values: 'db', 'apc' and 'memcache'.
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql.initial.sql
Changed
@@ -48,6 +48,7 @@ `lastfiltertype` int(11) DEFAULT NULL, `supportedfields` longblob DEFAULT NULL, `resync` tinyint(1) DEFAULT NULL, + `is_deleted` tinyint(1) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `device_id--class--folderid` (`device_id`(40),`class`(40),`folderid`(40)), KEY `folderstates::device_id--devices::id` (`device_id`), @@ -109,12 +110,22 @@ CONSTRAINT `syncroton_relations_state::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; +CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` ( + `device_id` varchar(40) NOT NULL, + `type` varchar(16) NOT NULL, + `data` longblob NOT NULL, + PRIMARY KEY (`device_id`, `type`), + KEY `syncroton_subscriptions::device_id` (`device_id`), + CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id` + FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + -- Roundcube core table should exist if we're using the same database CREATE TABLE IF NOT EXISTS `system` ( - `name` varchar(64) NOT NULL, - `value` mediumtext, - PRIMARY KEY(`name`) + `name` varchar(64) NOT NULL, + `value` mediumtext, + PRIMARY KEY (`name`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024102300'); +INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2025043000');
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2025042900.sql
Added
@@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` ( + `device_id` varchar(40) NOT NULL, + `type` varchar(16) NOT NULL, + `data` longblob NOT NULL, + PRIMARY KEY (`device_id`, `type`), + KEY `syncroton_subscriptions::device_id` (`device_id`), + CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id` + FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2025043000.sql
Added
@@ -0,0 +1,1 @@ +ALTER TABLE `syncroton_folder` ADD `is_deleted` tinyint(1) NOT NULL DEFAULT '0';
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Ping.php
Changed
@@ -37,6 +37,24 @@ protected $_changesDetected = false; protected $_foldersWithChanges = ; + private function goToSleep($sleepInterval) + { + // take a break to save battery lifetime + call_user_func(Syncroton_Registry::getSleepCallback()); + sleep($sleepInterval); + + // make sure the connection is still alive, abort otherwise + if (connection_aborted()) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Exiting on aborted connection"); + } + exit; + } + + // reconnect external connections, etc. + call_user_func(Syncroton_Registry::getWakeupCallback()); + } + /** * process the XML file and add, change, delete or fetches data * @@ -48,6 +66,7 @@ $intervalStart = time(); $status = self::STATUS_NO_CHANGES_FOUND; + $pingTimeout = Syncroton_Registry::getPingTimeout(); // the client does not send a wbxml document, if the Ping parameters did not change compared with the last request if ($this->_requestBody instanceof DOMDocument) { $xml = simplexml_import_dom($this->_requestBody); @@ -55,6 +74,11 @@ if (isset($xml->HeartbeatInterval)) { $this->_device->pinglifetime = (int)$xml->HeartbeatInterval; + // Magic value for testing + if ($this->_device->pinglifetime == 9999) { + $pingTimeout = 1; + $this->_device->pinglifetime = 900; + } } if (isset($xml->Folders->Folder)) { @@ -110,7 +134,6 @@ } $intervalEnd = $intervalStart + $lifeTime; - $secondsLeft = $intervalEnd; $folders = $this->_device->pingfolder ? unserialize($this->_device->pingfolder) : ; @@ -123,29 +146,7 @@ } if ($status === self::STATUS_NO_CHANGES_FOUND) { - $sleepCallback = Syncroton_Registry::getSleepCallback(); - $wakeupCallback = Syncroton_Registry::getWakeupCallback(); - do { - // take a break to save battery lifetime - call_user_func($sleepCallback); - sleep(min(Syncroton_Registry::getPingTimeout(), $lifeTime)); - - // make sure the connection is still alive, abort otherwise - if (connection_aborted()) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Exiting on aborted connection"); - } - exit; - } - - // reconnect external connections, etc. - call_user_func($wakeupCallback); - - // Calculate secondsLeft before any loop break just to have a correct value - // for logging purposes in case we breaked from the loop early - $secondsLeft = $intervalEnd - time(); - try { /** @var Syncroton_Model_Device $device */ $device = $this->_deviceBackend->get($this->_device->id); @@ -259,7 +260,6 @@ break; } - // Update secondsLeft (again) $secondsLeft = $intervalEnd - time(); if ($this->_logger instanceof Zend_Log) { @@ -271,9 +271,16 @@ // break if there are less than PingTimeout + 10 seconds left for the next loop // otherwise the response will be returned after the client has finished his Ping // request already maybe - } while (Syncroton_Server::validateSession() && $secondsLeft > (Syncroton_Registry::getPingTimeout() + 10)); + if ($secondsLeft < ($pingTimeout + 10)) { + break; + } else { + $this->goToSleep($pingTimeout); + } + + } while (Syncroton_Server::validateSession()); } + $secondsLeft = $intervalEnd - time(); if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: $lifeTime SecondsLeft: $secondsLeft Status: $status)"); }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/SendMail.php
Changed
@@ -23,6 +23,7 @@ protected $_defaultNameSpace = 'uri:ComposeMail'; protected $_documentElement = 'SendMail'; + protected $_clientId; protected $_mime; protected $_saveInSent; protected $_source; @@ -48,6 +49,7 @@ } elseif ($this->_requestBody) { $xml = simplexml_import_dom($this->_requestBody); + $this->_clientId = (string) $xml->ClientId; $this->_mime = (string) $xml->Mime; $this->_saveInSent = isset($xml->SaveInSentItems); $this->_replaceMime = isset($xml->ReplaceMime); @@ -125,6 +127,6 @@ */ protected function sendMail($dataController) { - $dataController->sendEmail($this->_mime, $this->_saveInSent); + $dataController->sendEmail($this->_mime, $this->_saveInSent, $this->_clientId); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/SmartForward.php
Changed
@@ -26,6 +26,12 @@ */ protected function sendMail($dataController) { - $dataController->forwardEmail($this->_source, $this->_mime, $this->_saveInSent, $this->_replaceMime); + $dataController->forwardEmail( + $this->_source, + $this->_mime, + $this->_saveInSent, + $this->_replaceMime, + $this->_clientId + ); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/SmartReply.php
Changed
@@ -26,6 +26,6 @@ */ protected function sendMail($dataController) { - $dataController->replyEmail($this->_source, $this->_mime, $this->_saveInSent, $this->_replaceMime); + $dataController->replyEmail($this->_source, $this->_mime, $this->_saveInSent, $this->_replaceMime, $this->_clientId); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Data/Email.php
Changed
@@ -31,7 +31,7 @@ * (non-PHPdoc) * @see Syncroton_Data_IDataEmail::forwardEmail() */ - public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime) + public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime, $clientId) { if ($inputStream == 'triggerException') { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAILBOX_SERVER_OFFLINE); @@ -59,7 +59,7 @@ * (non-PHPdoc) * @see Syncroton_Data_IDataEmail::replyEmail() */ - public function replyEmail($source, $inputStream, $saveInSent, $replaceMime) + public function replyEmail($source, $inputStream, $saveInSent, $replaceMime, $clientId) { // forward email } @@ -78,7 +78,7 @@ * (non-PHPdoc) * @see Syncroton_Data_IDataEmail::sendEmail() */ - public function sendEmail($inputStream, $saveInSent) + public function sendEmail($inputStream, $saveInSent, $clientId) { if ($inputStream == 'triggerException') { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAILBOX_SERVER_OFFLINE);
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Data/IDataEmail.php
Changed
@@ -21,26 +21,31 @@ /** * send an email * - * @param resource $inputStream - * @param boolean $saveInSent + * @param resource $inputStream + * @param bool $saveInSent + * @param ?string $clientId */ - public function sendEmail($inputStream, $saveInSent); + public function sendEmail($inputStream, $saveInSent, $clientId); /** * forward an email * - * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId - * @param string $inputStream - * @param string $saveInSent + * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId + * @param string $inputStream + * @param string $saveInSent + * @param bool $replaceMime + * @param ?string $clientId */ - public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime); + public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime, $clientId); /** * reply to an email * - * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId - * @param string $inputStream - * @param string $saveInSent + * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId + * @param string $inputStream + * @param string $saveInSent + * @param bool $replaceMime + * @param ?string $clientId */ - public function replyEmail($source, $inputStream, $saveInSent, $replaceMime); + public function replyEmail($source, $inputStream, $saveInSent, $replaceMime, $clientId); }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/AXMLEntry.php
Changed
@@ -50,7 +50,7 @@ foreach ($this->_elements as $elementName => $value) { // skip empty values - if ($value === null || $value === '' || (is_array($value) && empty($value))) { + if ($value === null || (is_array($value) && empty($value))) { continue; }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/IContent.php
Changed
@@ -22,6 +22,8 @@ * @property DateTime $creation_time * @property string $creation_synckey * @property string $is_deleted + * + * @phpstan-require-extends Syncroton_Model_Content */ interface Syncroton_Model_IContent {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/IDevice.php
Changed
@@ -38,6 +38,8 @@ * @property string $emailfilter_id * @property string $lastsynccollection * @property DateTime $lastping + * + * @phpstan-require-extends Syncroton_Model_Device */ interface Syncroton_Model_IDevice extends Syncroton_Model_IEntry {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/IFolder.php
Changed
@@ -25,6 +25,8 @@ * @property int $creationSynckey * @property int $lastfiltertype * @property int $type + * + * @phpstan-require-extends Syncroton_Model_Folder */ interface Syncroton_Model_IFolder {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/ISyncState.php
Changed
@@ -23,6 +23,8 @@ * @property ?array $pendingdata * @property string $clientIdMap JSON-encoded array * @property string $extraData JSON-encoded array + * + * @phpstan-require-extends Syncroton_Model_SyncState */ interface Syncroton_Model_ISyncState {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Server.php
Changed
@@ -165,6 +165,11 @@ if (!$response) { $response = $command->getResponse(); } + + if ($response === null) { + // FIXME: Is this really needed? It is for PHP cli-server, but not really for a real http server + header('Content-Length: 0'); + } } catch (Syncroton_Exception_ProvisioningNeeded $sepn) { if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_device.php
Changed
@@ -32,23 +32,6 @@ protected $interface_name = 'Syncroton_Model_IDevice'; /** - * Kolab Sync storage backend - * - * @var kolab_sync_storage - */ - protected $backend; - - - /** - * Constructor - */ - public function __construct() - { - parent::__construct(); - $this->backend = kolab_sync::storage(); - } - - /** * Create (register) a new device * * @param Syncroton_Model_IDevice $device Device object @@ -59,33 +42,23 @@ { $device = parent::create($device); - // Create device entry in kolab backend - $created = $this->backend->device_create( - 'ID' => $device->id, - 'TYPE' => $device->devicetype, - 'ALIAS' => $device->friendlyname, - , $device->deviceid); - - if (!$created) { - throw new Syncroton_Exception_NotFound('Device creation failed'); + $sync = kolab_sync::get_instance(); + + // Some devices create dummy devices with name "validate" (#1109) + // This device entry is used in two initial requests, but later + // the device registers a real name. We can remove this dummy entry + // on new device creation + if ($device->deviceid != 'validate') { + $this->db->query( + 'DELETE FROM `' . $this->table_name . '` WHERE `deviceid` = ? AND `owner_id` = ?', + 'validate', $sync->user->ID + ); } - return $device; - } - - /** - * Delete a device - * - * @param string|Syncroton_Model_IDevice $device Device object - * - * @return bool True on success, False on failure - */ - public function delete($device) - { - // Update IMAP annotation - $this->backend->device_delete($device->deviceid); + // Auto-subscribe a default set of folders + $sync->storage()->device_init($device->deviceid); - return parent::delete($device); + return $device; } /** @@ -109,18 +82,7 @@ throw new Syncroton_Exception_NotFound('Device not found'); } - $device = $this->get_object($device); - - // Make sure device exists (could be deleted by the user) - $dev = $this->backend->device_get($deviceid); - if (empty($dev)) { - // Remove the device (and related cached data) from database - $this->delete($device); - - throw new Syncroton_Exception_NotFound('Device not found'); - } - - return $device; + return $this->get_object($device); } /**
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_folder.php
Changed
@@ -46,6 +46,21 @@ } /** + * mark folder as deleted. The state gets removed finally, + * when the synckey gets validated during next sync. + * + * @param Syncroton_Model_IFolder|string $id + */ + public function delete($id) + { + $id = $id instanceof Syncroton_Model_IFolder ? $id->id : $id; + + $result = $this->db->query("UPDATE `{$this->table_name}` SET `is_deleted` = 1 WHERE `id` = ?", $id); + + return $result; + } + + /** * Get array of ids which got send to the client for a given class * * @param Syncroton_Model_Device|string $deviceid Device object or identifier @@ -144,8 +159,12 @@ Syncroton_Data_Factory::CLASS_TASKS, ; - // Reset imap cache so we work with up-to-date folders list + // Reset imap cache, metadata cache and the folder list cache so we work with up-to-date folders lists rcube::get_instance()->get_storage()->clear_cache('mailboxes', true); + kolab_sync::storage()->reset(); + foreach ($folder_classes as $class) { + Syncroton_Data_Factory::factory($class, $device, $timestamp)->clearCache(); + } // Retrieve all folders already sent to the client $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE `device_id` = ?", $device->id);
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_state.php
Changed
@@ -146,6 +146,28 @@ } /** + * Run a query and retry on deadlock + */ + private function runAndRetry($query) + { + $retryCounter = 0; + while (true) { + $result = $this->db->query($query); + if ($this->db->is_error($result)) { + $retryCounter++; + // Retry on deadlock + if ($this->db->error_info()0 != '40001' || $retryCounter > 60) { + throw new Exception("Failed to run query ($retryCounter retries): " . $query); + } + } else { + break; + } + // Give the other transactions some time before we try again + sleep(1); + } + } + + /** * Validates specified sync state by checking for existance of newer keys * * @param Syncroton_Model_IDevice|string $deviceid Device object or identifier @@ -184,32 +206,24 @@ $state = $states$sync_key; $next = max(array_keys($states)); - $where = ; - $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); - $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; - // found more recent synckey => the last sync response was not received by the client if ($next > $sync_key) { // We store the clientIdMap with the "next" sync state, so we need to copy it back. $state->clientIdMap = $states$next->clientIdMap; $state->counterNext = $next; } else { - // finally delete all entries marked for removal in syncroton_content table - $retryCounter = 0; - while (true) { - $result = $this->db->query("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); - if ($this->db->is_error($result)) { - $retryCounter++; - // Retry on deadlock - if ($this->db->error_info()0 != '40001' || $retryCounter > 60) { - throw new Exception('Failed to delete entries in sync_key check'); - } - } else { - break; - } - //Give the other transactions some time before we try again - sleep(1); + // Cleanup deleted entries + if ($folderid == 'FolderSync') { + $where = ; + $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); + $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; + $this->runAndRetry("DELETE FROM `syncroton_folder` WHERE " . implode(' AND ', $where)); + } else { + $where = ; + $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); + $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); + $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; + $this->runAndRetry("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data.php
Changed
@@ -334,7 +334,13 @@ */ public function updateFolder(Syncroton_Model_IFolder $folder) { - $result = $this->backend->folder_rename($folder->serverId, $this->device->deviceid, $folder->displayName, $folder->parentId); + $result = $this->backend->folder_rename( + $folder->serverId, + $this->device->deviceid, + $this->modelName, + $folder->displayName, + $folder->parentId + ); if ($result) { return $folder; @@ -353,7 +359,7 @@ } // @TODO: throw exception - return $this->backend->folder_delete($folder, $this->device->deviceid); + return $this->backend->folder_delete($folder, $this->device->deviceid, $this->modelName); } /** @@ -371,7 +377,7 @@ // TODO: Respond with MailboxQuotaExceeded status. Where exactly? foreach ($this->extractFolders($folderid) as $folderid) { - if (!$this->backend->folder_empty($folderid, $this->device->deviceid, !empty($options'deleteSubFolders'))) { + if (!$this->backend->folder_empty($folderid, $this->device->deviceid, $this->modelName, !empty($options'deleteSubFolders'))) { throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR); } } @@ -587,10 +593,16 @@ return $this->searchEntries($folderId, $filter, self::RESULT_COUNT, $syncState->extraData); } - + /** + * Get additional metadata for a specified folder + * + * @param Syncroton_Model_IFolder $folder Folder object + * + * @return string|null JSON-encoded string + */ public function getExtraData(Syncroton_Model_IFolder $folder) { - return $this->backend->getExtraData($folder->serverId, $this->device->deviceid); + return $this->backend->getExtraData($folder->serverId, $this->device->deviceid, $this->modelName); } /** @@ -748,6 +760,14 @@ } /** + * Clear the internal folder list cache + */ + public function clearCache() + { + $this->folders = ; + } + + /** * List of all IMAP folders (or subtree) * * @param string $parentid Parent folder identifier
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_calendar.php
Changed
@@ -1160,7 +1160,7 @@ $is_organizer = in_array_nocase($event'organizer''email', $user_emails); } - if ($event'status' == 'CANCELLED') { + if (isset($event'status') && $event'status' == 'CANCELLED') { $status = $is_organizer ? 5 : 7; } else { $status = $is_organizer ? 1 : 3;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_contacts.php
Changed
@@ -192,19 +192,21 @@ $result$key = $value; } + $emails = $this->getKolabContactEmails($data); + // email address(es): email1Address, email2Address, email3Address for ($x = 0; $x < 3; $x++) { - if (!empty($data'email'$x)) { - $email = $data'email'$x; - if (is_array($email)) { - $email = $email'address'; - } - if ($email) { - $result'email' . ($x + 1) . 'Address' = $email; - } + if (!empty($emails$x)) { + $result'email' . ($x + 1) . 'Address' = $emails$x; } } + // Outlook's synchronization will break with an empty contact (no nodes under ApplicationData), + // so we make sure to always set a property (an empty name is enough as long as there is an XML node). + if (empty($result)) { + $result'firstName' = ''; + } + return new Syncroton_Model_Contact($result); } @@ -269,31 +271,7 @@ } // email address(es): email1Address, email2Address, email3Address - $emails = ; - for ($x = 0; $x < 3; $x++) { - $key = 'email' . ($x + 1) . 'Address'; - $value = $data->$key ?? null; - if ($value) { - // Android sends email address as: Lars Kneschke <l.kneschke@metaways.de> - if (preg_match('/(.*)<(.+@^@+)>/', $value, $matches)) { - $value = trim($matches2); - } - - // sanitize email address, it can contain broken (non-unicode) characters (#3287) - $value = rcube_charset::clean($value); - - // try to find address type, at least we can do this if - // address wasn't changed - $type = ''; - foreach ((array)$contact'email' as $email) { - if ($email'address' == $value) { - $type = $email'type'; - } - } - $emails = 'address' => $value, 'type' => $type; - } - } - $contact'email' = $emails; + $this->setKolabContactEmails($contact, $data); return $contact; } @@ -638,4 +616,99 @@ return ; } + + /** + * Extract list of email addresses from a Kolab contact + */ + protected function getKolabContactEmails($contact) + { + // Contacts from XML (Kolab3) contain 'email' item set with an array that contains address and type + // or is just an address. + // Contacts from DAV (Kolab4) contain 'email:<type>' items with an array of email addresses. + + $emails = ; + foreach ('email', 'email:work', 'email:other', 'email:home' as $key) { + foreach ($contact$key ?? as $item) { + if (is_string($item) && strpos($item, '@')) { + $emails = $item; + } elseif (is_array($item) && !empty($item)) { + if (isset($item'address')) { + $emails = $item'address'; + } else { + $emails = array_merge($emails, $item); + } + } + } + } + + // Remove duplicates and empty values + return array_values(array_filter(array_unique($emails))); + } + + /** + * Set Kolab contact email addresses + */ + protected function setKolabContactEmails(&$contact, $data) + { + // On Kolab3 (XML) contacts have 'email' item that is a list of arrays that contain address and type + // or is just an address. No types. + // On Kolab4 (DAV) contacts have 'email:home', 'email:other' and 'email:home' items with + // an array of email addresses each. + + // Get addresses from ActiveSync properties (email1Address, email2Address, email3Address) + $emails = ; + for ($x = 0; $x < 3; $x++) { + $key = 'email' . ($x + 1) . 'Address'; + $email = $data->$key ?? null; + if ($email) { + // sanitize email address, it can contain broken (non-unicode) characters (#3287) + $email = rcube_charset::clean($email); + + // Android sends email address as: Lars Kneschke <l.kneschke@metaways.de> + if (preg_match('/(.*)<(.+@^@+)>/', $email, $matches)) { + $email = trim($matches2); + } + + $emails = $email; + } + } + + // Warning: If contact has more than 3 addresses in Kolab they will be removed + + if ($this->backend instanceof kolab_sync_storage_kolab4) { + // Remove addresses that do not exist anymore + $existing = ; + foreach ('email:work', 'email:other', 'email:home' as $key) { + if (!empty($contact$key)) { + $contact$key = array_values(array_intersect($contact$key, $emails)); + $existing = array_merge($existing, $contact$key); + if (empty($contact$key)) { + unset($contact$key); + } + } + } + + // Add new addresses + foreach (array_diff($emails, $existing) as $email) { + if (!isset($contact'email:other')) { + $contact'email:other' = ; + } + $contact'email:other' = $email; + } + } else { + foreach ($emails as $idx => $email) { + // try to find address type, at least we can do this if address wasn't changed + $type = ''; + foreach ($contact'email' ?? as $existing) { + if (isset($existing'address') && $existing'address' == $email) { + $type = $existing'type' ?? ''; + } + } + + $emails$idx = 'address' => $email, 'type' => $type; + } + + $contact'email' = $emails; + } + } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -30,6 +30,9 @@ { public const MAX_SEARCH_RESULT = 200; + protected const MAIL_SUBMITTED = 1; + protected const MAIL_DONE = 2; + /** * Mapping from ActiveSync Email namespace fields */ @@ -510,9 +513,13 @@ $is_ios = preg_match('/(iphone|ipad)/i', $this->device->devicetype); // attachments - $attachments = array_merge($message->attachments, $message->inline_parts); + $attachments = $message->attachments; + if (isset($message->inline_parts)) { + $attachments = array_merge($attachments, $message->inline_parts); + } if (!empty($attachments)) { $result'attachments' = ; + $attachment_ids = ; foreach ($attachments as $attachment) { $att = ; @@ -521,28 +528,37 @@ continue; } + // Eliminate possible duplicates in $attachments + if (in_array($attachment->mime_id, $attachment_ids)) { + continue; + } + $filename = rcube_charset::clean($attachment->filename); if (empty($filename) && $attachment->mimetype == 'text/html') { $filename = 'HTML Part'; } - $att'displayName' = $filename; + $att'displayName' = $filename; $att'fileReference' = $serverId . '::' . $attachment->mime_id; - $att'method' = 1; + // Message/rfc822 parts have Method=5, anything else Method=1 + // FIXME: Method=6 is for OLE objects, but is it inline images or sth else? + $att'method' = strcasecmp($attachment->mimetype, 'message/rfc822') === 0 ? 5 : 1; $att'estimatedDataSize' = $attachment->size; if (!empty($attachment->content_id)) { $att'contentId' = rcube_charset::clean($attachment->content_id); } + if (!empty($attachment->content_location)) { $att'contentLocation' = rcube_charset::clean($attachment->content_location); } - if (in_array($attachment, $message->inline_parts)) { + if (strcasecmp($attachment->disposition, 'inline') === 0) { $att'isInline' = 1; } $result'attachments' = new Syncroton_Model_EmailAttachment($att); + $attachment_ids = $attachment->mime_id; } } @@ -813,30 +829,44 @@ * Send an email * * @param mixed $message MIME message - * @param boolean $saveInSent Enables saving the sent message in Sent folder + * @param bool $saveInSent Enables saving the sent message in Sent folder + * @param ?string $clientId Message client-id * * @throws Syncroton_Exception_Status */ - public function sendEmail($message, $saveInSent) + public function sendEmail($message, $saveInSent, $clientId) { + if (($status = $this->sentMailStatus($clientId, $cache, $cache_key)) === self::MAIL_DONE) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MESSAGE_PREVIOUSLY_SENT); + } + if (!($message instanceof kolab_sync_message)) { $message = new kolab_sync_message($message); } - $sent = $message->send($smtp_error); + // Snet the message (if not sent previously) + if (!$status) { + $sent = $message->send($smtp_error); - if (!$sent) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAIL_SUBMISSION_FAILED); + if (!$sent) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAIL_SUBMISSION_FAILED); + } + + if (!empty($cache)) { + $cache->set($cache_key, self::MAIL_SUBMITTED); + } } // Save sent message in Sent folder if ($saveInSent) { - $sent_folder = kolab_sync::get_instance()->config->get('sent_mbox'); - - if (strlen($sent_folder) && $this->storage->folder_exists($sent_folder)) { - return $this->storage->save_message($sent_folder, $message->source(), '', false, 'SEEN'); + if (!$message->saveInSent()) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } } + + if (!empty($cache)) { + $cache->set($cache_key, self::MAIL_DONE); + } } /** @@ -845,13 +875,18 @@ * @param array|string $itemId A string LongId or an array with following properties: * collectionId, itemId and instanceId * @param resource|string $body MIME message - * @param boolean $saveInSent Enables saving the sent message in Sent folder - * @param boolean $replaceMime If enabled, original message would be appended + * @param bool $saveInSent Enables saving the sent message in Sent folder + * @param bool $replaceMime If enabled, original message would be appended + * @param ?string $clientId Message client-id * * @throws Syncroton_Exception_Status */ - public function forwardEmail($itemId, $body, $saveInSent, $replaceMime) + public function forwardEmail($itemId, $body, $saveInSent, $replaceMime, $clientId) { + if ($this->sentMailStatus($clientId) === self::MAIL_DONE) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MESSAGE_PREVIOUSLY_SENT); + } + /* @TODO: The SmartForward command can be applied to a meeting. When SmartForward is applied to a recurring meeting, @@ -893,7 +928,7 @@ } // Send message - $this->sendEmail($sync_msg, $saveInSent); + $this->sendEmail($sync_msg, $saveInSent, $clientId); // Set FORWARDED flag on the replied message if (empty($message->headers->flags'FORWARDED')) { @@ -908,13 +943,18 @@ * @param array|string $itemId A string LongId or an array with following properties: * collectionId, itemId and instanceId * @param resource|string $body MIME message - * @param boolean $saveInSent Enables saving the sent message in Sent folder - * @param boolean $replaceMime If enabled, original message would be appended + * @param bool $saveInSent Enables saving the sent message in Sent folder + * @param bool $replaceMime If enabled, original message would be appended + * @param ?string $clientId Message client-id * * @throws Syncroton_Exception_Status */ - public function replyEmail($itemId, $body, $saveInSent, $replaceMime) + public function replyEmail($itemId, $body, $saveInSent, $replaceMime, $clientId) { + if ($this->sentMailStatus($clientId) === self::MAIL_DONE) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MESSAGE_PREVIOUSLY_SENT); + } + $msg = $this->parseMessageId($itemId); $message = $this->getObject($itemId); @@ -944,7 +984,7 @@ } // Send message - $this->sendEmail($sync_msg, $saveInSent); + $this->sendEmail($sync_msg, $saveInSent, $clientId); // Set ANSWERED flag on the replied message if (empty($message->headers->flags'ANSWERED')) { @@ -977,7 +1017,7 @@ // @TODO: caching with Options->RebuildResults support foreach ($folders as $folderid) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); + $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid, $this->modelName); if ($foldername === null) { continue; @@ -1568,4 +1608,27 @@ return kolab_sync_message::fake_message($headers, $msg); } } + + /** + * Check in the cache if specified message (client-id) has been previously processed + * and with what result. It's used to prevent a duplicate submission. + */ + protected function sentMailStatus($clientId, &$cache = null, &$cache_key = null) + { + // Note: ClientId is set with ActiveSync version >= 14.0 + if ($clientId === null || $clientId === '') { + return 0; + } + + $engine = kolab_sync::get_instance(); + $status = null; + $cache_key = "ClientId:{$clientId}"; + + if ($cache_type = $engine->config->get('activesync_cache', 'db')) { + $cache = $engine->get_cache('activesync_cache', $cache_type, '1d', false); + $status = $cache->get($cache_key); + } + + return (int) $status; + } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_message.php
Changed
@@ -216,13 +216,21 @@ unset($smtp_headers'Bcc'); // send message - if (!is_object($rcube->smtp)) { - $rcube->smtp_init(true); - } + if (isset($headers'X-Syncroton-Test') + && preg_match('/smtp=(true|false)/i', $headers'X-Syncroton-Test', $m) + ) { + $sent = $m1 == 'true'; + $smtp_response = ; + $smtp_error = 999; + } else { + if (!is_object($rcube->smtp)) { + $rcube->smtp_init(true); + } - $sent = $rcube->smtp->send_mail($headers'From', $recipients, $smtp_headers, $this->body, $smtp_opts); - $smtp_response = $rcube->smtp->get_response(); - $smtp_error = $rcube->smtp->get_error(); + $sent = $rcube->smtp->send_mail($headers'From', $recipients, $smtp_headers, $this->body, $smtp_opts); + $smtp_response = $rcube->smtp->get_response(); + $smtp_error = $rcube->smtp->get_error(); + } // log error if (!$sent) { @@ -261,6 +269,38 @@ } /** + * Save message in Sent folder + * + * @return bool True on success (or when the folder does not exist), False otherwise + */ + public function saveInSent() + { + $engine = kolab_sync::get_instance(); + $storage = $engine->get_storage(); + $sent_folder = $engine->config->get('sent_mbox'); + + if (isset($this->headers'X-Syncroton-Test') + && preg_match('/imap=(true|false)/i', $this->headers'X-Syncroton-Test', $m) + ) { + return $m1 == 'true'; + } + + if (strlen($sent_folder) && $storage->folder_exists($sent_folder)) { + $source = $this->source(); + $uid = $storage->save_message($sent_folder, $source, '', false, 'SEEN'); + + if (empty($uid)) { + rcube::raise_error('code' => 500, 'type' => 'imap', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Failed to save message in {$sent_folder}", true, false); + return false; + } + } + + return true; + } + + /** * Parses the message source and fixes 8bit data for ActiveSync. * This way any not UTF8 characters will be encoded before * sending to the device.
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php
Changed
@@ -51,12 +51,11 @@ public $syncTimeStamp; protected $storage; - protected $folder_meta; protected $folder_uids; protected $folders = ; - protected $root_meta; protected $relations = ; protected $relationSupport = self::MODEL_TASKS, self::MODEL_NOTES, self::MODEL_EMAIL; + protected $subscriptions; protected $tag_rts = ; private $modseq = ; @@ -116,6 +115,8 @@ // Disable paging $this->storage->set_pagesize(999999); + + $this->subscriptions = new kolab_subscriptions(); } /** @@ -127,29 +128,6 @@ } /** - * List known devices - * - * @return array Device list as hash array - */ - public function devices_list() - { - if ($this->root_meta === null) { - // @TODO: consider server annotation instead of INBOX - if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) { - $this->root_meta = $this->unserialize_metadata($metaself::ROOT_MAILBOXself::ASYNC_KEY); - } else { - $this->root_meta = ; - } - } - - if (!empty($this->root_meta'DEVICE') && is_array($this->root_meta'DEVICE')) { - return $this->root_meta'DEVICE'; - } - - return ; - } - - /** * Get list of folders available for sync * * @param string $deviceid Device identifier @@ -160,33 +138,15 @@ */ public function folders_list($deviceid, $type, $flat_mode = false) { - // get all folders of specified type - $folders = kolab_storage::list_folders('', '*', $type, false, $typedata); - - // get folders activesync config - $folderdata = $this->folder_meta(); - - if (!is_array($folders) || !is_array($folderdata)) { - return false; - } + $typedata = kolab_storage::folders_typedata(); $folders_list = ; // check if folders are "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - + foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $sub) { // force numeric folder name to be a string (T1283) $folder = (string) $folder; - if (!empty($type) && !in_array($folder, $folders)) { - continue; - } - // Activesync folder identifier (serverId) $folder_type = !empty($typedata$folder) ? $typedata$folder : 'mail'; $folder_id = $this->folder_id($folder, $folder_type); @@ -257,35 +217,6 @@ } /** - * Getter for folder metadata - * - * @return array|bool Hash array with meta data for each folder, False on backend failure - */ - protected function folder_meta() - { - if (!isset($this->folder_meta)) { - // get folders activesync config - $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY); - - if (!is_array($folderdata)) { - return $this->folder_meta = false; - } - - $this->folder_meta = ; - - foreach ($folderdata as $folder => $meta) { - if (isset($metaself::ASYNC_KEY)) { - if ($metadata = $this->unserialize_metadata($metaself::ASYNC_KEY)) { - $this->folder_meta$folder = $metadata; - } - } - } - } - - return $this->folder_meta; - } - - /** * Creates folder and subscribes to the device * * @param string $name Folder name (UTF8) @@ -299,9 +230,10 @@ { $parent = null; $name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP'); + $type = self::type_activesync2kolab($type); if ($parentid) { - $parent = $this->folder_id2name($parentid, $deviceid); + $parent = $this->folder_id2name($parentid, $deviceid, $type); if ($parent === null) { throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND); @@ -317,12 +249,10 @@ throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS); } - $type = self::type_activesync2kolab($type); $created = kolab_storage::folder_create($name, $type, true); if ($created) { - // Set ActiveSync subscription flag - $this->folder_set($name, $deviceid, 1); + $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type); return $this->folder_id($name, $type); } @@ -341,17 +271,18 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * @param string $new_name New folder name (UTF8) * @param ?string $parentid Folder parent identifier * * @return bool True on success, False on failure */ - public function folder_rename($folderid, $deviceid, $new_name, $parentid) + public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid) { - $old_name = $this->folder_id2name($folderid, $deviceid); + $old_name = $this->folder_id2name($folderid, $deviceid, $type); if ($parentid) { - $parent = $this->folder_id2name($parentid, $deviceid); + $parent = $this->folder_id2name($parentid, $deviceid, $type); } $name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP'); @@ -366,18 +297,22 @@ return true; } - $this->folder_meta = null; - // TODO: folder type change? - $type = kolab_storage::folder_type($old_name); - // don't use kolab_storage for moving mail folders - if (preg_match('/^mail/', $type)) { - return $this->storage->rename_folder($old_name, $name); + if ($type == self::MODEL_EMAIL) { + $result = $this->storage->rename_folder($old_name, $name); } else { - return kolab_storage::folder_rename($old_name, $name); + $result = kolab_storage::folder_rename($old_name, $name); } + + if ($result) { + // Set ActiveSync subscription flag + // TODO: Use old subscription flag value + $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type); + } + + return $result; } /** @@ -385,18 +320,16 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * * @return bool True on success, False otherwise */ - public function folder_delete($folderid, $deviceid) + public function folder_delete($folderid, $deviceid, $type) { - $name = $this->folder_id2name($folderid, $deviceid); - $type = kolab_storage::folder_type($name); - - unset($this->folder_meta$name); + $name = $this->folder_id2name($folderid, $deviceid, $type); // don't use kolab_storage for deleting mail folders - if (preg_match('/^mail/', $type)) { + if ($type == self::MODEL_EMAIL) { return $this->storage->delete_folder($name); } @@ -408,30 +341,27 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * @param bool $recursive Apply to the folder and its subfolders * * @return bool True on success, False otherwise */ - public function folder_empty($folderid, $deviceid, $recursive = false) + public function folder_empty($folderid, $deviceid, $type, $recursive = false) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); // Remove all entries if (!$this->storage->clear_folder($foldername)) { return false; } - // Remove subfolders + // Empty subfolders if ($recursive) { $delim = $this->storage->get_hierarchy_delimiter(); - $folderdata = $this->folder_meta(); + $folders = $this->subscriptions->list_subscriptions($deviceid, $type); - if (!is_array($folderdata)) { - return false; - } - - foreach ($folderdata as $subfolder => $meta) { - if (!empty($meta'FOLDER'$deviceid'S') && strpos((string) $subfolder, $foldername . $delim)) { + foreach (array_keys($folders) as $subfolder) { + if (strpos((string) $subfolder, $foldername . $delim)) { if (!$this->storage->clear_folder((string) $subfolder)) { return false; } @@ -443,233 +373,6 @@ } /** - * Sets ActiveSync subscription flag on a folder - * - * @param string $name Folder name (UTF7-IMAP) - * @param string $deviceid Device identifier - * @param int $flag Flag value (0|1|2) - * - * @return bool True on success, False on failure - */ - protected function folder_set($name, $deviceid, $flag) - { - if (empty($deviceid)) { - return false; - } - - // get folders activesync config - $metadata = $this->folder_meta(); - - if (!is_array($metadata)) { - return false; - } - - $metadata = $metadata$name ?? ; - - if ($flag) { - if (empty($metadata)) { - $metadata = ; - } - - if (empty($metadata'FOLDER')) { - $metadata'FOLDER' = ; - } - - if (empty($metadata'FOLDER'$deviceid)) { - $metadata'FOLDER'$deviceid = ; - } - - // Z-Push uses: - // 1 - synchronize, no alarms - // 2 - synchronize with alarms - $metadata'FOLDER'$deviceid'S' = $flag; - } else { - unset($metadata'FOLDER'$deviceid'S'); - - if (empty($metadata'FOLDER'$deviceid)) { - unset($metadata'FOLDER'$deviceid); - } - - if (empty($metadata'FOLDER')) { - unset($metadata'FOLDER'); - } - - if (empty($metadata)) { - $metadata = null; - } - } - - // Return if nothing's been changed - if (!self::data_array_diff($this->folder_meta$name ?? null, $metadata)) { - return true; - } - - $this->folder_meta$name = $metadata; - - return $this->storage->set_metadata($name, self::ASYNC_KEY => $this->serialize_metadata($metadata)); - } - - /** - * Returns device metadata - * - * @param string $id Device ID - * - * @return array|null Device metadata - */ - public function device_get($id) - { - $devices_list = $this->devices_list(); - return $devices_list$id ?? null; - } - - /** - * Registers new device on server - * - * @param array $device Device data - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_create($device, $id) - { - // Fill local cache - $this->devices_list(); - - // Some devices create dummy devices with name "validate" (#1109) - // This device entry is used in two initial requests, but later - // the device registers a real name. We can remove this dummy entry - // on new device creation - $this->device_delete('validate'); - - // Old Kolab_ZPush device parameters - // MODE: -1 | 0 | 1 (not set | flatmode | foldermode) - // TYPE: device type string - // ALIAS: user-friendly device name - - // Syncroton (kolab_sync_backend_device) uses - // ID: internal identifier in syncroton database - // TYPE: device type string - // ALIAS: user-friendly device name - - $metadata = $this->root_meta; - $metadata'DEVICE'$id = $device; - $metadata = self::ASYNC_KEY => $this->serialize_metadata($metadata); - - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // Update local cache - $this->root_meta'DEVICE'$id = $device; - - // subscribe default set of folders - $this->device_init_subscriptions($id); - } - - return $result; - } - - /** - * Device update. - * - * @param array $device Device data - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_update($device, $id) - { - $devices_list = $this->devices_list(); - $old_device = $devices_list$id; - - if (!$old_device) { - return false; - } - - // Do nothing if nothing is changed - if (!self::data_array_diff($old_device, $device)) { - return true; - } - - $device = array_merge($old_device, $device); - - $metadata = $this->root_meta; - $metadata'DEVICE'$id = $device; - $metadata = self::ASYNC_KEY => $this->serialize_metadata($metadata); - - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // Update local cache - $this->root_meta'DEVICE'$id = $device; - } - - return $result; - } - - /** - * Device delete. - * - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_delete($id) - { - $device = $this->device_get($id); - - if (!$device) { - return false; - } - - unset($this->root_meta'DEVICE'$id, $this->root_meta'FOLDER'$id); - - if (empty($this->root_meta'DEVICE')) { - unset($this->root_meta'DEVICE'); - } - if (empty($this->root_meta'FOLDER')) { - unset($this->root_meta'FOLDER'); - } - - $metadata = $this->serialize_metadata($this->root_meta); - $metadata = self::ASYNC_KEY => $metadata; - - // update meta data - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // remove device annotation for every folder - foreach ($this->folder_meta() as $folder => $meta) { - // skip root folder (already handled above) - if ($folder == self::ROOT_MAILBOX) { - continue; - } - - if (!empty($meta'FOLDER') && isset($meta'FOLDER'$id)) { - unset($meta'FOLDER'$id); - - if (empty($meta'FOLDER')) { - unset($this->folder_meta$folder'FOLDER'); - unset($meta'FOLDER'); - } - if (empty($meta)) { - unset($this->folder_meta$folder); - $meta = null; - } - - $metadata = self::ASYNC_KEY => $this->serialize_metadata($meta); - $res = $this->storage->set_metadata($folder, $metadata); - - if ($res && $meta) { - $this->folder_meta$folder = $meta; - } - } - } - } - - return $result; - } - - /** * Creates an item in a folder. * * @param string $folderid Folder identifier @@ -683,7 +386,7 @@ public function createItem($folderid, $deviceid, $type, $data, $params = ) { if ($type == self::MODEL_EMAIL) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); $uid = $this->storage->save_message($foldername, $data, '', false, $params'flags' ?? ); @@ -733,7 +436,7 @@ public function deleteItem($folderid, $deviceid, $type, $uid, $moveToTrash = false) { if ($type == self::MODEL_EMAIL) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); $trash = kolab_sync::get_instance()->config->get('trash_mbox'); // move message to the Trash folder @@ -786,7 +489,7 @@ public function updateItem($folderid, $deviceid, $type, $uid, $data, $params = ) { if ($type == self::MODEL_EMAIL) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); // Note: We do not support a message body update, as it's not needed @@ -905,7 +608,7 @@ return $this->folders$unique_key; } - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); return $this->folders$unique_key = kolab_storage::get_folder($foldername, $type); } @@ -921,17 +624,12 @@ */ public function getFolderConfig($folderid, $deviceid, $type) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); - $metadata = $this->folder_meta(); - $config = ; - - if (!empty($metadata$foldername'FOLDER'$deviceid)) { - $config = $metadata$foldername'FOLDER'$deviceid; - } + $subs = $this->subscriptions->folder_subscriptions($foldername, $type); return - 'ALARMS' => ($config'S' ?? 0) == 2, + 'ALARMS' => ($subs$deviceid ?? 0) == 2, ; } @@ -948,7 +646,7 @@ public function getItem($folderid, $deviceid, $type, $uid) { if ($type == self::MODEL_EMAIL) { - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); $message = new rcube_message($uid, $foldername); if (!empty($message->headers)) { @@ -1027,8 +725,8 @@ public function moveItem($srcFolderId, $deviceid, $type, $uid, $dstFolderId) { if ($type === self::MODEL_EMAIL) { - $src_name = $this->folder_id2name($srcFolderId, $deviceid); - $dst_name = $this->folder_id2name($dstFolderId, $deviceid); + $src_name = $this->folder_id2name($srcFolderId, $deviceid, $type); + $dst_name = $this->folder_id2name($dstFolderId, $deviceid, $type); if ($dst_name === null) { throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); @@ -1174,7 +872,7 @@ $result = $result_type == kolab_sync_data::RESULT_COUNT ? 0 : ; - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); if ($foldername === null) { return $result; @@ -1280,10 +978,11 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * * @return string|null Extra data (JSON-encoded) */ - public function getExtraData($folderid, $deviceid) + public function getExtraData($folderid, $deviceid, $type) { //We explicitly return a cached value that was used during the search. //Otherwise we'd risk storing a higher modseq value and missing an update. @@ -1292,7 +991,7 @@ } //If we didn't fetch modseq in the first place we have to fetch it now. - $foldername = $this->folder_id2name($folderid, $deviceid); + $foldername = $this->folder_id2name($folderid, $deviceid, $type); if ($foldername !== null) { $folder_data = $this->storage->folder_data($foldername); if (!empty($folder_data'HIGHESTMODSEQ')) { @@ -1618,12 +1317,19 @@ } /** - * Subscribe default set of folders on device registration + * Subscribes to a default set of folder on a new device registration + * + * @param string $deviceid Device ID */ - protected function device_init_subscriptions($deviceid) + public function device_init($deviceid) { - // INBOX always exists - $this->folder_set('INBOX', $deviceid, 1); + $subscribed = + 'mail' => 'INBOX' => 1, // INBOX always exists + 'event' => , + 'contact' => , + 'task' => , + 'note' => , + ; $supported_types = 'mail.drafts', @@ -1660,99 +1366,76 @@ // only personal folders if ($this->storage->folder_namespace($folder) == 'personal') { $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; - $this->folder_set($folder, $deviceid, $flag); + $type, = explode('.', $type); + $subscribed$type$folder = $flag; $folders = $folder; } } } - // we're in default mode, exit - if (!$mode) { - return; - } - - // below we support additionally all mail folders - $supported_types = 'mail'; - $supported_types = 'mail.junkemail'; + if ($mode) { + // below we support additionally all mail folders + $supported_types = 'mail'; + $supported_types = 'mail.junkemail'; - // get configured special folders - $special_folders = ; - $map = - 'drafts' => 'mail.drafts', - 'junk' => 'mail.junkemail', - 'sent' => 'mail.sentitems', - 'trash' => 'mail.wastebasket', - ; + // get configured special folders + $special_folders = ; + $map = + 'drafts' => 'mail.drafts', + 'junk' => 'mail.junkemail', + 'sent' => 'mail.sentitems', + 'trash' => 'mail.wastebasket', + ; - foreach ($map as $folder => $type) { - if ($folder = $config->get($folder . '_mbox')) { - $special_folders$folder = $type; + foreach ($map as $folder => $type) { + if ($folder = $config->get($folder . '_mbox')) { + $special_folders$folder = $type; + } } - } - // get folders list(s) - if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) { - $all_folders = $this->storage->list_folders(); - if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) { - $subscribed_folders = $this->storage->list_folders_subscribed(); + // get folders list(s) + if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) { + $all_folders = $this->storage->list_folders(); + if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) { + $subscribed_folders = $this->storage->list_folders_subscribed(); + } + } else { + $all_folders = $this->storage->list_folders_subscribed(); } - } else { - $all_folders = $this->storage->list_folders_subscribed(); - } - foreach ($all_folders as $folder) { - // folder already subscribed - if (in_array($folder, $folders)) { - continue; - } + foreach ($all_folders as $folder) { + // folder already subscribed + if (in_array($folder, $folders)) { + continue; + } - $type = ($foldertypes$folder ?? null) ?: 'mail'; - if ($type == 'mail' && isset($special_folders$folder)) { - $type = $special_folders$folder; - } + $type = ($foldertypes$folder ?? null) ?: 'mail'; + if ($type == 'mail' && isset($special_folders$folder)) { + $type = $special_folders$folder; + } - if (!in_array($type, $supported_types)) { - continue; - } + if (!in_array($type, $supported_types)) { + continue; + } - $ns = strtoupper($this->storage->folder_namespace($folder)); + $ns = strtoupper($this->storage->folder_namespace($folder)); - // subscribe the folder according to configured mode - // and folder namespace/subscription status - if (($mode & constant("self::INIT_ALL_{$ns}")) - || (($mode & constant("self::INIT_SUB_{$ns}")) - && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders))) - ) { - $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; - $this->folder_set($folder, $deviceid, $flag); + // subscribe the folder according to configured mode + // and folder namespace/subscription status + if (($mode & constant("self::INIT_ALL_{$ns}")) + || (($mode & constant("self::INIT_SUB_{$ns}")) + && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders))) + ) { + $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; + $type, = explode('.', $type); + $subscribed$type$folder = $flag; + } } } - } - - /** - * Helper method to decode saved IMAP metadata - */ - protected function unserialize_metadata($str) - { - if (!empty($str)) { - $data = json_decode($str, true); - return $data; - } - - return null; - } - /** - * Helper method to encode IMAP metadata for saving - */ - protected function serialize_metadata($data) - { - if (!empty($data) && is_array($data)) { - $data = json_encode($data); - return $data; + foreach ($subscribed as $type => $list) { + $this->subscriptions->set_subscriptions($deviceid, $type, $list); } - - return null; } /** @@ -1837,19 +1520,6 @@ return $this->folder_uids$name; } - /* - @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves. - There's one inconvenience of this solution: folder name/type change - would be handled in ActiveSync as delete + create. - - // get folders unique identifier - $folderdata = $this->storage->get_metadata($name, self::UID_KEY); - - if ($folderdata && !empty($folderdata$name)) { - $uid = $folderdata$nameself::UID_KEY; - return $this->folder_uids$name = $uid; - } - */ if (strcasecmp($name, 'INBOX') === 0) { // INBOX is always inbox, prevent from issues related with a change of // folder type annotation (it can be initially unset). @@ -1874,57 +1544,32 @@ /** * Returns IMAP folder name * - * @param string $id Folder identifier - * @param string $deviceid Device dentifier + * @param string $id Folder identifier + * @param string $deviceid Device dentifier + * @param string $type Folder type * * @return string|null Folder name (UTF7-IMAP) */ - public function folder_id2name($id, $deviceid) + public function folder_id2name($id, $deviceid, $type) { + // TODO: This method should become protected + // check in cache first if (!empty($this->folder_uids)) { if (($name = array_search($id, $this->folder_uids)) !== false) { return $name; } } - /* - @TODO: see folder_id() - - // get folders unique identifier - $folderdata = $this->storage->get_metadata('*', self::UID_KEY); - - foreach ((array)$folderdata as $folder => $data) { - if (!empty($dataself::UID_KEY)) { - $uid = $dataself::UID_KEY; - $this->folder_uids$folder = $uid; - if ($uid == $id) { - $name = $folder; - } - } - } - */ - // get all folders of specified type - $folderdata = $this->folder_meta(); - - if (!is_array($folderdata) || empty($id)) { - return null; - } $name = null; - // check if folders are "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - - if ($uid = $this->folder_id($folder)) { - $this->folder_uids$folder = $uid; - } + if (strpos($type, '.')) { + $type, = explode('.', $type); + } - if ($uid === $id) { + // Get the uids of all folders subscribed for activesync + foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $props) { + if ($this->folder_id($folder) === $id) { $name = $folder; } } @@ -2046,34 +1691,4 @@ { return kolab_storage::$last_error; } - - /** - * Compares two arrays - * - * @param array $array1 - * @param array $array2 - * - * @return bool True if arrays differs, False otherwise - */ - protected static function data_array_diff($array1, $array2) - { - if (!is_array($array1) || !is_array($array2)) { - return $array1 != $array2; - } - - if (count($array1) != count($array2)) { - return true; - } - - foreach ($array1 as $key => $val) { - if (!array_key_exists($key, $array2)) { - return true; - } - if ($val !== $array2$key) { - return true; - } - } - - return false; - } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage_kolab4.php
Changed
@@ -79,6 +79,9 @@ // Disable paging $this->storage->set_pagesize(999999); + + // Folders subscriptions engine + $this->subscriptions = new kolab_subscriptions($url); } /** @@ -96,12 +99,6 @@ // get mail folders subscribed for sync if ($type === self::MODEL_EMAIL) { - $folderdata = $this->folder_meta(); - - if (!is_array($folderdata)) { - return false; - } - $special_folders = $this->storage->get_special_folders(true); $type_map = 'drafts' => 3, @@ -110,13 +107,7 @@ ; // Get the folders "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - + foreach ($this->subscriptions->list_subscriptions($deviceid, self::MODEL_EMAIL) as $folder => $meta) { // Force numeric folder name to be a string (T1283) $folder = (string) $folder; @@ -140,10 +131,10 @@ } } - // TODO: For now all DAV folders are subscribed - if (empty($list)) { - foreach ($this->davStorage->get_folders($type) as $folder) { + foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder) { + /** @var kolab_storage_dav_folder $folder */ + $folder = $folder2; $folder_data = $this->folder_data($folder, $type); $list$folder_data'serverId' = $folder_data; @@ -181,7 +172,7 @@ $name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP'); if ($parentid) { - $parent = $this->folder_id2name($parentid, $deviceid); + $parent = $this->folder_id2name($parentid, $deviceid, self::MODEL_EMAIL); if ($parent === null) { throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND); @@ -203,7 +194,7 @@ if ($created) { // Set ActiveSync subscription flag - $this->folder_set($name, $deviceid, 1); + $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL); return $this->folder_id($name, 'mail'); } @@ -231,6 +222,10 @@ $props = 'name' => $name, 'type' => $type; if ($id = $this->davStorage->folder_update($props)) { + // Set ActiveSync subscription flag + $this->subscriptions->folder_subscribe($deviceid, $this->davStorage->new_location, 1, $type); + $this->folders = ; + return "DAV:{$type}:{$id}"; } @@ -245,12 +240,13 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * @param string $new_name New folder name (UTF8) * @param ?string $parentid Folder parent identifier * * @return bool True on success, False on failure */ - public function folder_rename($folderid, $deviceid, $new_name, $parentid) + public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid) { // DAV folder if (strpos($folderid, 'DAV:') === 0) { @@ -267,10 +263,10 @@ } // Mail folder - $old_name = $this->folder_id2name($folderid, $deviceid); + $old_name = $this->folder_id2name($folderid, $deviceid, $type); if ($parentid) { - $parent = $this->folder_id2name($parentid, $deviceid); + $parent = $this->folder_id2name($parentid, $deviceid, $type); } $name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP'); @@ -284,9 +280,14 @@ return true; } - $this->folder_meta = null; + $result = $this->storage->rename_folder($old_name, $name); + + if ($result) { + // Set ActiveSync subscription flag + $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL); + } - return $this->storage->rename_folder($old_name, $name); + return $result; } /** @@ -294,10 +295,11 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * * @return bool True on success, False otherwise */ - public function folder_delete($folderid, $deviceid) + public function folder_delete($folderid, $deviceid, $type) { // DAV folder if (strpos($folderid, 'DAV:') === 0) { @@ -307,9 +309,7 @@ } // Mail folder - $name = $this->folder_id2name($folderid, $deviceid); - - unset($this->folder_meta$name); + $name = $this->folder_id2name($folderid, $deviceid, $type); return $this->storage->delete_folder($name); } @@ -319,11 +319,12 @@ * * @param string $folderid Folder identifier * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) * @param bool $recursive Apply to the folder and its subfolders * * @return bool True on success, False otherwise */ - public function folder_empty($folderid, $deviceid, $recursive = false) + public function folder_empty($folderid, $deviceid, $type, $recursive = false) { // DAV folder if (strpos($folderid, 'DAV:') === 0) { @@ -339,7 +340,7 @@ } // Mail folder - return parent::folder_empty($folderid, $deviceid, $recursive); + return parent::folder_empty($folderid, $deviceid, $type, $recursive); } /** @@ -390,20 +391,7 @@ if (isset($this->folder_uids$name)) { return $this->folder_uids$name; } - /* - @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves. - There's one inconvenience of this solution: folder name/type change - would be handled in ActiveSync as delete + create. - @TODO: Consider using MAILBOXID (RFC8474) that Cyrus v3 supports - // get folders unique identifier - $folderdata = $this->storage->get_metadata($name, self::UID_KEY); - - if ($folderdata && !empty($folderdata$name)) { - $uid = $folderdata$nameself::UID_KEY; - return $this->folder_uids$name = $uid; - } - */ if (strcasecmp($name, 'INBOX') === 0) { // INBOX is always inbox, prevent from issues related with a change of // folder type annotation (it can be initially unset). @@ -420,50 +408,19 @@ /** * Returns IMAP folder name * - * @param string $id Folder identifier - * @param string $deviceid Device dentifier + * @param string $id Folder identifier + * @param string $deviceid Device dentifier + * @param string $type Folder type * * @return null|string Folder name (UTF7-IMAP) */ - public function folder_id2name($id, $deviceid) + public function folder_id2name($id, $deviceid, $type) { - // TODO: This method should become protected and be used for mail folders only if (strpos($id, 'DAV:') === 0) { throw new Exception("Unsupported folder_id2name() call on a DAV folder"); } - // check in cache first - if (!empty($this->folder_uids)) { - if (($name = array_search($id, $this->folder_uids)) !== false) { - return $name; - } - } - - // get all folders of specified type - $folderdata = $this->folder_meta(); - - if (!is_array($folderdata) || empty($id)) { - return null; - } - - // check if folders are "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - - if ($uid = $this->folder_id($folder, 'mail')) { - $this->folder_uids$folder = $uid; - } - - if ($uid === $id) { - $name = $folder; - } - } - - return $name ?? null; + return parent::folder_id2name($id, $deviceid, $type); } /** @@ -502,7 +459,7 @@ * @param string $deviceid Device identifier * @param string $type Activesync model name (folder type) * - * @return ?kolab_storage_folder + * @return ?kolab_storage_dav_folder */ public function getFolder($folderid, $deviceid, $type) { @@ -532,10 +489,15 @@ */ public function getFolderConfig($folderid, $deviceid, $type) { - // TODO: Get "alarms" from the DAV folder props, or implement - // a storage for folder properties + $alarms = 0; + + if ($folder = $this->getFolder($folderid, $deviceid, $type)) { + $subs = $this->subscriptions->folder_subscriptions($folder->href, $type); + $alarms = $subs$deviceid ?? 0; + } + return - 'ALARMS' => true, + 'ALARMS' => $alarms == 2, ; } @@ -549,9 +511,11 @@ } /** - * Subscribe default set of folders on device registration + * Subscribes to a default set of folder on a new device registration + * + * @param string $deviceid Device ID */ - protected function device_init_subscriptions($deviceid) + public function device_init($deviceid) { $config = rcube::get_instance()->config; $mode = (int) $config->get('activesync_init_subscriptions'); @@ -575,6 +539,13 @@ $all_folders = $this->storage->list_folders_subscribed(); } + $subscribe = + 'mail' => 'INBOX' => 1, + 'contact' => , + 'event' => , + 'task' => , + ; + foreach ($all_folders as $folder) { $ns = strtoupper($this->storage->folder_namespace($folder)); @@ -584,20 +555,29 @@ || ($mode & constant("self::INIT_ALL_{$ns}")) || (($mode & constant("self::INIT_SUB_{$ns}")) && ($subscribed_folders === null || in_array($folder, $subscribed_folders))) ) { - $this->folder_set($folder, $deviceid, 1); + $subscribe'mail'$folder = 1; } } - // TODO: Subscribe personal DAV folders, for now we assume all are subscribed - // TODO: Subscribe shared DAV folders + foreach ($subscribe as $type => $list) { + if ($type != 'mail') { + foreach ($this->subscriptions->list_folders($type) as $folder) { + // TODO: Subscribe personal DAV folders, for now we assume all are subscribed + $list$folder0 = ($type == 'event' || $type == 'task') ? 2 : 1; + } + } + + $this->subscriptions->set_subscriptions($deviceid, $type, $list); + } } - public function getExtraData($folderid, $deviceid) + public function getExtraData($folderid, $deviceid, $type) { if (strpos($folderid, 'DAV:') === 0) { return null; } - return parent::getExtraData($folderid, $deviceid); + + return parent::getExtraData($folderid, $deviceid, $type); } /**
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_timezone_converter.php
Changed
@@ -40,15 +40,8 @@ protected $_startDate = ; /** - * If set then the timezone guessing results will be cached. - * This is strongly recommended for performance reasons. - * - * @var rcube_cache - */ - protected $cache = null; - - /** * array of offsets known by ActiceSync clients, but unknown by php + * * @var array */ protected $_knownTimezones =
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/FoldersTest.php
Changed
@@ -15,6 +15,14 @@ $this->deleteTestFolder('Test Folder New', 'mail'); $this->deleteTestFolder('Test Contacts Folder', 'contact'); $this->deleteTestFolder('Test Contacts New', 'contact'); + // Make sure the default folders exist + if (!$this->isStorageDriver('kolab4')) { + $this->createTestFolder('Calendar', 'event.default'); + $this->createTestFolder('Contacts', 'contact.default'); + $this->createTestFolder('Tasks', 'task.default'); + $this->createTestFolder('Notes', 'note.default'); + } + //TODO: handle kolab4 case? } /** @@ -22,6 +30,7 @@ */ public function testFolderSyncBasic() { + $this->registerDevice(); $this->foldersCleanup(); $request = <<<EOF @@ -118,6 +127,7 @@ */ public function testSyncKeyResend() { + $this->createTestFolder("NewFolderToRemove", "mail"); $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -130,9 +140,12 @@ $dom = $this->fromWbxml($response->getBody()); $xpath = $this->xpath($dom); $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); //Now change something $this->createTestFolder("NewFolder", "mail"); + $this->deleteTestFolder('NewFolderToRemove', 'mail'); + $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -147,7 +160,7 @@ $xpath = $this->xpath($dom); $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); - $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame(strval(2), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); //Resend the same synckey $request = <<<EOF @@ -164,7 +177,7 @@ $xpath = $this->xpath($dom); $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); - $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame(strval(2), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); //And now make sure we can still move on $request = <<<EOF @@ -375,6 +388,7 @@ $dom = $this->fromWbxml($response->getBody()); $xpath = $this->xpath($dom); + // This depends on multifolder blacklists to be configured (otherwise we get no "collective" folders) $deleted = $this->isStorageDriver('kolab4') ? 3 : 4; // No Notes folder in Kolab4 $syncKey = 2;
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/PingTest.php
Changed
@@ -28,11 +28,11 @@ $dom = $this->fromWbxml($response->getBody()); $xpath = $this->xpath($dom); - //Initially we know no folders + // Initially we know no folders $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); - //We discover folders with a foldersync + // We discover folders with a foldersync $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -44,7 +44,7 @@ $response = $this->request($request, 'FolderSync'); $this->assertEquals(200, $response->getStatusCode()); - //Now we get to the actual ping + // Now we get to the actual ping $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -63,12 +63,61 @@ $this->assertEquals(200, $response->getStatusCode()); $dom = $this->fromWbxml($response->getBody()); $xpath = $this->xpath($dom); - // Initially we know no folders + // We don't find the folder state because it was never synced $this->assertSame('2', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); + + // Sync inbox + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + $this->assertEquals(200, $response->getStatusCode()); + + // Now we should get no change + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Ping xmlns="uri:Ping"> + <HeartbeatInterval>0</HeartbeatInterval> + <Folders> + <Folder> + <Id>38b950ebd62cd9a66929c89615d0fc04</Id> + <Class>Email</Class> + </Folder> + </Folders> + </Ping> + EOF; + + $response = $this->request($request, 'Ping'); + $this->assertEquals(200, $response->getStatusCode()); + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $this->assertSame('1', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); } /** - * Test Ping command + * Test Unknown Folder */ public function testUnknownFolder() { @@ -95,4 +144,151 @@ $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); } + + /** + * Test New Folder + */ + public function testNewFolder() + { + // Initialize the folder state + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>0</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $this->assertEquals(200, $response->getStatusCode()); + + $this->createTestFolder("NewFolder", "mail"); + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Ping xmlns="uri:Ping"> + <HeartbeatInterval>900</HeartbeatInterval> + <Folders> + <Folder> + <Id>38b950ebd62cd9a66929c89615d0fc04</Id> + <Class>Email</Class> + </Folder> + </Folders> + </Ping> + EOF; + + $response = $this->request($request, 'Ping'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); + } + + /** + * Test Changed Subscription Folder + */ + public function testNewFolderSubscriptionState() + { + // Initialize the folder state + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>0</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $this->assertEquals(200, $response->getStatusCode()); + + $this->setSubscriptionState("NewFolder", 'mail', null); + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Ping xmlns="uri:Ping"> + <HeartbeatInterval>900</HeartbeatInterval> + <Folders> + <Folder> + <Id>38b950ebd62cd9a66929c89615d0fc04</Id> + <Class>Email</Class> + </Folder> + </Folders> + </Ping> + EOF; + + $response = $this->request($request, 'Ping'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); + } + + /** + * Test changed subscription while ping is running + */ + public function testNewFolderSubscriptionStateDuringPing() + { + // Initialize the folder state + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>0</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $this->assertEquals(200, $response->getStatusCode()); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>1</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $this->assertEquals(200, $response->getStatusCode()); + + // Set the metadata via a background script, while Ping is running. + // This is essential to trigger a bug where the initial Ping seeds the roundcube metadata cache, + // and then subsequent checks don't update the cache. We have to use a separate process to set + // the metadata, because otherwise we update the cache (we could also not use the roundcube + // infrastructure instead). + $deviceId = self::$deviceId; + $username = self::$username; + $password = self::$password; + $script = TESTS_DIR . 'bootstrap.php'; + exec("php {$script} subscribe --folder=NewFolder --type=mail --delay=5" + . " --device={$deviceId} --user={$username} --password={$password} &", $output); + $this->assertSame(, $output); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Ping xmlns="uri:Ping"> + <HeartbeatInterval>9999</HeartbeatInterval> + <Folders> + <Folder> + <Id>38b950ebd62cd9a66929c89615d0fc04</Id> + <Class>Email</Class> + </Folder> + </Folders> + </Ping> + EOF; + + $response = $this->request($request, 'Ping'); + $this->assertEquals(200, $response->getStatusCode()); + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue); + } }
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/SendMailTest.php
Added
@@ -0,0 +1,112 @@ +<?php + +class SendMailTest extends Tests\SyncTestCase +{ + /** + * Test SendMail command + */ + public function testSendMail() + { + $this->emptyTestFolder('Sent', 'mail'); + + $clientId = microtime(); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <SendMail xmlns="uri:ComposeMail"> + <ClientId>{$clientId}</ClientId> + <SaveInSentItems /> + <Mime>From: testuser1@kolab.org + To: testuser2@kolab.org + Subject: Test + MIME-Version: 1.0 + Content-Type: text/plain; charset="iso-8859-1" + Message-ID: <msg1@kolab.org> + X-Syncroton-Test: smtp=true + + This is the email body content.</Mime> + </SendMail> + EOF; + + $response = $this->request($request, 'SendMail'); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame('', (string) $response->getBody()); + $emails = $this->listEmails('Sent', '*'); + $this->assertCount(1, $emails); + // TODO: Assert mail content + } + + /** + * Test SendMail command + */ + public function testSendMailErrorHandling() + { + $this->emptyTestFolder('Sent', 'mail'); + + $clientId = microtime(); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <SendMail xmlns="uri:ComposeMail"> + <ClientId>{$clientId}</ClientId> + <SaveInSentItems /> + <Mime>From: testuser1@kolab.org + To: testuser2@kolab.org + Subject: Test + MIME-Version: 1.0 + Content-Type: text/plain; charset="iso-8859-1" + Message-ID: <msg1@kolab.org> + X-Syncroton-Test: smtp=false imap=true + + This is the email body content.</Mime> + </SendMail> + EOF; + + // Expect a SMTP error + $response = $this->request($request, 'SendMail'); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame('120', $xpath->query("//ns:SendMail/ns:Status")->item(0)->nodeValue); + $this->assertCount(0, $this->listEmails('Sent', '*')); + + // Test IMAP error handling + $request = str_replace('smtp=false', 'smtp=true', $request); + $request = str_replace('imap=true', 'imap=false', $request); + + $response = $this->request($request, 'SendMail'); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame('110', $xpath->query("//ns:SendMail/ns:Status")->item(0)->nodeValue); + $this->assertCount(0, $this->listEmails('Sent', '*')); + + // Test no error + // smtp=false would cause error, but the submission should get skipped now + $request = str_replace('smtp=true', 'smtp=false', $request); + $request = str_replace('imap=false', '', $request); + + $response = $this->request($request, 'SendMail'); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame('', (string) $response->getBody()); + $this->assertCount(1, $this->listEmails('Sent', '*')); + + // Send the same mail again, expect an error + $response = $this->request($request, 'SendMail'); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame('118', $xpath->query("//ns:SendMail/ns:Status")->item(0)->nodeValue); + $this->assertCount(1, $this->listEmails('Sent', '*')); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/CalendarTest.php
Changed
@@ -44,6 +44,10 @@ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + if ($this->isStorageDriver('kolab')) { + $this->markTestSkipped("Appending test objects does not work with 'kolab' storage yet."); + } + // Append two event objects and sync them $this->appendObject($davFolder, 'event.ics1', 'event'); $this->appendObject($davFolder, 'event.ics2', 'event');
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/ContactsTest.php
Changed
@@ -53,6 +53,10 @@ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + if ($this->isStorageDriver('kolab')) { + $this->markTestSkipped("Appending test objects does not work with 'kolab' storage yet."); + } + // Append two contact objects and sync them // TODO: Test a folder with contact groups inside $this->appendObject($davFolder, 'contact.vcard1', 'contact'); @@ -75,10 +79,19 @@ $root .= "/ns:Commands/ns:Add"; $this->assertStringMatchesFormat("CRC%s", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); - $this->assertSame('Jack', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(0)->nodeValue); - $this->assertSame('Strong', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(0)->nodeValue); - $this->assertSame('Jane', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(1)->nodeValue); - $this->assertSame('Doe', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(1)->nodeValue); + $r = "{$root}1/ns:ApplicationData"; + $this->assertSame('Jack', $xpath->query("{$r}/Contacts:FirstName")->item(0)->nodeValue); + $this->assertSame('Strong', $xpath->query("{$r}/Contacts:LastName")->item(0)->nodeValue); + $this->assertSame('jack@kolab.org', $xpath->query("{$r}/Contacts:Email1Address")->item(0)->nodeValue); + $this->assertNull($xpath->query("{$r}/Contacts:Email2Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email3Address")->item(0)); + $r = "{$root}2/ns:ApplicationData"; + $this->assertSame('Jane', $xpath->query("{$r}/Contacts:FirstName")->item(0)->nodeValue); + $this->assertSame('J.', $xpath->query("{$r}/Contacts:MiddleName")->item(0)->nodeValue); + $this->assertSame('Doe', $xpath->query("{$r}/Contacts:LastName")->item(0)->nodeValue); + $this->assertNull($xpath->query("{$r}/Contacts:Email1Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email2Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email3Address")->item(0)); return $syncKey; } @@ -113,6 +126,10 @@ <ClientId>42</ClientId> <ApplicationData> <Contacts:FirstName>Lars</Contacts:FirstName> + <Contacts:LastName>Ulrich</Contacts:LastName> + <Contacts:Email1Address>lars@kolab.org</Contacts:Email1Address> + <Contacts:Email2Address>lars.tw@kolab.org</Contacts:Email2Address> + <Contacts:Email3Address>lars.th@kolab.org</Contacts:Email3Address> </ApplicationData> </Add> </Commands> @@ -137,7 +154,13 @@ $serverId = $xpath->query("ns:ServerId", $root)->item(0)->nodeValue; $this->assertStringMatchesFormat("CRC%s", $serverId); - // TODO: Test the content on the server + // Assert the content on the server + $contacts = $this->getDavObjects('Contacts', 'contact'); + $this->assertCount(3, $contacts); + usort($contacts, function ($c1, $c2) { return $c1'surname' <=> $c2'surname'; }); + $this->assertSame('Lars', $contacts2'firstname'); + $this->assertSame('Ulrich', $contacts2'surname'); + $this->assertSame('lars@kolab.org', 'lars.tw@kolab.org', 'lars.th@kolab.org', $contacts2'email:other'); return $syncKey, $serverId; }
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/EmailTest.php
Changed
@@ -112,29 +112,7 @@ $this->assertSame('test sync', $xpath->query("{$root}/ns:ApplicationData/Email:Subject")->item(0)->nodeValue); // List the rest of the mail - $request = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> - <Collections> - <Collection> - <SyncKey>{$syncKey}</SyncKey> - <CollectionId>{$folderId}</CollectionId> - <DeletesAsMoves>1</DeletesAsMoves> - <GetChanges>1</GetChanges> - <Options> - <FilterType>0</FilterType> - <Conflict>1</Conflict> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>2</Type> - <TruncationSize>51200</TruncationSize> - <AllOrNone>0</AllOrNone> - </BodyPreference> - </Options> - </Collection> - </Collections> - </Sync> - EOF; + $request = preg_replace('|<SyncKey>\d</SyncKey>|', "<SyncKey>{$syncKey}</SyncKey>", $request); $response = $this->request($request, 'Sync'); @@ -155,6 +133,41 @@ $this->assertStringMatchesFormat("{$folderId}::%d", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); $this->assertSame('sync test with attachment', $xpath->query("{$root}/ns:ApplicationData/Email:Subject")->item(0)->nodeValue); $this->assertSame(1, $xpath->query("{$root}/ns:ApplicationData/AirSyncBase:Body")->count()); + $attachments = $xpath->query("{$root}/ns:ApplicationData/AirSyncBase:Attachments/AirSyncBase:Attachment"); + $this->assertSame(3, $attachments->count()); + $att = $attachments->item(0); + $this->assertSame('message.eml', $xpath->query('AirSyncBase:DisplayName', $att)->item(0)->nodeValue); + $this->assertSame('5', $xpath->query('AirSyncBase:Method', $att)->item(0)->nodeValue); + $this->assertSame('63', $xpath->query('AirSyncBase:EstimatedDataSize', $att)->item(0)->nodeValue); + $this->assertSame(0, $xpath->query('AirSyncBase:IsInline', $att)->count()); + $att = $attachments->item(1); + $this->assertSame('logo1.gif', $xpath->query('AirSyncBase:DisplayName', $att)->item(0)->nodeValue); + $this->assertSame('1', $xpath->query('AirSyncBase:Method', $att)->item(0)->nodeValue); + $this->assertSame('76', $xpath->query('AirSyncBase:EstimatedDataSize', $att)->item(0)->nodeValue); + $this->assertSame(0, $xpath->query('AirSyncBase:IsInline', $att)->count()); + $att = $attachments->item(2); + $this->assertSame('logo2.gif', $xpath->query('AirSyncBase:DisplayName', $att)->item(0)->nodeValue); + $this->assertSame('1', $xpath->query('AirSyncBase:Method', $att)->item(0)->nodeValue); + $this->assertSame('76', $xpath->query('AirSyncBase:EstimatedDataSize', $att)->item(0)->nodeValue); + $this->assertSame('1', $xpath->query('AirSyncBase:IsInline', $att)->item(0)->nodeValue); + $this->assertSame('foo4foo1@bar.net', $xpath->query('AirSyncBase:ContentId', $att)->item(0)->nodeValue); + $this->assertSame('fiction1/fiction2', $xpath->query('AirSyncBase:ContentLocation', $att)->item(0)->nodeValue); + + // Test the empty Sync response + $request = preg_replace('|<SyncKey>\d</SyncKey>|', "<SyncKey>{$syncKey}</SyncKey>", $request); + + $response = $this->request($request, 'Sync'); + + // According to https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-ascmd/b4b366a5-7dfb-45a9-a256-af8fa7c53400 + // the empty response should look like this + // + // HTTP/1.1 200 OK + // Date: Fri, 10 Apr 2009 20:32:39 GMT + // Content-Length: 0 + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('', (string) $response->getBody()); + $this->assertSame('0', $response->getHeader('Content-Length')); return $syncKey; }
View file
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php
Changed
@@ -46,12 +46,8 @@ self::$deviceId = 'test' . str_replace('.', '', microtime(true)); $db->query('DELETE FROM syncroton_device'); - $db->query('DELETE FROM syncroton_synckey'); - $db->query('DELETE FROM syncroton_folder'); $db->query('DELETE FROM syncroton_data'); $db->query('DELETE FROM syncroton_data_folder'); - $db->query('DELETE FROM syncroton_content'); - $db->query('DELETE FROM syncroton_relations_state'); self::$client = new \GuzzleHttp\Client( 'http_errors' => false, @@ -76,19 +72,16 @@ { if (self::$deviceId) { $sync = \kolab_sync::get_instance(); - + /* if (self::$authenticated || $sync->authenticate(self::$username, self::$password)) { $sync->password = self::$password; - - $storage = $sync->storage(); - $storage->device_delete(self::$deviceId); } + */ $db = $sync->get_dbh(); $db->query('DELETE FROM syncroton_device'); - $db->query('DELETE FROM syncroton_synckey'); - $db->query('DELETE FROM syncroton_folder'); - $db->query('DELETE FROM syncroton_relations_state'); + $db->query('DELETE FROM syncroton_data'); + $db->query('DELETE FROM syncroton_data_folder'); } } @@ -224,21 +217,74 @@ /** * Create a folder */ - protected function createTestFolder($name, $type) + protected function createTestFolder($name, $type, $subscriptionState = '1') { // Create IMAP folders if ($type == 'mail' || $this->isStorageDriver('kolab')) { $imap = $this->getImapStorage(); - //TODO set type if not mail $imap->create_folder($name, true); + if ($type != 'mail' && $this->isStorageDriver('kolab')) { + $imap->set_metadata($name, '/private/vendor/kolab/folder-type' => $type); + } + $this->setSubscriptionState($name, $type, $subscriptionState); + } + } + + /** + * Subscribe a test folder + */ + public static function setSubscriptionState($name, $type, $subscriptionState, $deviceid = null) + { + $sync = \kolab_sync::get_instance(); + $db = $sync->get_dbh(); + $query = $db->query("SELECT `id` FROM `syncroton_device` WHERE `deviceid` = ?", $deviceid ?: self::$deviceId); + $arr = $db->fetch_array($query); + $device_id = $arr0 ?? null; + + if (!$device_id) { + throw new \Exception('Device ID not found'); + } + + $type, = explode('.', $type); + + $query = $db->query( + "SELECT `data` FROM `syncroton_subscriptions` WHERE `device_id` = ? AND `type` = ?", + $device_id, + $type + ); - $metadata = ; - $metadata'FOLDER' = ; - $metadata'FOLDER'self::$deviceId = ; - $metadata'FOLDER'self::$deviceId'S' = '1'; - $imap->set_metadata($name, '/private/vendor/kolab/activesync' => json_encode($metadata)); + if ($record = $db->fetch_assoc($query)) { + $data = json_decode($record'data', true); + } else { + if (!$subscriptionState) { + return; + } + } + if ($subscriptionState) { + $data$name = (int) $subscriptionState; + } elseif (!isset($data$name)) { return; + } else { + unset($data$name); + } + + $data = json_encode($data); + + if ($record) { + $db->query( + 'UPDATE syncroton_subscriptions SET `data` = ? WHERE `device_id` = ? AND `type` = ?', + $data, + $device_id, + $type + ); + } else { + $db->query( + 'INSERT INTO `syncroton_subscriptions` (`data`, `device_id`, `type`) VALUES (?, ?, ?)', + $data, + $device_id, + $type + ); } } @@ -278,6 +324,26 @@ } /** + * Get objects from a DAV folder + */ + protected function getDavObjects($foldername, $type, $query = ) + { + $dav = $this->getDavStorage(); + + foreach ($dav->get_folders($type) as $folder) { + if ($folder->get_name() === $foldername) { + $result = ; + foreach ($folder->select($query) as $object) { + $result = $object; + } + return $result; + } + } + + throw new \Exception("Folder not found"); + } + + /** * Initialize DAV storage */ protected function getDavStorage() @@ -294,7 +360,7 @@ // Make sure user is authenticated $this->getImapStorage(); - if (!empty($sync->user)) { + if ($sync->user) { // required e.g. for DAV client cache use \rcube::get_instance()->user = $sync->user; } @@ -383,11 +449,6 @@ { $sync = \kolab_sync::get_instance(); - if (self::$deviceId) { - $storage = $sync->storage(); - $storage->device_delete(self::$deviceId); - } - $db = $sync->get_dbh(); $db->query('DELETE FROM syncroton_device'); } @@ -418,6 +479,7 @@ $xpath->registerNamespace("AirSyncBase", "uri:AirSyncBase"); $xpath->registerNamespace("Calendar", "uri:Calendar"); $xpath->registerNamespace("Contacts", "uri:Contacts"); + $xpath->registerNamespace("ComposeMail", "uri:ComposeMail"); $xpath->registerNamespace("Email", "uri:Email"); $xpath->registerNamespace("Email2", "uri:Email2"); $xpath->registerNamespace("Settings", "uri:Settings"); @@ -443,7 +505,7 @@ if (method_exists("PHPUnit\Framework\TestCase", "assertMatchesRegularExpression")) { parent::assertMatchesRegularExpression($arg1, $arg2, $message); } else { - parent::assertRegExp($arg1, $arg2); + parent::assertRegExp($arg1, $arg2, $message); } } }
View file
kolab-syncroton-2.4.2.tar.gz/tests/bootstrap.php
Changed
@@ -13,3 +13,16 @@ require_once(TESTS_DIR . '/SyncTestCase.php'); rcube::get_instance()->config->set('devel_mode', false); + +// CLI (background) commands for Ping tests +if (!empty($argv) && $argv1 == 'subscribe') { + $args = rcube_utils::get_opt(); + + if (!empty($args'delay')) { + sleep($args'delay'); + } + + kolab_sync::get_instance()->authenticate($args'user', $args'password'); + + Tests\SyncTestCase::setSubscriptionState($args'folder', $args'type', 1, $args'device'); +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard1
Changed
@@ -1,6 +1,6 @@ BEGIN:VCARD VERSION:3.0 -UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabcdef +UID:abcdef-0123-4567-89ab-abcdefabcdef FN:Jane Doe N:Doe;Jane;J.;; END:VCARD
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard2
Changed
@@ -1,6 +1,7 @@ BEGIN:VCARD VERSION:3.0 -UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabc123 +UID:abcdef-0123-4567-89ab-abcdefabc123 FN:Jack Strong N:Strong;Jack;;; +EMAIL;TYPE=work:jack@kolab.org END:VCARD
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/mail.sync2
Changed
@@ -12,9 +12,25 @@ ZWVl --BOUNDARY +Content-Type: message/rfc822 +Content-Disposition: attachment; filename=message.eml + +Subject: Forwarded +From: kolab@domain.tld + +Forwarded message +--BOUNDARY +Content-Transfer-Encoding: base64 +Content-Type: image/gif; name=logo1.gif +Content-Disposition: attachment; filename=logo1.gif; size=2574 + +/9j/4AAQSkZJRgABAgEASABIAAD/4QqARXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUA +--BOUNDARY Content-Transfer-Encoding: base64 -Content-Type: image/jpeg; name=logo.gif -Content-Disposition: inline; filename=logo.gif; size=2574 +Content-Type: image/gif; name=logo2.gif +Content-Disposition: inline; filename=logo2.gif; size=2574 +Content-ID: <foo4foo1@bar.net> +Content-Location: fiction1/fiction2 /9j/4AAQSkZJRgABAgEASABIAAD/4QqARXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUA --BOUNDARY--
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 1:2.4.2.39-1~kolab1 +Version: 1:2.4.2.43-1~kolab1 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Homepage: http://www.kolab.org/
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.