/** * handle post request */ protected function _handlePost() { $requestParameters = $this->_getRequestParameters($this->_request); if ($this->_logger instanceof Syncroton_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST ' . print_r($requestParameters, true)); } $className = 'Syncroton_Command_' . $requestParameters['command']; if (!class_exists($className)) { if ($this->_logger instanceof Syncroton_Log) { $this->_logger->critical(__METHOD__ . '::' . __LINE__ . " command not supported: " . $requestParameters['command']); } header("HTTP/1.1 501 not implemented"); return; } // get user device $device = $this->_getUserDevice($this->_userId, $requestParameters); if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml' || $requestParameters['contentType'] == 'application/vnd.ms-sync') { // decode wbxml request try { $decoder = new Syncroton_Wbxml_Decoder($this->_body); $requestBody = $decoder->decode(); if ($this->_logger instanceof Syncroton_Log) { $requestBody->formatOutput = true; $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml request:\n" . $requestBody->saveXML()); } } catch (Syncroton_Wbxml_Exception_UnexpectedEndOfFile $e) { $requestBody = NULL; } } else { $requestBody = $this->_body; } header("MS-Server-ActiveSync: 14.00.0536.000"); // avoid sending HTTP header "Content-Type: text/html" for empty sync responses ini_set('default_mimetype', 'application/vnd.ms-sync.wbxml'); try { $command = new $className($requestBody, $device, $requestParameters); $command->handle(); $response = $command->getResponse(); } catch (Syncroton_Exception_ProvisioningNeeded $sepn) { if ($this->_logger instanceof Syncroton_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed"); } header("HTTP/1.1 449 Retry after sending a PROVISION command"); if (version_compare($device->acsversion, '14.0', '>=')) { $response = $sepn->domDocument; } else { // pre 14.0 method return; } } catch (Exception $e) { if ($this->_logger instanceof Syncroton_Log) { $this->_logger->critical(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e)); } if ($this->_logger instanceof Syncroton_Log) { $this->_logger->critical(__METHOD__ . '::' . __LINE__ . " exception message: " . $e->getMessage()); } if ($this->_logger instanceof Syncroton_Log) { $this->_logger->critical(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString()); } header("HTTP/1.1 500 Internal server error"); return; } if ($response instanceof DOMDocument) { if ($this->_logger instanceof Syncroton_Log) { $this->_logDomDocument('debug', $response, __METHOD__, __LINE__); } if (isset($command) && $command instanceof Syncroton_Command_ICommand) { $this->_sendHeaders($command->getHeaders()); } $outputStream = fopen("php://temp", 'r+'); $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); try { $encoder->encode($response); } catch (Syncroton_Wbxml_Exception $swe) { if ($this->_logger instanceof Syncroton_Log) { $this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe); $this->_logDomDocument('error', $response, __METHOD__, __LINE__); } header("HTTP/1.1 500 Internal server error"); return; } if ($requestParameters['acceptMultipart'] == true) { $parts = $command->getParts(); // output multipartheader $bodyPartCount = 1 + count($parts); // number of parts (4 bytes) $header = pack('i', $bodyPartCount); $partOffset = 4 + $bodyPartCount * 2 * 4; // wbxml body start and length $streamStat = fstat($outputStream); $header .= pack('ii', $partOffset, $streamStat['size']); $partOffset += $streamStat['size']; // calculate start and length of parts foreach ($parts as $partId => $partStream) { rewind($partStream); $streamStat = fstat($partStream); // part start and length $header .= pack('ii', $partOffset, $streamStat['size']); $partOffset += $streamStat['size']; } echo $header; } // output body rewind($outputStream); fpassthru($outputStream); // output multiparts if (isset($parts)) { foreach ($parts as $partStream) { rewind($partStream); fpassthru($partStream); } } } }
/** * decodes the tags * * @return DOMDocument the decoded xml */ public function decode() { $openTags = NULL; $node = NULL; $this->_codePage = $this->_dtd->getCurrentCodePage(); while (!feof($this->_stream)) { $byte = $this->_getByte(); switch ($byte) { case Syncroton_Wbxml_Abstract::END: $node = $node->parentNode; $openTags--; break; case Syncroton_Wbxml_Abstract::OPAQUE: $length = $this->_getMultibyteUInt(); if ($length > 0) { $opaque = $this->_getOpaque($length); try { // let see if we can decode it. maybe the opaque data is wbxml encoded content $opaqueDataStream = fopen("php://temp", 'r+'); fputs($opaqueDataStream, $opaque); rewind($opaqueDataStream); $opaqueContentDecoder = new Syncroton_Wbxml_Decoder($opaqueDataStream); $dom = $opaqueContentDecoder->decode(); fclose($opaqueDataStream); foreach ($dom->childNodes as $newNode) { if ($newNode instanceof DOMElement) { $newNode = $this->_dom->importNode($newNode, true); $node->appendChild($newNode); } } } catch (Exception $e) { // if not, just treat it as a string $node->appendChild($this->_dom->createTextNode($opaque)); } } break; case Syncroton_Wbxml_Abstract::STR_I: $string = $this->_getTerminatedString(); $node->appendChild($this->_dom->createTextNode($string)); break; case Syncroton_Wbxml_Abstract::SWITCH_PAGE: $page = $this->_getByte(); $this->_codePage = $this->_dtd->switchCodePage($page); #echo "switched to codepage $page\n"; break; default: $tagHasAttributes = ($byte & 0x80) != 0; $tagHasContent = ($byte & 0x40) != 0; // get rid of bit 7+8 $tagHexCode = $byte & 0x3f; try { $tag = $this->_codePage->getTag($tagHexCode); } catch (Syncroton_Wbxml_Exception $swe) { // tag can not be converted to ASCII name $tag = sprintf('unknown tag 0x%x', $tagHexCode); } $nameSpace = $this->_codePage->getNameSpace(); $codePageName = $this->_codePage->getCodePageName(); #echo "Tag: $nameSpace:$tag\n"; if ($node === NULL) { // create the domdocument $node = $this->_createDomDocument($nameSpace, $tag); $newNode = $node->documentElement; } else { if (!$this->_dom->isDefaultNamespace($nameSpace)) { $this->_dom->documentElement->setAttribute('xmlns:' . $codePageName, $nameSpace); } $newNode = $node->appendChild($this->_dom->createElementNS('uri:' . $codePageName, $tag)); } if ($tagHasAttributes) { $attributes = $this->_getAttributes(); } if ($tagHasContent == true) { $node = $newNode; $openTags++; } break; } } return $this->_dom; }
/** * test create contact */ public function testCreateContact() { $personalContainer = $this->_getPersonalContainer('Addressbook'); $this->testSyncOfContacts(); // lets add one contact $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; $doc->loadXML('<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> <Sync xmlns="uri:AirSync" xmlns:Contacts="uri:Contacts" xmlns:AirSyncBase="uri:AirSyncBase"><Collections> <Collection> <Class>Contacts</Class><SyncKey>2</SyncKey><CollectionId>' . $personalContainer->getId() . '</CollectionId><DeletesAsMoves/><GetChanges/><WindowSize>100</WindowSize> <Options><AirSyncBase:BodyPreference><AirSyncBase:Type>1</AirSyncBase:Type><AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize></AirSyncBase:BodyPreference><Conflict>1</Conflict></Options> <Commands><Add><ClientId>42</ClientId><ApplicationData><Contacts:FirstName>aaaadde</Contacts:FirstName><Contacts:LastName>aaaaade</Contacts:LastName></ApplicationData></Add></Commands> </Collection> </Collections></Sync>'); // decode to wbxml and back again to test the wbxml en-/decoder $xmlStream = fopen("php://temp", 'r+'); $encoder = new Syncroton_Wbxml_Encoder($xmlStream, 'UTF-8', 3); $encoder->encode($doc); rewind($xmlStream); $decoder = new Syncroton_Wbxml_Decoder($xmlStream); $doc = $decoder->decode(); #$doc->formatOutput = true; echo $doc->saveXML(); $sync = new Syncroton_Command_Sync($doc, $this->_device, $this->_device->policykey); $sync->handle(); $syncDoc = $sync->getResponse(); #$syncDoc->formatOutput = true; echo $syncDoc->saveXML(); $xpath = new DomXPath($syncDoc); $xpath->registerNamespace('AirSync', 'uri:AirSync'); $nodes = $xpath->query('//AirSync:Sync/AirSync:Collections/AirSync:Collection/AirSync:Class'); $this->assertEquals(1, $nodes->length, 'one contact should be in here: ' . $syncDoc->saveXML()); $this->assertEquals('Contacts', $nodes->item(0)->nodeValue, $syncDoc->saveXML()); $nodes = $xpath->query('//AirSync:Sync/AirSync:Collections/AirSync:Collection/AirSync:SyncKey'); $this->assertEquals(1, $nodes->length, $syncDoc->saveXML()); $this->assertEquals(3, $nodes->item(0)->nodeValue, $syncDoc->saveXML()); $nodes = $xpath->query('//AirSync:Sync/AirSync:Collections/AirSync:Collection/AirSync:Status'); $this->assertEquals(1, $nodes->length, $syncDoc->saveXML()); $this->assertEquals(Syncroton_Command_Sync::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $syncDoc->saveXML()); $nodes = $xpath->query('//AirSync:Sync/AirSync:Collections/AirSync:Collection/AirSync:Responses/AirSync:Add/AirSync:ServerId'); $this->assertEquals(1, $nodes->length, $syncDoc->saveXML()); $this->assertFalse(empty($nodes->item(0)->nodeValue), $syncDoc->saveXML()); $nodes = $xpath->query('//AirSync:Sync/AirSync:Collections/AirSync:Collection/AirSync:Responses/AirSync:Add/AirSync:Status'); $this->assertEquals(1, $nodes->length, $syncDoc->saveXML()); $this->assertEquals(Syncroton_Command_Sync::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $syncDoc->saveXML()); }
/** * try to encode XML until we have wbxml tests * * @param $testDoc * @return string returns encoded/decoded xml string */ public static function encodeXml($testDoc) { $outputStream = fopen("php://temp", 'r+'); $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); $encoder->encode($testDoc); rewind($outputStream); $decoder = new Syncroton_Wbxml_Decoder($outputStream); $xml = $decoder->decode(); return $xml->saveXML(); }
/** * */ public function testDecode() { $decoder = new Syncroton_Wbxml_Decoder(fopen(__DIR__ . '/files/simple.wbxml', 'r+')); $requestBody = $decoder->decode(); $this->assertTrue($requestBody instanceof DomDocument); }