Пример #1
0
 /**
  * Import a message change from the wbxml stream
  *
  * @param string|boolean $id                       A server message id or
  *                                                 false if a new message.
  * @param Horde_ActiveSync_Message_Base $message   A message object
  * @param Horde_ActiveSync_Device $device          A device descriptor
  * @param integer $clientid                        Client id sent from PIM
  *                                                 on message addition.
  * @param string $class   The collection class (only needed for SMS).
  *                        @since 2.6.0
  *
  * @todo Revisit passing $class for SMS. Probably pass class in the
  *       const'r.
  *
  * @return string|array|boolean The server message id, an array containing
  *                              the serverid and failure code, or false
  */
 public function importMessageChange($id, Horde_ActiveSync_Message_Base $message, Horde_ActiveSync_Device $device, $clientid, $class = null)
 {
     // Don't support SMS, but can't tell client that. Send back a phoney
     // UID for any imported SMS objects.
     if ($class == Horde_ActiveSync::CLASS_SMS || strpos($id, 'IGNORESMS_') === 0) {
         return 'IGNORESMS_' . $clientid;
     }
     // Changing an existing object
     if ($id && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) {
         $conflict = $this->_isConflict(Horde_ActiveSync::CHANGE_TYPE_CHANGE, $this->_folderId, $id);
         if ($conflict) {
             $this->_logger->notice(sprintf('[%s] Conflict when updating %s, will overwrite client version on next sync.', $this->_procid, $id));
             return array($id, Horde_ActiveSync_Request_Sync::STATUS_CONFLICT);
         }
     } elseif (!$id && ($uid = $this->_state->isDuplicatePIMAddition($clientid))) {
         // Already saw this addition, but PIM never received UID
         $this->_logger->notice(sprintf('[%s] Duplicate addition for %s', $this->_procid, $uid));
         return $uid;
     }
     // Tell the backend about the change
     if (!($stat = $this->_as->driver->changeMessage($this->_folderId, $id, $message, $device))) {
         $this->_logger->err(sprintf('[%s] Change message failed when updating %s', $this->_procid, $id));
         return $id ? array($id, Horde_ActiveSync_Request_Sync::STATUS_NOTFOUND) : array(false, Horde_ActiveSync_Request_Sync::STATUS_SERVERERROR);
     }
     $stat['serverid'] = $this->_folderId;
     // Record the state of the message, but only if we aren't updating
     // categories. @todo This should be fixed, but for now we can't
     // differentiate between different flag changes. Note that categories
     // only exists for email changes so for non email this will still
     // work as before.
     if (!array_key_exists('categories', $stat) || $stat['categories'] === false) {
         $this->_state->updateState($message instanceof Horde_ActiveSync_Message_Mail ? Horde_ActiveSync::CHANGE_TYPE_FLAGS : Horde_ActiveSync::CHANGE_TYPE_CHANGE, $stat, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser(), $clientid);
     }
     return $stat['id'];
 }
Пример #2
0
 /**
  * Import a message change from the wbxml stream
  *
  * @param string|boolean $id                       A server message id or
  *                                                 false if a new message.
  * @param Horde_ActiveSync_Message_Base $message   A message object
  * @param Horde_ActiveSync_Device $device          A device descriptor
  * @param integer $clientid                        Client id sent from client.
  *                                                 on message addition.
  * @param string $class    The collection class - needed for SMS since the
  *                         actual serverid will be for an email folder.
  *                         @since 2.6.0
  * @param string $synckey  The synckey currently being processed when
  *                         processing a SYNC_MODIFY command.
  *                         @since  2.31.0
  *
  * @todo Revisit passing $class for SMS. Probably pass class in the
  *       const'r.
  *
  * @return array|boolean  A stat array, or an array containing the 'error'
  *                        key on error, or false on duplicate addition.
  */
 public function importMessageChange($id, Horde_ActiveSync_Message_Base $message, Horde_ActiveSync_Device $device, $clientid, $class = false, $synckey = false)
 {
     // Don't support SMS, but can't tell client that. Send back a phoney
     // UID for any imported SMS objects.
     if ($class == Horde_ActiveSync::CLASS_SMS || strpos($id, 'IGNORESMS_') === 0) {
         return 'IGNORESMS_' . $clientid;
     }
     // Changing an existing object
     if ($id && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) {
         // This is complicated by the fact that in EAS 16.0, clients
         // will send a CHANGE for adding/editing an exception along with
         // a seperate change with the entire appointment - even if nothing
         // else has changed. This leads to conficts (since the appointment
         // is marked as changed after the first edit). Sniff that out here
         // and prevent the conflict check for those messages.
         if (!$message instanceof Horde_ActiveSync_Message_Appointment && $this->_state->isDuplicatePIMChange($id, $synckey)) {
             $conflict = $this->_isConflict(Horde_ActiveSync::CHANGE_TYPE_CHANGE, $this->_folderId, $id);
             if ($conflict) {
                 $this->_logger->notice(sprintf('[%s] Conflict when updating %s, will overwrite client version on next sync.', $this->_procid, $id));
                 return array($id, 'error' => array(Horde_ActiveSync_Request_Sync::STATUS_CONFLICT));
             }
         }
     } elseif (!$id && ($uid = $this->_state->isDuplicatePIMAddition($clientid))) {
         // Already saw this addition, but client never received UID
         $this->_logger->notice(sprintf('[%s] Duplicate addition for %s', $this->_procid, $uid));
         return $uid;
     }
     // Set the supported/ghosted data if this is a SYNC_MODIFY.
     if ($id && !empty($device->supported[$class])) {
         $message->setSupported($device->supported[$class]);
     }
     // Tell the backend about the change
     if (!($stat = $this->_as->driver->changeMessage($this->_folderId, $id, $message, $device))) {
         $this->_logger->err(sprintf('[%s] Change message failed when updating %s', $this->_procid, $id));
         return $id ? array(0 => $id, 'error' => Horde_ActiveSync_Request_Sync::STATUS_NOTFOUND) : array(0 => false, 'error' => Horde_ActiveSync_Request_Sync::STATUS_SERVERERROR);
     }
     $stat['serverid'] = $this->_folderId;
     // Record the state of the message.
     // Email messages are only changed if they are Drafts or if we are
     // updating flags.
     // When CHANGING a draft message, we are actually deleting the old one
     // and replacing it with a message (since we can't edit an existing
     // IMAP message while keeping the UID the same). So, do not call
     // updateState() for these messages since we don't want to ignore
     // this as a PIM sourced change - the change will be caught during
     // normal ping/sync cycle.
     if ($message instanceof Horde_ActiveSync_Message_Mail) {
         if (!empty($message->airsyncbasebody) && !empty($id)) {
             // Changing an existing Draft mail.
             return $stat;
         }
         // Either a flag change, or adding a new Draft mail.
         $changeType = !empty($message->airsyncbasebody) ? Horde_ActiveSync::CHANGE_TYPE_DRAFT : Horde_ActiveSync::CHANGE_TYPE_FLAGS;
     } else {
         $changeType = Horde_ActiveSync::CHANGE_TYPE_CHANGE;
     }
     $this->_state->updateState($changeType, $stat, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser(), $clientid);
     return $stat;
 }
Пример #3
0
 /**
  * Load and initialize the sync state
  *
  * @param array $collection  The collection array for the collection, if
  *                           a FOLDERSYNC, pass an empty array.
  * @param string $syncKey    The synckey of the state to load. If empty will
  *                           force a reset of the state for the class
  *                           specified in $id
  * @param string $type       The type of state a
  *                           Horde_ActiveSync::REQUEST_TYPE constant.
  * @param string $id         The folder id this state represents. If empty
  *                           assumed to be a foldersync state.
  *
  * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StateGone
  */
 public function loadState(array $collection, $syncKey, $type = null, $id = null)
 {
     // Initialize the local members.
     $this->_collection = $collection;
     $this->_changes = null;
     $this->_type = $type;
     // If this is a FOLDERSYNC, mock the device id.
     if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC && empty($id)) {
         $id = Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC;
     }
     // synckey == 0 is an initial sync or reset.
     if (empty($syncKey)) {
         $this->_logger->notice(sprintf('[%s] %s::loadState: clearing folder state.', $this->_procid, __CLASS__));
         if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) {
             $this->_folder = array();
         } else {
             // Create a new folder object.
             $this->_folder = $this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL ? new Horde_ActiveSync_Folder_Imap($this->_collection['serverid'], Horde_ActiveSync::CLASS_EMAIL) : ($this->_collection['serverid'] == 'RI' ? new Horde_ActiveSync_Folder_RI('RI', 'RI') : new Horde_ActiveSync_Folder_Collection($this->_collection['serverid'], $this->_collection['class']));
         }
         $this->_syncKey = '0';
         $this->_resetDeviceState($id);
         return;
     }
     $this->_logger->info(sprintf('[%s] Loading state for synckey %s', $this->_procid, $syncKey));
     // Check if synckey is allowed
     if (!preg_match('/^\\{([0-9A-Za-z-]+)\\}([0-9]+)$/', $syncKey, $matches)) {
         throw new Horde_ActiveSync_Exception('Invalid sync key');
     }
     $this->_syncKey = $syncKey;
     // Cleanup older syncstates
     $this->_gc($syncKey);
     // Load the state
     $this->_loadState($type);
 }
Пример #4
0
 /**
  * Authenticate to the backend.
  *
  * @param Horde_ActiveSync_Credentials $credentials  The credentials object.
  *
  * @return boolean  True on successful authentication to the backend.
  * @throws Horde_ActiveSync_Exception
  */
 public function authenticate(Horde_ActiveSync_Credentials $credentials)
 {
     if (!$credentials->username) {
         // No provided username or Authorization header.
         self::$_logger->notice(sprintf('[%s] Client did not provide authentication data.', $this->_procid));
         return false;
     }
     $user = $this->_driver->getUsernameFromEmail($credentials->username);
     $pos = strrpos($user, '\\');
     if ($pos !== false) {
         $domain = substr($user, 0, $pos);
         $user = substr($user, $pos + 1);
     } else {
         $domain = null;
     }
     // Authenticate
     if ($result = $this->_driver->authenticate($user, $credentials->password, $domain)) {
         if ($result === self::AUTH_REASON_USER_DENIED) {
             $this->_globalError = Horde_ActiveSync_Status::SYNC_NOT_ALLOWED;
         } elseif ($result === self::AUTH_REASON_DEVICE_DENIED) {
             $this->_globalError = Horde_ActiveSync_Status::DEVICE_BLOCKED_FOR_USER;
         } elseif ($result !== true) {
             $this->_globalError = Horde_ActiveSync_Status::DENIED;
         }
     } else {
         return false;
     }
     if (!$this->_driver->setup($user)) {
         return false;
     }
     return true;
 }
Пример #5
0
 /**
  * Poll the backend for changes.
  *
  * @param integer $heartbeat  The heartbeat lifetime to wait for changes.
  * @param integer $interval   The wait interval between poll iterations.
  * @param array $options      An options array containing any of:
  *   - pingable: (boolean)  Only poll collections with the pingable flag set.
  *                DEFAULT: false
  *
  * @return boolean|integer True if changes were detected in any of the
  *                         collections, false if no changes detected
  *                         or a status code if failed.
  */
 public function pollForChanges($heartbeat, $interval, array $options = array())
 {
     $dataavailable = false;
     $started = time();
     $until = $started + $heartbeat;
     $this->_logger->info(sprintf('Waiting for changes for %s seconds', $heartbeat));
     // If pinging, make sure we have pingable collections. Note we can't
     // filter on them here because the collections might change during the
     // loop below.
     if (!empty($options['pingable']) && !$this->havePingableCollections()) {
         $this->_logger->err('No pingable collections.');
         return self::COLLECTION_ERR_SERVER;
     }
     // Need to update AND SAVE the timestamp for race conditions to be
     // detected.
     $this->lasthbsyncstarted = $started;
     $this->save();
     // We only check for remote wipe request once every 5 iterations to
     // save on DB load since we must reload the device's state each time.
     $rw_check_countdown = 5;
     while (($now = time()) < $until) {
         // Try not to go over the heartbeat interval.
         if ($until - $now < $interval) {
             $interval = $until - $now;
         }
         // See if another process has altered the sync_cache.
         if ($this->checkStaleRequest()) {
             return self::COLLECTION_ERR_STALE;
         }
         // Make sure the collections are still there (there might have been
         // an error in refreshing them from the cache). Ideally this should
         // NEVER happen.
         if (!count($this->_collections)) {
             $this->_logger->err('NO COLLECTIONS! This should not happen!');
             return self::COLLECTION_ERR_SERVER;
         }
         // Check for WIPE request once every 5 iterations to balance between
         // performance and speed of catching a remote wipe request.
         if ($rw_check_countdown-- == 0) {
             $rw_check_countdown = 5;
             if ($this->_as->provisioning != Horde_ActiveSync::PROVISIONING_NONE) {
                 $rwstatus = $this->_as->state->getDeviceRWStatus($this->_as->device->id, true);
                 if ($rwstatus == Horde_ActiveSync::RWSTATUS_PENDING || $rwstatus == Horde_ActiveSync::RWSTATUS_WIPED) {
                     return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED;
                 }
             }
         }
         // Check each collection we are interested in.
         foreach ($this->_collections as $id => $collection) {
             // Initialize the collection's state data in the state handler.
             try {
                 $this->initCollectionState($collection, true);
             } catch (Horde_ActiveSync_Exception_StateGone $e) {
                 $this->_logger->notice(sprintf('[%s] State not found for %s. Continuing.', $this->_procid, $id));
                 if (!empty($options['pingable'])) {
                     return self::COLLECTION_ERR_PING_NEED_FULL;
                 }
                 $dataavailable = true;
                 $this->setGetChangesFlag($id);
                 continue;
             } catch (Horde_ActiveSync_Exception_InvalidRequest $e) {
                 // Thrown when state is unable to be initialized because the
                 // collection has not yet been synched, but was requested to
                 // be pinged.
                 $this->_logger->err(sprintf('[%s] Unable to initialize state for %s. Ignoring during pollForChanges: %s.', $this->_procid, $id, $e->getMessage()));
                 continue;
             } catch (Horde_ActiveSync_Exception_FolderGone $e) {
                 $this->_logger->warn('Folder gone for collection ' . $collection['id']);
                 return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED;
             } catch (Horde_ActiveSync_Exception $e) {
                 $this->_logger->err('Error loading state: ' . $e->getMessage());
                 $this->_as->state->loadState(array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id);
                 $this->setGetChangesFlag($id);
                 $dataavailable = true;
                 continue;
             }
             if (!empty($options['pingable']) && !$this->_cache->collectionIsPingable($id)) {
                 $this->_logger->notice(sprintf('[%s] Skipping %s because it is not PINGable.', $this->_procid, $id));
                 continue;
             }
             try {
                 if ($cnt = $this->getCollectionChangeCount(true)) {
                     $dataavailable = true;
                     $this->setGetChangesFlag($id);
                     if (!empty($options['pingable'])) {
                         $this->_cache->setPingChangeFlag($id);
                     }
                 } else {
                     try {
                         $this->_as->state->updateSyncStamp();
                     } catch (Horde_ActiveSync_Exception $e) {
                         $this->_logger->err($e->getMessage());
                     }
                 }
             } catch (Horde_ActiveSync_Exception_StaleState $e) {
                 $this->_logger->notice(sprintf('[%s] SYNC terminating and force-clearing device state: %s', $this->_procid, $e->getMessage()));
                 $this->_as->state->loadState(array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id);
                 $this->setGetChangesFlag($id);
                 $dataavailable = true;
             } catch (Horde_ActiveSync_Exception_FolderGone $e) {
                 $this->_logger->notice(sprintf('[%s] SYNC terminating: %s', $this->_procid, $e->getMessage()));
                 // If we are missing a folder, we should clear the PING
                 // cache also, to be sure it picks up any hierarchy changes
                 // since most clients don't seem smart enough to figure this
                 // out on their own.
                 $this->resetPingCache();
                 return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED;
             } catch (Horde_Exception_AuthenticationFailure $e) {
                 // We lost authentication for some reason.
                 $this->_logger->err(sprintf('[%s] Authentication lost during PING!!', $this->_procid));
                 return self::COLLECTION_ERR_AUTHENTICATION;
             } catch (Horde_ActiveSync_Exception $e) {
                 $this->_logger->err(sprintf('[%s] Sync object cannot be configured, throttling: %s', $this->_procid, $e->getMessage()));
                 $this->_sleep(30);
                 continue;
             }
         }
         if (!empty($dataavailable)) {
             $this->_logger->info(sprintf('[%s] Found changes!', $this->_procid));
             break;
         }
         // Wait a bit...
         $this->_sleep($interval);
         // Refresh the collections.
         $this->updateCollectionsFromCache();
     }
     // Check that no other Sync process already started
     // If so, we exit here and let the other process do the export.
     if ($this->checkStaleRequest()) {
         $this->_logger->info('Changes in cache determined during Sync Wait/Heartbeat, exiting here.');
         return self::COLLECTION_ERR_STALE;
     }
     $this->_logger->info(sprintf('[%s] Looping Sync complete: DataAvailable: %s, DataImported: %s', $this->_procid, $dataavailable, $this->importedChanges));
     return $dataavailable;
 }
Пример #6
0
 /**
  * Recursively decodes the WBXML from input stream. This means that if this
  * message contains complex types (like Appointment.Recuurence for example)
  * the sub-objects are auto-instantiated and decoded as well. Places the
  * decoded objects in the local properties array.
  *
  * @param Horde_ActiveSync_Wbxml_Decoder  The stream decoder
  *
  * @throws Horde_ActiveSync_Exception
  */
 public function decodeStream(Horde_ActiveSync_Wbxml_Decoder &$decoder)
 {
     while (1) {
         $entity = $decoder->getElement();
         if ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
             if (!($entity[Horde_ActiveSync_Wbxml::EN_FLAGS] & Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT)) {
                 $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
                 if (!isset($map[self::KEY_TYPE])) {
                     $this->{$map[self::KEY_ATTRIBUTE]} = '';
                 } elseif ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES) {
                     $this->{$map[self::KEY_ATTRIBUTE]} = '';
                 }
                 continue;
             }
             // Found start tag
             if (!isset($this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]])) {
                 $this->_logger->err(sprintf('Tag %s unexpected in type XML type %s.', $entity[Horde_ActiveSync_Wbxml::EN_TAG], get_class($this)));
                 throw new Horde_ActiveSync_Exception('Unexpected tag');
             } else {
                 $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
                 if (isset($map[self::KEY_VALUES])) {
                     // Handle arrays of attribute values
                     while (1) {
                         // If we can have multiple types of objects in this
                         // container, or we are parsing a NO_CONTAINER,
                         // check that we are not at the end tag of the
                         // or we have a valid start tag for the NO_CONTAINER
                         // object. If not, break out of loop.
                         if (is_array($map[self::KEY_VALUES])) {
                             $token = $decoder->peek();
                             if ($token[Horde_ActiveSync_Wbxml_Decoder::EN_TYPE] == Horde_ActiveSync_Wbxml_Decoder::EN_TYPE_ENDTAG) {
                                 break;
                             }
                         } elseif (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) && !$decoder->getElementStartTag($map[self::KEY_VALUES])) {
                             break;
                         }
                         // We know we have some valid value, parse out what
                         // it is. Either an array of (possibly varied)
                         // objects, a single object, or simple value.
                         if (is_array($map[self::KEY_VALUES])) {
                             $token = $decoder->getToken();
                             if (($idx = array_search($token[Horde_ActiveSync_Wbxml_Decoder::EN_TAG], $map[self::KEY_VALUES])) !== false) {
                                 $class = $map[self::KEY_TYPE][$idx];
                                 $decoded = new $class(array('protocolversion' => $this->_version, 'logger' => $this->_logger));
                                 $decoded->commandType = $this->commandType;
                                 $decoded->decodeStream($decoder);
                             } else {
                                 throw new Horde_ActiveSync_Exception('Error in message map configuration');
                             }
                         } elseif (isset($map[self::KEY_TYPE])) {
                             $class = $map[self::KEY_TYPE];
                             $decoded = new $class(array('protocolversion' => $this->_version, 'logger' => $this->_logger));
                             $decoded->commandType = $this->commandType;
                             $decoded->decodeStream($decoder);
                         } else {
                             $decoded = $decoder->getElementContent();
                         }
                         // Assign the parsed value to the mapped attribute.
                         if (!isset($this->{$map[self::KEY_ATTRIBUTE]})) {
                             $this->{$map[self::KEY_ATTRIBUTE]} = array($decoded);
                         } else {
                             $this->{$map[self::KEY_ATTRIBUTE]}[] = $decoded;
                         }
                         // Get the end tag of this attribute node.
                         if (!$decoder->getElementEndTag()) {
                             throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
                         }
                         // For NO_CONTAINER attributes, need some magic to
                         // make sure we break out properly.
                         if (isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) {
                             $e = $decoder->peek();
                             // Go back to the initial while if another block
                             // of a non-container element is found.
                             if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
                                 continue 2;
                             }
                             // Break on end tag because no other container
                             // elements block end is reached.
                             if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG || empty($e)) {
                                 break;
                             }
                         }
                     }
                     // Do not get container end tag for an array without a container
                     if (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) && !$decoder->getElementEndTag()) {
                         return false;
                     }
                 } else {
                     // Handle a simple attribute value
                     if (isset($map[self::KEY_TYPE])) {
                         if (in_array($map[self::KEY_TYPE], array(self::TYPE_DATE, self::TYPE_DATE_DASHES, self::TYPE_DATE_LOCAL))) {
                             $decoded = $this->_parseDate($decoder->getElementContent());
                         } elseif ($map[self::KEY_TYPE] == self::TYPE_HEX) {
                             $decoded = self::_hex2bin($decoder->getElementContent());
                         } else {
                             // Complex type, decode recursively
                             $class = $map[self::KEY_TYPE];
                             $subdecoder = new $class(array('protocolversion' => $this->_version, 'logger' => $this->_logger));
                             $subdecoder->commandType = $this->commandType;
                             $subdecoder->decodeStream($decoder);
                             $decoded = $subdecoder;
                         }
                     } else {
                         // Simple type, just get content
                         $decoded = $decoder->getElementContent();
                         if ($decoded === false) {
                             $decoded = '';
                             $this->_logger->notice(sprintf('Unable to get expected content for %s: Setting to an empty string.', $entity[Horde_ActiveSync_Wbxml::EN_TAG]));
                         }
                     }
                     if (!$decoder->getElementEndTag()) {
                         $this->_logger->err(sprintf('Unable to get end tag for %s.', $entity[Horde_ActiveSync_Wbxml::EN_TAG]));
                         throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
                     }
                     $this->{$map[self::KEY_ATTRIBUTE]} = $decoded;
                 }
             }
         } elseif ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
             $decoder->_ungetElement($entity);
             break;
         } else {
             $this->_logger->err('Unexpected content in type');
             break;
         }
     }
     if (!$this->_validateDecodedValues()) {
         throw new Horde_ActiveSync_Exception(sprintf('Invalid values detected in %s.', get_class($this)));
     }
 }
Пример #7
0
 /**
  * Recursively decodes the WBXML from input stream. This means that if this
  * message contains complex types (like Appointment.Recuurence for example)
  * the sub-objects are auto-instantiated and decoded as well. Places the
  * decoded objects in the local properties array.
  *
  * @param Horde_ActiveSync_Wbxml_Decoder  The stream decoder
  *
  * @throws Horde_ActiveSync_Exception
  */
 public function decodeStream(Horde_ActiveSync_Wbxml_Decoder &$decoder)
 {
     while (1) {
         $entity = $decoder->getElement();
         if ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
             if (!($entity[Horde_ActiveSync_Wbxml::EN_FLAGS] & Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT)) {
                 $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
                 if (!isset($map[self::KEY_TYPE])) {
                     $this->{$map[self::KEY_ATTRIBUTE]} = '';
                 } elseif ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES) {
                     $this->{$map[self::KEY_ATTRIBUTE]} = '';
                 }
                 continue;
             }
             // Found start tag
             if (!isset($this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]])) {
                 $this->_logger->err('Tag ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG] . ' unexpected in type XML type ' . get_class($this));
                 throw new Horde_ActiveSync_Exception('Unexpected tag');
             } else {
                 $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]];
                 if (isset($map[self::KEY_VALUES])) {
                     // Handle arrays of attribute values
                     while (1) {
                         //do not get start tag for an array without a container
                         if (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER)) {
                             if (!$decoder->getElementStartTag($map[self::KEY_VALUES])) {
                                 break;
                             }
                         }
                         if (isset($map[self::KEY_TYPE])) {
                             $class = $map[self::KEY_TYPE];
                             $decoded = new $class(array('protocolversion' => $this->_version, 'logger' => $this->_logger));
                             $decoded->decodeStream($decoder);
                         } else {
                             $decoded = $decoder->getElementContent();
                         }
                         if (!isset($this->{$map[self::KEY_ATTRIBUTE]})) {
                             $this->{$map[self::KEY_ATTRIBUTE]} = array($decoded);
                         } else {
                             $this->{$map[self::KEY_ATTRIBUTE]}[] = $decoded;
                         }
                         if (!$decoder->getElementEndTag()) {
                             throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
                         }
                         if (isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) {
                             $e = $decoder->peek();
                             //go back to the initial while if another block of no container elements is found
                             if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
                                 continue 2;
                             }
                             //break on end tag because no container elements block end is reached
                             if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
                                 break;
                             }
                             if (empty($e)) {
                                 break;
                             }
                         }
                     }
                     //do not get container end tag for an array without a container
                     if (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER)) {
                         if (!$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 } else {
                     // Handle a simple attribute value
                     if (isset($map[self::KEY_TYPE])) {
                         // Complex type, decode recursively
                         if ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES) {
                             $decoded = $this->_parseDate($decoder->getElementContent());
                         } elseif ($map[self::KEY_TYPE] == self::TYPE_HEX) {
                             $decoded = self::hex2bin($decoder->getElementContent());
                         } else {
                             $class = $map[self::KEY_TYPE];
                             $subdecoder = new $class(array('protocolversion' => $this->_version, 'logger' => $this->_logger));
                             $subdecoder->decodeStream($decoder);
                             $decoded = $subdecoder;
                         }
                     } else {
                         // Simple type, just get content
                         $decoded = $decoder->getElementContent();
                         if ($decoded === false) {
                             $decoded = '';
                             $this->_logger->notice('Unable to get expected content for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG] . ': Setting to an empty string.');
                         }
                     }
                     if (!$decoder->getElementEndTag()) {
                         $this->_logger->err('Unable to get end tag for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]);
                         throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag');
                     }
                     $this->{$map[self::KEY_ATTRIBUTE]} = $decoded;
                 }
             }
         } elseif ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
             $decoder->_ungetElement($entity);
             break;
         } else {
             $this->_logger->err('Unexpected content in type');
             break;
         }
     }
 }