diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php index 4f5c648..a60a02c 100644 --- a/lib/ext/Syncroton/Command/ItemOperations.php +++ b/lib/ext/Syncroton/Command/ItemOperations.php @@ -1,306 +1,306 @@ */ /** * class to handle ActiveSync ItemOperations command * * @package Syncroton * @subpackage Command */ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml { const STATUS_SUCCESS = 1; const STATUS_PROTOCOL_ERROR = 2; const STATUS_SERVER_ERROR = 3; const STATUS_ITEM_FAILED_CONVERSION = 14; protected $_defaultNameSpace = 'uri:ItemOperations'; protected $_documentElement = 'ItemOperations'; /** * list of items to move * * @var array */ protected $_fetches = array(); /** * list of folder to empty * * @var array */ protected $_emptyFolderContents = array(); /** * parse MoveItems request * */ public function handle() { $xml = simplexml_import_dom($this->_requestBody); if (isset($xml->Fetch)) { foreach ($xml->Fetch as $fetch) { $this->_fetches[] = $this->_handleFetch($fetch); } } if (isset($xml->EmptyFolderContents)) { foreach ($xml->EmptyFolderContents as $emptyFolderContents) { $this->_emptyFolderContents[] = $this->_handleEmptyFolderContents($emptyFolderContents); } } } /** * generate ItemOperations response * * @todo add multipart support to all types of fetches */ public function getResponse() { // add aditional namespaces $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase' , 'uri:AirSyncBase'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSync' , 'uri:AirSync'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Search' , 'uri:Search'); $itemOperations = $this->_outputDom->documentElement; $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS)); $response = $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Response')); foreach ($this->_fetches as $fetch) { $fetchTag = $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Fetch')); try { $dataController = Syncroton_Data_Factory::factory($fetch['store'], $this->_device, $this->_syncTimeStamp); if (isset($fetch['collectionId'])) { $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS)); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId'])); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId'])); $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties'); $dataController ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'], 'options' => $fetch['options'])), $fetch['serverId']) ->appendXML($properties, $this->_device); $fetchTag->appendChild($properties); } elseif (isset($fetch['longId'])) { $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS)); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:Search', 'LongId', $fetch['longId'])); $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties'); $dataController ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['longId'], 'options' => $fetch['options'])), $fetch['longId']) ->appendXML($properties, $this->_device); $fetchTag->appendChild($properties); } elseif (isset($fetch['fileReference'])) { $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS)); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference'])); $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties'); $fileReference = $dataController->getFileReference($fetch['fileReference']); // unset data field and move content to stream - if ($this->_requestParameters['acceptMultipart'] == true) { + if (!empty($this->_requestParameters['acceptMultipart'])) { $this->_headers['Content-Type'] = 'application/vnd.ms-sync.multipart'; $partStream = fopen("php://temp", 'r+'); if (is_resource($fileReference->data)) { stream_copy_to_stream($fileReference->data, $partStream); } else { fwrite($partStream, $fileReference->data); } unset($fileReference->data); $this->_parts[] = $partStream; $fileReference->part = count($this->_parts); } /** * the client requested a range. But we return the whole file. * * That's not correct, but allowed. The server is allowed to overwrite the range. * * @todo implement cutting $fileReference->data into pieces */ if (isset($fetch['options']['range'])) { $dataSize = $this->_getDataSize($fileReference->data); $total = $this->_outputDom->createElementNS('uri:ItemOperations', 'Total', $dataSize); $properties->appendChild($total); $rangeEnd = $dataSize > 0 ? $dataSize - 1 : 0; $range = $this->_outputDom->createElementNS('uri:ItemOperations', 'Range', '0-' . $rangeEnd); $properties->appendChild($range); } $fileReference->appendXML($properties, $this->_device); $fetchTag->appendChild($properties); } } catch (Syncroton_Exception_NotFound $e) { $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION)); } catch (Exception $e) { //echo __LINE__; echo $e->getMessage(); echo $e->getTraceAsString(); $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SERVER_ERROR)); } } foreach ($this->_emptyFolderContents as $emptyFolderContents) { try { $folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']); $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp); $dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']); $status = Syncroton_Command_ItemOperations::STATUS_SUCCESS; } catch (Syncroton_Exception_Status_ItemOperations $e) { $status = $e->getCode(); } catch (Exception $e) { $status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR; } $emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents'); $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status)); $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId'])); $response->appendChild($emptyFolderContentsTag); } return $this->_outputDom; } /** * parse fetch request * * @param SimpleXMLElement $fetch * @return array */ protected function _handleFetch(SimpleXMLElement $fetch) { $fetchArray = array( 'store' => (string)$fetch->Store, 'options' => array() ); // try to fetch element from namespace AirSync $airSync = $fetch->children('uri:AirSync'); if (isset($airSync->CollectionId)) { $fetchArray['collectionId'] = (string)$airSync->CollectionId; $fetchArray['serverId'] = (string)$airSync->ServerId; } // try to fetch element from namespace Search $search = $fetch->children('uri:Search'); if (isset($search->LongId)) { $fetchArray['longId'] = (string)$search->LongId; } // try to fetch element from namespace AirSyncBase $airSyncBase = $fetch->children('uri:AirSyncBase'); if (isset($airSyncBase->FileReference)) { $fetchArray['fileReference'] = (string)$airSyncBase->FileReference; } if (isset($fetch->Options)) { // try to fetch element from namespace AirSyncBase $airSyncBase = $fetch->Options->children('uri:AirSyncBase'); if (isset($airSyncBase->BodyPreference)) { foreach ($airSyncBase->BodyPreference as $bodyPreference) { $type = (int) $bodyPreference->Type; $fetchArray['options']['bodyPreferences'][$type] = array( 'type' => $type ); // optional if (isset($bodyPreference->TruncationSize)) { $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize; } // optional if (isset($bodyPreference->AllOrNone)) { $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone; } } } if (isset($fetch->Options->MIMESupport)){ $fetchArray['options']['mimeSupport'] = (int) $fetch->Options->MIMESupport; } if (isset($airSyncBase->Range)) { $fetchArray['options']['range'] = (string) $airSyncBase->Range; } } return $fetchArray; } /** * handle empty folder request * * @param SimpleXMLElement $emptyFolderContent * @return array */ protected function _handleEmptyFolderContents(SimpleXMLElement $emptyFolderContent) { $folderArray = array( 'collectiondId' => null, 'options' => array('deleteSubFolders' => FALSE) ); // try to fetch element from namespace AirSync $airSync = $emptyFolderContent->children('uri:AirSync'); $folderArray['collectionId'] = (string)$airSync->CollectionId; if (isset($emptyFolderContent->Options)) { $folderArray['options']['deleteSubFolders'] = isset($emptyFolderContent->Options->DeleteSubFolders); } return $folderArray; } /** * return length of data * * @param string|resource $data * @return number */ protected function _getDataSize($data) { if (is_resource($data)) { rewind($data); fseek($data, 0, SEEK_END); return ftell($data); } else { return strlen($data); } } } diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php index 6307c81..3674c1c 100644 --- a/lib/ext/Syncroton/Command/SendMail.php +++ b/lib/ext/Syncroton/Command/SendMail.php @@ -1,125 +1,125 @@ * @author Aleksander Machniak */ /** * class to handle ActiveSync SendMail command * * @package Syncroton * @subpackage Command */ class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml { protected $_defaultNameSpace = 'uri:ComposeMail'; protected $_documentElement = 'SendMail'; protected $_mime; protected $_saveInSent; protected $_source; protected $_replaceMime = false; /** * Process the XML file and add, change, delete or fetches data */ public function handle() { - if ($this->_requestParameters['contentType'] == 'message/rfc822') { + if (!empty($this->_requestParameters['contentType']) && $this->_requestParameters['contentType'] == 'message/rfc822') { $this->_mime = $this->_requestBody; $this->_saveInSent = $this->_requestParameters['saveInSent']; $this->_replaceMime = false; $this->_source = array( 'collectionId' => $this->_requestParameters['collectionId'], 'itemId' => $this->_requestParameters['itemId'], 'instanceId' => null ); } else if ($this->_requestBody) { $xml = simplexml_import_dom($this->_requestBody); $this->_mime = (string) $xml->Mime; $this->_saveInSent = isset($xml->SaveInSentItems); $this->_replaceMime = isset($xml->ReplaceMime); if (isset ($xml->Source)) { if ($xml->Source->LongId) { $this->_source = (string)$xml->Source->LongId; } else { $this->_source = array( 'collectionId' => (string)$xml->Source->FolderId, 'itemId' => (string)$xml->Source->ItemId, 'instanceId' => isset($xml->Source->InstanceId) ? (string)$xml->Source->InstanceId : null ); } } } if (empty($this->_mime)) { if ($this->_logger instanceof Zend_Log) $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Sending email failed: Empty input"); if (version_compare($this->_device->acsversion, '14.0', '<')) { header("HTTP/1.1 400 Invalid content"); die; } $response_type = 'Syncroton_Model_' . $this->_documentElement; $response = new $response_type(array( 'status' => Syncroton_Exception_Status::INVALID_CONTENT, )); $response->appendXML($this->_outputDom->documentElement, $this->_device); return $this->_outputDom; } if ($this->_logger instanceof Zend_Log) $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " saveInSent: " . (int)$this->_saveInSent); } /** * this function generates the response for the client * * @return void|DOMDocument */ public function getResponse() { $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp); try { $this->sendMail($dataController); } catch (Syncroton_Exception_Status $ses) { if ($this->_logger instanceof Zend_Log) $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Sending email failed: " . $ses->getMessage()); $response_type = 'Syncroton_Model_' . $this->_documentElement; $response = new $response_type(array( 'status' => $ses->getCode(), )); $response->appendXML($this->_outputDom->documentElement, $this->_device); return $this->_outputDom; } } /** * Execute email sending method of data controller * To be overwritten by SmartForward and SmartReply command handlers */ protected function sendMail($dataController) { $dataController->sendEmail($this->_mime, $this->_saveInSent); } } diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php index feb20a8..37819f8 100644 --- a/lib/ext/Syncroton/Command/Wbxml.php +++ b/lib/ext/Syncroton/Command/Wbxml.php @@ -1,222 +1,222 @@ */ /** * abstract class for all commands using wbxml encoded content * * @package Syncroton * @subpackage Command */ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand { /** * informations about the currently device * * @var Syncroton_Model_Device */ protected $_device; /** * informations about the currently device * * @var Syncroton_Backend_IDevice */ protected $_deviceBackend; /** * informations about the currently device * * @var Syncroton_Backend_IFolder */ protected $_folderBackend; /** * @var Syncroton_Backend_ISyncState */ protected $_syncStateBackend; /** * @var Syncroton_Backend_IContent */ protected $_contentStateBackend; /** * * @var Syncroton_Backend_IPolicy */ protected $_policyBackend; /** * the domDocument containing the xml response from the server * * @var DOMDocument */ protected $_outputDom; /** * the domDocucment containing the xml request from the client * * @var DOMDocument */ protected $_requestBody; /** * the default namespace * * @var string */ protected $_defaultNameSpace; /** * the main xml tag * * @var string */ protected $_documentElement; /** * @var array */ protected $_requestParameters; /** * @var Syncroton_Model_SyncState */ protected $_syncState; protected $_skipValidatePolicyKey = false; /** * timestamp to use for all sync requests * * @var DateTime */ protected $_syncTimeStamp; /** * @var string */ protected $_transactionId; /** * @var string */ protected $_policyKey; /** * @var Zend_Log */ protected $_logger; /** * list of part streams * * @var array */ protected $_parts = array(); /** * list of headers * * @var array */ protected $_headers = array(); /** * the constructor * * @param mixed $requestBody * @param Syncroton_Model_Device $device * @param array $requestParameters */ public function __construct($requestBody, Syncroton_Model_IDevice $device, $requestParameters) { $this->_requestBody = $requestBody; $this->_device = $device; $this->_requestParameters = $requestParameters; - $this->_policyKey = $requestParameters['policyKey']; - + $this->_policyKey = isset($requestParameters['policyKey']) ? $requestParameters['policyKey'] : null; + $this->_deviceBackend = Syncroton_Registry::getDeviceBackend(); $this->_folderBackend = Syncroton_Registry::getFolderBackend(); $this->_syncStateBackend = Syncroton_Registry::getSyncStateBackend(); $this->_contentStateBackend = Syncroton_Registry::getContentStateBackend(); $this->_policyBackend = Syncroton_Registry::getPolicyBackend(); if (Syncroton_Registry::isRegistered('loggerBackend')) { $this->_logger = Syncroton_Registry::get('loggerBackend'); } $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('UTC')); // set default content type $this->_headers['Content-Type'] = 'application/vnd.ms-sync.wbxml'; if ($this->_logger instanceof Zend_Log) $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " sync timestamp: " . $this->_syncTimeStamp->format('Y-m-d H:i:s')); if (isset($this->_defaultNameSpace) && isset($this->_documentElement)) { // Creates an instance of the DOMImplementation class $imp = new DOMImplementation(); // Creates a DOMDocumentType instance $dtd = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/"); // Creates a DOMDocument instance $this->_outputDom = $imp->createDocument($this->_defaultNameSpace, $this->_documentElement, $dtd); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Syncroton', 'uri:Syncroton'); $this->_outputDom->formatOutput = false; $this->_outputDom->encoding = 'utf-8'; } if ($this->_skipValidatePolicyKey != true) { if (!empty($this->_device->policyId)) { $policy = $this->_policyBackend->get($this->_device->policyId); if((int)$policy->policyKey != (int)$this->_policyKey) { $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 142)); $sepn = new Syncroton_Exception_ProvisioningNeeded(); $sepn->domDocument = $this->_outputDom; throw $sepn; } // should we wipe the mobile phone? if ($this->_device->remotewipe >= Syncroton_Command_Provision::REMOTEWIPE_REQUESTED) { $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 140)); $sepn = new Syncroton_Exception_ProvisioningNeeded(); $sepn->domDocument = $this->_outputDom; throw $sepn; } } } } /** * (non-PHPdoc) * @see Syncroton_Command_ICommand::getHeaders() */ public function getHeaders() { return $this->_headers; } /** * return array of part streams * * @return array */ public function getParts() { return $this->_parts; } }