diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index f65abba..d7654e0 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,453 +1,456 @@
*/
/**
* class to handle incoming http ActiveSync requests
*
* @package Syncroton
*/
class Syncroton_Server
{
const PARAMETER_ATTACHMENTNAME = 0;
const PARAMETER_COLLECTIONID = 1;
const PARAMETER_ITEMID = 3;
const PARAMETER_OPTIONS = 7;
const MAX_HEARTBEAT_INTERVAL = 3540; // 59 minutes
protected $_body;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IDevice
*/
protected $_deviceBackend;
/**
* @var Zend_Log
*/
protected $_logger;
/**
* @var Zend_Controller_Request_Http
*/
protected $_request;
protected $_userId;
public function __construct($userId, Zend_Controller_Request_Http $request = null, $body = null)
{
if (Syncroton_Registry::isRegistered('loggerBackend')) {
$this->_logger = Syncroton_Registry::get('loggerBackend');
}
$this->_userId = $userId;
$this->_request = $request instanceof Zend_Controller_Request_Http ? $request : new Zend_Controller_Request_Http();
$this->_body = $body !== null ? $body : fopen('php://input', 'r');
$this->_deviceBackend = Syncroton_Registry::getDeviceBackend();
}
public function handle()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST METHOD: ' . $this->_request->getMethod());
switch($this->_request->getMethod()) {
case 'OPTIONS':
$this->_handleOptions();
break;
case 'POST':
$this->_handlePost();
break;
case 'GET':
echo "It works!
Your userid is: {$this->_userId} and your IP address is: {$_SERVER['REMOTE_ADDR']}.";
break;
}
}
/**
* handle options request
*/
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
$this->_sendHeaders($command->getHeaders());
}
protected function _sendHeaders(array $headers)
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value);
}
}
/**
* handle post request
*/
protected function _handlePost()
{
$requestParameters = $this->_getRequestParameters($this->_request);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST ' . print_r($requestParameters, true));
$className = 'Syncroton_Command_' . $requestParameters['command'];
if(!class_exists($className)) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__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 Zend_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);
$response = $command->handle();
if (!$response) {
$response = $command->getResponse();
}
} catch (Syncroton_Exception_ProvisioningNeeded $sepn) {
if ($this->_logger instanceof Zend_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 Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e));
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " exception message: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
header("HTTP/1.1 500 Internal server error");
return;
}
if ($response instanceof DOMDocument) {
if ($this->_logger instanceof Zend_Log) {
$this->_logDomDocument(Zend_Log::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 Zend_Log) {
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe);
$this->_logDomDocument(Zend_Log::WARN, $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);
}
}
}
}
/**
* write (possible big) DOMDocument in smaller chunks to log file
*
* @param unknown $priority
* @param DOMDocument $dom
* @param string $method
* @param int $line
*/
protected function _logDomDocument($priority, DOMDocument $dom, $method, $line)
{
$loops = 0;
- $tempStream = fopen('php://temp/maxmemory:5242880', 'r+');
-
+ $tempStream = tmpfile();
+
+ $meta_data = stream_get_meta_data($tempStream);
+ $filename = $meta_data["uri"];
+
$dom->formatOutput = true;
- fwrite($tempStream, $dom->saveXML());
+ $dom->save($filename);
$dom->formatOutput = false;
rewind($tempStream);
// log data in 1MByte chunks
while (!feof($tempStream)) {
$this->_logger->log($method . '::' . $line . " xml response($loops):\n" . fread($tempStream, 1048576), $priority);
$loops++;
}
fclose($tempStream);
}
/**
* return request params
*
* @return array
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
if (strpos($request->getRequestUri(), '&') === false) {
$commands = array(
0 => 'Sync',
1 => 'SendMail',
2 => 'SmartForward',
3 => 'SmartReply',
4 => 'GetAttachment',
9 => 'FolderSync',
10 => 'FolderCreate',
11 => 'FolderDelete',
12 => 'FolderUpdate',
13 => 'MoveItems',
14 => 'GetItemEstimate',
15 => 'MeetingResponse',
16 => 'Search',
17 => 'Settings',
18 => 'Ping',
19 => 'ItemOperations',
20 => 'Provision',
21 => 'ResolveRecipients',
22 => 'ValidateCert'
);
$requestParameters = substr($request->getRequestUri(), strpos($request->getRequestUri(), '?'));
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($requestParameters));
rewind($stream);
// unpack the first 4 bytes
$unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
// 140 => 14.0
$protocolVersion = substr($unpacked['protocolVersion'], 0, -1) . '.' . substr($unpacked['protocolVersion'], -1);
$command = $commands[$unpacked['command']];
$locale = $unpacked['locale'];
// unpack deviceId
$length = ord(fread($stream, 1));
if ($length > 0) {
$toUnpack = fread($stream, $length);
$unpacked = unpack("H" . ($length * 2) . "string", $toUnpack);
$deviceId = $unpacked['string'];
}
// unpack policyKey
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('Vstring', fread($stream, $length));
$policyKey = $unpacked['string'];
}
// unpack device type
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$deviceType = $unpacked['string'];
}
while (! feof($stream)) {
$tag = ord(fread($stream, 1));
$length = ord(fread($stream, 1));
switch ($tag) {
case self::PARAMETER_ATTACHMENTNAME:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$attachmentName = $unpacked['string'];
break;
case self::PARAMETER_COLLECTIONID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$collectionId = $unpacked['string'];
break;
case self::PARAMETER_ITEMID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$itemId = $unpacked['string'];
break;
case self::PARAMETER_OPTIONS:
$options = ord(fread($stream, 1));
$saveInSent = !!($options & 0x01);
$acceptMultiPart = !!($options & 0x02);
break;
default:
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " found unhandled command parameters");
}
}
$result = array(
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
'deviceType' => isset($deviceType) ? $deviceType : null,
'policyKey' => isset($policyKey) ? $policyKey : null,
'saveInSent' => isset($saveInSent) ? $saveInSent : false,
'collectionId' => isset($collectionId) ? $collectionId : null,
'itemId' => isset($itemId) ? $itemId : null,
'attachmentName' => isset($attachmentName) ? $attachmentName : null,
'acceptMultipart' => isset($acceptMultiPart) ? $acceptMultiPart : false
);
} else {
$result = array(
'protocolVersion' => $request->getServer('HTTP_MS_ASPROTOCOLVERSION'),
'command' => $request->getQuery('Cmd'),
'deviceId' => $request->getQuery('DeviceId'),
'deviceType' => $request->getQuery('DeviceType'),
'policyKey' => $request->getServer('HTTP_X_MS_POLICYKEY'),
'saveInSent' => $request->getQuery('SaveInSent') == 'T',
'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId'),
'attachmentName' => $request->getQuery('AttachmentName'),
'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
$result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
$result['contentType'] = $request->getServer('CONTENT_TYPE');
return $result;
}
/**
* get existing device of owner or create new device for owner
*
* @param unknown_type $ownerId
* @param unknown_type $deviceId
* @param unknown_type $deviceType
* @param unknown_type $userAgent
* @param unknown_type $protocolVersion
* @return Syncroton_Model_Device
*/
protected function _getUserDevice($ownerId, $requestParameters)
{
try {
$device = $this->_deviceBackend->getUserDevice($ownerId, $requestParameters['deviceId']);
$device->useragent = $requestParameters['userAgent'];
$device->acsversion = $requestParameters['protocolVersion'];
if ($device->isDirty()) {
$device = $this->_deviceBackend->update($device);
}
} catch (Syncroton_Exception_NotFound $senf) {
$device = $this->_deviceBackend->create(new Syncroton_Model_Device(array(
'owner_id' => $ownerId,
'deviceid' => $requestParameters['deviceId'],
'devicetype' => $requestParameters['deviceType'],
'useragent' => $requestParameters['userAgent'],
'acsversion' => $requestParameters['protocolVersion'],
'policyId' => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
)));
}
return $device;
}
public static function validateSession()
{
$validatorFunction = Syncroton_Registry::getSessionValidator();
return $validatorFunction();
}
}
diff --git a/lib/ext/Syncroton/Wbxml/Encoder.php b/lib/ext/Syncroton/Wbxml/Encoder.php
index f17db83..9e88db2 100644
--- a/lib/ext/Syncroton/Wbxml/Encoder.php
+++ b/lib/ext/Syncroton/Wbxml/Encoder.php
@@ -1,369 +1,372 @@
* @version $Id:Encoder.php 4968 2008-10-17 09:09:33Z l.kneschke@metaways.de $
*/
/**
* class to convert XML to WBXML
*
* @package Wbxml
* @subpackage Wbxml
*/
class Syncroton_Wbxml_Encoder extends Syncroton_Wbxml_Abstract
{
/**
* stack of dtd objects
*
* @var array
*/
protected $_dtdStack = array();
/**
* stack of stream resources
*
* @var array
*/
protected $_streamStack = array();
/**
* stack of levels when to pop data from the other stacks
*
* @var array
*/
protected $_popStack = array();
/**
* count level of tags
*
* @var string
*/
protected $_level = 0;
/**
* when to take data next time from the different stacks
*
* @var unknown_type
*/
protected $_nextStackPop = NULL;
/**
* collect data trough different calls to _handleCharacters
*
* @var string
*/
protected $_currentTagData = NULL;
/**
* the current tag as read by the parser
*
* @var string
*/
protected $_currentTag = NULL;
/**
* the constructor
*
* @param resource $_stream
* @param string $_charSet
* @param integer $_version
*/
public function __construct($_stream, $_charSet = 'UTF-8', $_version = 2)
{
$this->_stream = $_stream;
$this->_charSet = $_charSet;
$this->_version = $_version;
}
/**
* initialize internal variables and write wbxml header to stream
*
* @param string $_urn
* @todo check if dpi > 0, instead checking the urn
*/
protected function _initialize($_dom)
{
$this->_dtd = Syncroton_Wbxml_Dtd_Factory::factory($_dom->doctype->name);
$this->_codePage = $this->_dtd->getCurrentCodePage();
// the WBXML version
$this->_writeByte($this->_version);
if($this->_codePage->getDPI() === NULL) {
// the document public identifier
$this->_writeMultibyteUInt(1);
} else {
// the document public identifier
// defined in string table
$this->_writeMultibyteUInt(0);
// the offset of the DPI in the string table
$this->_writeByte(0);
}
// write the charSet
$this->_writeCharSet($this->_charSet);
if($this->_codePage->getDPI() === NULL) {
// the length of the string table
$this->_writeMultibyteUInt(0);
} else {
// the length of the string table
$this->_writeMultibyteUInt(strlen($this->_codePage->getDPI()));
// the dpi
$this->_writeString($this->_codePage->getDPI());
}
}
/**
* write charset to stream
*
* @param string $_charSet
* @todo add charset lookup table. currently only utf-8 is supported
*/
protected function _writeCharSet($_charSet)
{
switch(strtoupper($_charSet)) {
case 'UTF-8':
$this->_writeMultibyteUInt(106);
break;
default:
throw new Syncroton_Wbxml_Exception('unsuported charSet ' . strtoupper($_charSet));
break;
}
}
/**
* start encoding of xml to wbxml
*
* @param string $_xml the xml string
* @return resource stream
*/
public function encode(DOMDocument $_dom)
{
$_dom->formatOutput = false;
- $tempStream = fopen('php://temp/maxmemory:5242880', 'r+');
- fwrite($tempStream, $_dom->saveXML());
+ $tempStream = tmpfile();
+
+ $meta_data = stream_get_meta_data($tempStream);
+ $filename = $meta_data["uri"];
+ $_dom->save($filename);
rewind($tempStream);
$this->_initialize($_dom);
$parser = xml_parser_create_ns($this->_charSet, ';');
xml_set_object($parser, $this);
xml_set_element_handler($parser, '_handleStartTag', '_handleEndTag');
xml_set_character_data_handler($parser, '_handleCharacters');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
while (!feof($tempStream)) {
if (!xml_parse($parser, fread($tempStream, 1048576), feof($tempStream))) {
// uncomment to write xml document to file
#rewind($tempStream);
#$xmlStream = fopen(tempnam(sys_get_temp_dir(), "xmlerrors"), 'r+');
#stream_copy_to_stream($tempStream, $xmlStream);
#fclose($xmlStream);
throw new Syncroton_Wbxml_Exception(sprintf('XML error: %s at line %d',
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)
));
}
}
fclose($tempStream);
xml_parser_free($parser);
}
/**
* get's called by xml parser when tag starts
*
* @param resource $_parser
* @param string $_tag current tag prefixed with namespace
* @param array $_attributes list of tag attributes
*/
protected function _handleStartTag($_parser, $_tag, $_attributes)
{
$this->_level++;
$this->_currentTagData = null;
// write data for previous tag happens whith
if($this->_currentTag !== NULL) {
$this->_writeTag($this->_currentTag, $this->_attributes, true);
}
list($nameSpace, $this->_currentTag) = explode(';', $_tag);
if($this->_codePage->getNameSpace() != $nameSpace) {
$this->_switchCodePage($nameSpace);
}
$this->_attributes = $_attributes;
}
/**
* strip uri: from nameSpace
*
* @param unknown_type $_nameSpace
* @return unknown
*/
protected function _stripNameSpace($_nameSpace)
{
return substr($_nameSpace, 4);
}
/**
* get's called by xml parser when tag ends
*
* @param resource $_parser
* @param string $_tag current tag prefixed with namespace
*/
protected function _handleEndTag($_parser, $_tag)
{
#echo "$_tag Level: $this->_level == $this->_nextStackPop \n";
if($this->_nextStackPop !== NULL && $this->_nextStackPop == $this->_level) {
#echo "TAG: $_tag\n";
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
$subStream = $this->_stream;
$subStreamLength = ftell($subStream);
$this->_dtd = array_pop($this->_dtdStack);
$this->_stream = array_pop($this->_streamStack);
$this->_nextStackPop = array_pop($this->_popStack);
$this->_codePage = $this->_dtd->getCurrentCodePage();
rewind($subStream);
#while (!feof($subStream)) {$buffer = fgets($subStream, 4096);echo $buffer;}
$this->_writeByte(Syncroton_Wbxml_Abstract::OPAQUE);
$this->_writeMultibyteUInt($subStreamLength);
$writenBytes = stream_copy_to_stream($subStream, $this->_stream);
if($writenBytes !== $subStreamLength) {
//echo "$writenBytes !== $subStreamLength\n";
throw new Syncroton_Wbxml_Exception('blow');
}
fclose($subStream);
#echo "$this->_nextStackPop \n"; exit;
} else {
if ($this->_currentTag !== NULL && $this->_currentTagData !== NULL) {
$this->_writeTag($this->_currentTag, $this->_attributes, true, $this->_currentTagData);
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
} elseif ($this->_currentTag !== NULL && $this->_currentTagData === NULL) {
// for example tag with no data, jumps directly from _handleStartTag to _handleEndTag
$this->_writeTag($this->_currentTag, $this->_attributes);
// no end tag required, tag has no content
} else {
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
}
}
#list($urn, $tag) = explode(';', $_tag); echo "$tag> ($this->_level)\n";
// reset $this->_currentTag, as tag got writen to stream already
$this->_currentTag = NULL;
$this->_level--;
}
/**
* collects data(value) of tag
* can be called multiple lines if the value contains linebreaks
*
* @param resource $_parser the xml parser
* @param string $_data the data(value) of the tag
*/
protected function _handleCharacters($_parser, $_data)
{
$this->_currentTagData .= $_data;
}
/**
* writes tag with data to stream
*
* @param string $_tag
* @param array $_attributes
* @param bool $_hasContent
* @param string $_data
*/
protected function _writeTag($_tag, $_attributes=NULL, $_hasContent=false, $_data=NULL)
{
if($_hasContent == false && $_data !== NULL) {
throw new Syncroton_Wbxml_Exception('$_hasContent can not be false, when $_data !== NULL');
}
// handle the tag
$identity = $this->_codePage->getIdentity($_tag);
if (is_array($_attributes) && isset($_attributes['uri:Syncroton;encoding'])) {
$encoding = 'opaque';
unset($_attributes['uri:Syncroton;encoding']);
} else {
$encoding = 'termstring';
}
if(!empty($_attributes)) {
$identity |= 0x80;
}
if($_hasContent == true) {
$identity |= 0x40;
}
$this->_writeByte($identity);
// handle the data
if($_data !== NULL) {
if ($encoding == 'opaque') {
$this->_writeOpaqueString(base64_decode($_data));
} else {
$this->_writeTerminatedString($_data);
}
}
$this->_currentTagData = NULL;
}
/**
* switch code page
*
* @param string $_urn
*/
protected function _switchCodePage($_nameSpace)
{
try {
$codePageName = $this->_stripNameSpace($_nameSpace);
if(!defined('Syncroton_Wbxml_Dtd_ActiveSync::CODEPAGE_'. strtoupper($codePageName))) {
throw new Syncroton_Wbxml_Exception('codepage ' . $codePageName . ' not found');
}
// switch to another codepage
// no need to write the wbxml header again
$codePageId = constant('Syncroton_Wbxml_Dtd_ActiveSync::CODEPAGE_'. strtoupper($codePageName));
$this->_codePage = $this->_dtd->switchCodePage($codePageId);
$this->_writeByte(Syncroton_Wbxml_Abstract::SWITCH_PAGE);
$this->_writeByte($codePageId);
} catch (Syncroton_Wbxml_Dtd_Exception_CodePageNotFound $e) {
// switch to another dtd
// need to write the wbxml header again
// put old dtd and stream on stack
$this->_dtdStack[] = $this->_dtd;
$this->_streamStack[] = $this->_stream;
$this->_popStack[] = $this->_nextStackPop;
$this->_nextStackPop = $this->_level;
$this->_stream = fopen("php://temp", 'r+');
$this->_initialize($_urn);
}
}
}
\ No newline at end of file