diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php --- a/lib/kolab_sync_data.php +++ b/lib/kolab_sync_data.php @@ -484,7 +484,7 @@ throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - return $entry['uid']; + return $entry['_serverId']; } /** @@ -505,13 +505,13 @@ } $entry = $this->toKolab($entry, $folderId, $oldEntry); - $entry = $this->updateObject($folderId, $serverId, $entry); + $entry = $this->updateObject($folderId, $serverId, $entry); if (empty($entry)) { throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - return $entry['uid']; + return $entry['_serverId']; } /** @@ -602,8 +602,8 @@ if (!is_array($uids)) { $error = true; } - else { - $result = array_merge($result, $uids); + else if (!empty($uids)) { + $result = array_merge($result, $this->applyServerId($uids, $folder)); } break; } @@ -641,8 +641,8 @@ case self::RESULT_UID: $uids = $folder->get_uids($tag_filter); - if (is_array($uids)) { - $result = array_unique(array_merge($result, $uids)); + if (is_array($uids) && !empty($uids)) { + $result = array_unique(array_merge($result, $this->applyServerId($uids, $folder))); } break; @@ -944,10 +944,37 @@ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); $folder = $this->getFolderObject($foldername); - if ($folder && $folder->valid && ($object = $folder->get_object($entryid))) { - $object['_folderid'] = $folderid; + if ($folder && $folder->valid) { + $crc = null; + $uid = $entryid; + + // See self::serverId() for full explanation + // Use (slower) UID prefix matching... + if (preg_match('/^CRC([0-9A-Fa-f]{8})(.+)$/', $uid, $matches)) { + $crc = $matches[1]; + $uid = $matches[2]; + + if (strlen($entryid) >= 64) { + foreach ($folder->select(array(array('uid', '~*', $uid))) as $object) { + if (($object['uid'] == $uid || strpos($object['uid'], $uid) === 0) + && $crc == $this->objectCRC($object['uid'], $folder) + ) { + $object['_folderid'] = $folderid; + return $object; + } + } + + continue; + } + } - return $object; + // Or (faster) strict UID matching... + if (($object = $folder->get_object($uid)) + && ($crc === null || $crc == $this->objectCRC($object['uid'], $folder)) + ) { + $object['_folderid'] = $folderid; + return $object; + } } } } @@ -981,6 +1008,8 @@ $this->setKolabTags($data['uid'], $tags); } + $data['_serverId'] = $this->serverId($data['uid'], $folder); + return $data; } } @@ -1006,6 +1035,8 @@ $this->setKolabTags($data['uid'], $tags); } + $data['_serverId'] = $this->serverId($object['uid'], $folder); + return $data; } } @@ -1021,7 +1052,7 @@ if ($object) { $folder = $this->getFolderObject($object['_mailbox']); - if ($folder && $folder->valid && $folder->delete($entryid)) { + if ($folder && $folder->valid && $folder->delete($object['uid'])) { if ($this->tag_categories) { $this->setKolabTags($object['uid'], null); } @@ -1848,4 +1879,65 @@ return false; } + + /** + * Generate CRC-based ServerId from object UID + */ + protected function serverId($uid, $folder) + { + if ($this->modelName == 'mail') { + return $uid; + } + + // When ActiveSync communicates with the client, it refers to objects with a ServerId + // We can't use object UID for ServerId because: + // - ServerId is limited to 64 chars, + // - there can be multiple calendars with a copy of the same event. + // + // The solution is to; Take the original UID, and regardless of its length, execute the following: + // - Hash the UID concatenated with the Folder ID using CRC32b, + // - Prefix the UID with 'CRC' and the hash string, + // - Tryncate the result to 64 characters. + // + // Searching for the server-side copy of the object now follows the logic; + // - If the ServerId is prefixed with 'CRC', strip off the first 11 characters + // and we search for the UID using the remainder; + // - if the UID is shorter than 53 characters, it'll be the complete UID, + // - if the UID is longer than 53 characters, it'll be the truncated UID, + // and we search for a wildcard match of * + // When multiple copies of the same event are found, the same CRC32b hash can be used + // on the events metadata (i.e. the copy's UID and Folder ID), and compared with the CRC from the ServerId. + + // ServerId is max. 64 characters, below we generate a string of max. 64 chars + // Note: crc32b is always 8 characters + return 'CRC' . $this->objectCRC($uid, $folder) . substr($uid, 0, 53); + } + + /** + * Calculate checksum on object UID and folder UID + */ + protected function objectCRC($uid, $folder) + { + if (!is_object($folder)) { + $folder = $this->getFolderObject($folder); + } + + $folder_uid = $folder->get_uid(); + + return strtoupper(hash('crc32b', $folder_uid . $uid)); // always 8 chars + } + + /** + * Apply serverId() on a set of uids + */ + protected function applyServerId($uids, $folder) + { + if (!empty($uids) && $this->modelName != 'mail') { + $self = $this; + $func = function($uid) use ($self, $folder) { return $self->serverId($uid, $folder); }; + $uids = array_map($func, $uids); + } + + return $uids; + } } diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php --- a/lib/kolab_sync_data_calendar.php +++ b/lib/kolab_sync_data_calendar.php @@ -413,14 +413,6 @@ continue 2; } break; - - case 'uid': - // If UID is too long, use auto-generated UID (#1034) - // It's because UID is used as ServerId which cannot be longer than 64 chars - if (strlen($value) > 64) { - $value = null; - } - break; } $this->setKolabDataItem($event, $name, $value);