/** * Receive, and parse, the incoming wbxml query. * * According to MS docs, OR is supported in the protocol, but will ALWAYS * return a searchToComplex status in Exchange 2007. Additionally, AND is * ONLY supported as the topmost element. No nested AND is allowed. All * such queries will return a searchToComplex status. * * @param boolean $subquery Parsing a subquery. * * @return array */ protected function _parseQuery($subquery = null) { $query = array(); while (($type = $this->_decoder->getElementStartTag(self::SEARCH_AND) ? self::SEARCH_AND : ($this->_decoder->getElementStartTag(self::SEARCH_OR) ? self::SEARCH_OR : ($this->_decoder->getElementStartTag(self::SEARCH_EQUALTO) ? self::SEARCH_EQUALTO : ($this->_decoder->getElementStartTag(self::SEARCH_LESSTHAN) ? self::SEARCH_LESSTHAN : ($this->_decoder->getElementStartTag(self::SEARCH_GREATERTHAN) ? self::SEARCH_GREATERTHAN : ($this->_decoder->getElementStartTag(self::SEARCH_FREETEXT) ? self::SEARCH_FREETEXT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID) ? Horde_ActiveSync::SYNC_FOLDERID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE) ? Horde_ActiveSync::SYNC_FOLDERTYPE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID) ? Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED) ? Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED : -1)))))))))) != -1) { switch ($type) { case self::SEARCH_AND: case self::SEARCH_OR: case self::SEARCH_EQUALTO: case self::SEARCH_LESSTHAN: case self::SEARCH_GREATERTHAN: $q = array('op' => $type, 'value' => $this->_parseQuery(true)); if ($subquery) { $query['subquery'][] = $q; } else { $query[] = $q; } $this->_decoder->getElementEndTag(); break; default: if ($query[$type] = $this->_decoder->getElementContent()) { if ($type == Horde_ActiveSync::SYNC_FOLDERID) { $query['serverid'] = $this->_collections->getBackendIdForFolderUid($query[$type]); } $this->_decoder->getElementEndTag(); } else { $this->_decoder->getElementStartTag(self::SEARCH_VALUE); $query[$type] = $this->_decoder->getElementContent(); switch ($type) { case Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED: $query[$type] = new Horde_Date($query[$type]); break; } $this->_decoder->getElementEndTag(); } break; } } return $query; }
/** * Handle incoming SYNC nodes * * @param array $collection The current collection array. * * @return boolean */ protected function _parseSyncCommands(&$collection) { // Some broken clients send SYNC_COMMANDS with a synckey of 0. // This is a violation of the spec, and could lead to all kinds // of data integrity issues. if (empty($collection['synckey'])) { $this->_logger->warn(sprintf('[%s] Attempting a SYNC_COMMANDS, but device failed to send synckey. Ignoring.', $this->_procid)); } if (empty($collection['class'])) { $collection['class'] = $this->_collections->getCollectionClass($collection['id']); } if (empty($collection['serverid'])) { try { $collection['serverid'] = $this->_collections->getBackendIdForFolderUid($collection['id']); } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED; $this->_handleError($colleciton); return false; } } try { $this->_collections->initCollectionState($collection); } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->warn(sprintf('[%s] State not found sending STATUS_KEYMISM', $this->_procid)); $this->_statusCode = self::STATUS_KEYMISM; $this->_handleError($collection); return false; } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->notice($e->getMessage()); $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return false; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return false; } // Configure importer with last state if (!empty($collection['synckey'])) { $importer = $this->_activeSync->getImporter(); $importer->init($this->_state, $collection['id'], $collection['conflict']); } $nchanges = 0; while (1) { // SYNC_MODIFY, SYNC_REMOVE, SYNC_ADD or SYNC_FETCH $element = $this->_decoder->getElement(); if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { $this->_decoder->_ungetElement($element); break; } $nchanges++; // Only sent during SYNC_MODIFY/SYNC_REMOVE/SYNC_FETCH if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_MODIFY || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_REMOVE || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_FETCH) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SERVERENTRYID)) { $serverid = $this->_decoder->getElementContent(); // Work around broken clients (Blackberry) that can send empty // $serverid values as a single empty <SYNC_SERVERENTRYID /> tag. if ($serverid !== false && !$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting </SYNC_SERVERENTRYID>'); return false; } } else { $serverid = false; } // This tag is only sent here during > 12.1 and SYNC_ADD requests... // and it's not even sent by all clients. Parse it if it's there, // ignore it if not. if ($this->_activeSync->device->version > Horde_ActiveSync::VERSION_TWELVEONE && $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) { $collection['class'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting </SYNC_FOLDERTYPE>'); return false; } } // Only sent during SYNC_ADD if ($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CLIENTENTRYID)) { $clientid = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting </SYNC_CLIENTENTRYID>'); return false; } } else { $clientid = false; } // Create Message object from messages passed from client. // Only passed during SYNC_ADD or SYNC_MODIFY if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_MODIFY) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA)) { switch ($collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: $appdata = Horde_ActiveSync::messageFactory('Mail'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_CONTACTS: $appdata = Horde_ActiveSync::messageFactory('Contact'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_CALENDAR: $appdata = Horde_ActiveSync::messageFactory('Appointment'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_TASKS: $appdata = Horde_ActiveSync::messageFactory('Task'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_NOTES: $appdata = Horde_ActiveSync::messageFactory('Note'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_SMS: $appdata = Horde_ActiveSync::messageFactory('Mail'); $appdata->decodeStream($this->_decoder); break; } if (!$this->_decoder->getElementEndTag()) { // End application data $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); return false; } } if (!empty($collection['synckey'])) { switch ($element[Horde_ActiveSync_Wbxml::EN_TAG]) { case Horde_ActiveSync::SYNC_MODIFY: if (isset($appdata)) { $id = $importer->importMessageChange($serverid, $appdata, $this->_device, false); if ($id && !is_array($id)) { $collection['importedchanges'] = true; } elseif (is_array($id)) { $collection['importfailures'][$id[0]] = $id[1]; } } break; case Horde_ActiveSync::SYNC_ADD: if (isset($appdata)) { $id = $importer->importMessageChange(false, $appdata, $this->_device, $clientid, $collection['class']); if ($clientid && $id && !is_array($id)) { $collection['clientids'][$clientid] = $id; $collection['importedchanges'] = true; } elseif (!$id || is_array($id)) { $collection['clientids'][$clientid] = false; } } break; case Horde_ActiveSync::SYNC_REMOVE: // Work around broken clients that send empty $serverid. if ($serverid) { $collection['removes'][] = $serverid; } break; case Horde_ActiveSync::SYNC_FETCH: $collection['fetchids'][] = $serverid; break; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing error'); return false; } } // Do all the SYNC_REMOVE requests at once if (!empty($collection['removes']) && !empty($collection['synckey'])) { if (!empty($collection['deletesasmoves']) && ($folderid = $this->_driver->getWasteBasket($collection['class']))) { $results = $importer->importMessageMove($collection['removes'], $folderid); } else { $results = $importer->importMessageDeletion($collection['removes'], $collection['class']); if (is_array($results)) { $results['results'] = $results; $results['missing'] = array_diff($collection['removes'], $results['results']); } } if (!empty($results['missing'])) { $collection['missing'] = $results['missing']; } unset($collection['removes']); $collection['importedchanges'] = true; } $this->_logger->info(sprintf('[%s] Processed %d incoming changes', $this->_procid, $nchanges)); if (!$this->_decoder->getElementEndTag()) { // end commands $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('PARSING ERROR'); return false; } return true; }