Commit 63ba8bea authored by Sebastian Kummer's avatar Sebastian Kummer

Merge pull request #111 in ZP/z-push from feature/ZP-797-rework-wbxml-handling to develop

* commit '52162fde':
  ZP-797 Fixed WBXML class constant.
  ZP-797 GetAttachment use fpassthru (more efficient).
  ZP-797 WBXMLDecoder is handling ActiveSync WBXML, which is only a subset of WBXML.
  ZP-797 WBXML{En,De}coder: use const instead of define for WBXML_* constants.
  ZP-797 WBXMLDecoder->getOpaque() use stream_get_contents().
  ZP-797 WBXMLDecoder->GetPlainInputStream() use stream_get_contents().
  ZP-797 WBXMLDecoder->_getToken() speedup.
  ZP-797 WBXMLDecoder->getTermStr(): use stream_get_line().
  ZP-797 WBXMLEncoder remove possible endless loop/improve multipart handling.
  ZP-797 WBXMLEncoder remove _outlog / use output buffering.
  ZP-797 WBXMLDecoder remove inlog / reread php://input.
parents f49acb93 52162fde
...@@ -66,20 +66,13 @@ class GetAttachment extends RequestProcessor { ...@@ -66,20 +66,13 @@ class GetAttachment extends RequestProcessor {
throw new StatusException(sprintf("HandleGetAttachment(): No stream resource returned by backend for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); throw new StatusException(sprintf("HandleGetAttachment(): No stream resource returned by backend for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
header("Content-Type: application/octet-stream"); header("Content-Type: application/octet-stream");
$l = 0; self::$topCollector->AnnounceInformation("Starting attachment streaming", true);
while (!feof($stream)) { $l = fpassthru($stream);
$d = fgets($stream, 4096);
$l += strlen($d);
echo $d;
// announce an update every 100K
if (($l/1024) % 100 == 0)
self::$topCollector->AnnounceInformation(sprintf("Streaming attachment: %d KB sent", round($l/1024)));
}
fclose($stream); fclose($stream);
self::$topCollector->AnnounceInformation(sprintf("Streamed %d KB attachment", $l/1024), true); if ($l === false)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment with %d KB sent to mobile", $l/1024)); throw new FatalException("HandleGetAttachment(): fpassthru === false !!!");
self::$topCollector->AnnounceInformation(sprintf("Streamed %d KB attachment", round($l/1024)), true);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment with %d KB sent to mobile", round($l/1024)));
} }
catch (StatusException $s) { catch (StatusException $s) {
// StatusException already logged so we just need to pass it upwards to send a HTTP error // StatusException already logged so we just need to pass it upwards to send a HTTP error
......
...@@ -630,4 +630,19 @@ class Request { ...@@ -630,4 +630,19 @@ class Request {
return ($re) ? preg_replace($re, $replacevalue, $input) : ''; return ($re) ? preg_replace($re, $replacevalue, $input) : '';
} }
/**
* Returns base64 encoded "php://input"
* With POST request (our case), you can open and read
* multiple times "php://input"
*
* @access public
* @return string - base64 encoded wbxml
*/
public static function GetInputAsBase64() {
$input = fopen('php://input', 'r');
$wbxml = base64_encode(stream_get_contents($input));
fclose($input);
return $wbxml;
}
} }
...@@ -129,17 +129,12 @@ abstract class RequestProcessor { ...@@ -129,17 +129,12 @@ abstract class RequestProcessor {
// if there is an error decoding wbxml, consume remaining data and include it in the WBXMLException // if there is an error decoding wbxml, consume remaining data and include it in the WBXMLException
if (!$handler->Handle(Request::GetCommandCode())) { if (!$handler->Handle(Request::GetCommandCode())) {
$wbxmlLog = "no decoder"; throw new WBXMLException("Debug data: " . Request::GetInputAsBase64());
if (self::$decoder) {
self::$decoder->readRemainingData();
$wbxmlLog = self::$decoder->getWBXMLLog();
}
throw new WBXMLException("Debug data: " . $wbxmlLog);
} }
// also log WBXML in happy case // also log WBXML in happy case
if (self::$decoder && @constant('WBXML_DEBUG') === true) { if (@constant('WBXML_DEBUG') === true) {
ZLog::Write(LOGLEVEL_WBXML, "WBXML-IN : ". self::$decoder->getWBXMLLog(), false); ZLog::Write(LOGLEVEL_WBXML, "WBXML-IN : ". Request::GetInputAsBase64(), false);
} }
} }
......
...@@ -44,21 +44,13 @@ ...@@ -44,21 +44,13 @@
class WBXMLDecoder extends WBXMLDefs { class WBXMLDecoder extends WBXMLDefs {
private $in; private $in;
private $inLog;
private $version; private $inLog;
private $publicid;
private $publicstringid;
private $charsetid;
private $stringtable;
private $tagcp = 0; private $tagcp = 0;
private $attrcp = 0;
private $ungetbuffer; private $ungetbuffer;
private $log = false;
private $logStack = array(); private $logStack = array();
private $inputBuffer = ""; private $inputBuffer = "";
private $isWBXML = true; private $isWBXML = true;
...@@ -105,31 +97,35 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -105,31 +97,35 @@ class WBXMLDecoder extends WBXMLDefs {
/** /**
* WBXML Decode Constructor * WBXML Decode Constructor
* We only handle ActiveSync WBXML, which is a subset of WBXML
* *
* @param stream $input the incoming data stream * @param stream $input the incoming data stream
* *
* @access public * @access public
*/ */
public function WBXMLDecoder($input) { public function WBXMLDecoder($input) {
// make sure WBXML_DEBUG is defined. It should be at this point $this->log = defined('WBXML_DEBUG') && WBXML_DEBUG;
if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false);
$this->in = $input; $this->in = $input;
$this->inLog = StringStreamWrapper::Open("");
$this->readVersion(); $version = $this->getByte();
if (isset($this->version) && $this->version != self::VERSION) { if($version != self::VERSION) {
$this->inputBuffer .= chr($version);
$this->isWBXML = false; $this->isWBXML = false;
return; return;
} }
$this->publicid = $this->getMBUInt(); $publicid = $this->getMBUInt();
if($this->publicid == 0) { if($publicid !== 1)
$this->publicstringid = $this->getMBUInt(); throw new WBXMLException("Wrong publicid : ".$publicid);
}
$charsetid = $this->getMBUInt();
if ($charsetid !== 106)
throw new WBXMLException("Wrong charset : ".$charsetid);
$this->charsetid = $this->getMBUInt(); $stringtablesize = $this->getMBUInt();
$this->stringtable = $this->getStringTable(); if ($stringtablesize !== 0)
throw new WBXMLException("Wrong string table size : ".$stringtablesize);
} }
/** /**
...@@ -265,11 +261,7 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -265,11 +261,7 @@ class WBXMLDecoder extends WBXMLDefs {
* @return string * @return string
*/ */
public function GetPlainInputStream() { public function GetPlainInputStream() {
$plain = $this->inputBuffer; return $this->inputBuffer.stream_get_contents($this->in);
while($data = fread($this->in, 4096))
$plain .= $data;
return $plain;
} }
/** /**
...@@ -293,20 +285,6 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -293,20 +285,6 @@ class WBXMLDecoder extends WBXMLDefs {
while($this->getElement()); while($this->getElement());
} }
/**
* Returns the WBXML data read from the stream
*
* @access public
* @return string - base64 encoded wbxml
*/
public function getWBXMLLog() {
$out = "";
if ($this->inLog) {
$out = base64_encode(stream_get_contents($this->inLog, -1,0));
}
return $out;
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* Private WBXMLDecoder stuff * Private WBXMLDecoder stuff
*/ */
...@@ -326,7 +304,8 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -326,7 +304,8 @@ class WBXMLDecoder extends WBXMLDefs {
} }
$el = $this->_getToken(); $el = $this->_getToken();
$this->logToken($el); if($this->log)
$this->logToken($el);
return $el; return $el;
} }
...@@ -340,9 +319,6 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -340,9 +319,6 @@ class WBXMLDecoder extends WBXMLDefs {
* @return * @return
*/ */
private function logToken($el) { private function logToken($el) {
if(!WBXML_DEBUG)
return;
$spaces = str_repeat(" ", count($this->logStack)); $spaces = str_repeat(" ", count($this->logStack));
switch($el[EN_TYPE]) { switch($el[EN_TYPE]) {
...@@ -376,217 +352,60 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -376,217 +352,60 @@ class WBXMLDecoder extends WBXMLDefs {
WBXMLDecoder::ResetInWhile("decoderGetToken"); WBXMLDecoder::ResetInWhile("decoderGetToken");
while(WBXMLDecoder::InWhile("decoderGetToken")) { while(WBXMLDecoder::InWhile("decoderGetToken")) {
$byte = $this->getByte(); $byte = fread($this->in, 1);
if($byte === "" || $byte === false)
if(!isset($byte))
break; break;
$byte = ord($byte);
switch($byte) { switch($byte) {
case WBXML_SWITCH_PAGE: case self::WBXML_SWITCH_PAGE:
$this->tagcp = $this->getByte(); $this->tagcp = $this->getByte();
break; break;
case WBXML_END: case self::WBXML_END:
$element[EN_TYPE] = EN_TYPE_ENDTAG; $element[EN_TYPE] = EN_TYPE_ENDTAG;
return $element; return $element;
case WBXML_ENTITY: case self::WBXML_STR_I:
$entity = $this->getMBUInt();
$element[EN_TYPE] = EN_TYPE_CONTENT;
$element[EN_CONTENT] = $this->entityToCharset($entity);
return $element;
case WBXML_STR_I:
$element[EN_TYPE] = EN_TYPE_CONTENT; $element[EN_TYPE] = EN_TYPE_CONTENT;
$element[EN_CONTENT] = $this->getTermStr(); $element[EN_CONTENT] = $this->getTermStr();
return $element; return $element;
case WBXML_LITERAL: case self::WBXML_OPAQUE:
$element[EN_TYPE] = EN_TYPE_STARTTAG;
$element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
$element[EN_FLAGS] = 0;
return $element;
case WBXML_EXT_I_0:
case WBXML_EXT_I_1:
case WBXML_EXT_I_2:
$this->getTermStr();
// Ignore extensions
continue;
case WBXML_PI:
// Ignore PI
$this->getAttributes();
continue;
case WBXML_LITERAL_C:
$element[EN_TYPE] = EN_TYPE_STARTTAG;
$element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
$element[EN_FLAGS] = EN_FLAGS_CONTENT;
return $element;
case WBXML_EXT_T_0:
case WBXML_EXT_T_1:
case WBXML_EXT_T_2:
$this->getMBUInt();
// Ingore extensions;
continue;
case WBXML_STR_T:
$element[EN_TYPE] = EN_TYPE_CONTENT;
$element[EN_CONTENT] = $this->getStringTableEntry($this->getMBUInt());
return $element;
case WBXML_LITERAL_A:
$element[EN_TYPE] = EN_TYPE_STARTTAG;
$element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
$element[EN_ATTRIBUTES] = $this->getAttributes();
$element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES;
return $element;
case WBXML_EXT_0:
case WBXML_EXT_1:
case WBXML_EXT_2:
continue;
case WBXML_OPAQUE:
$length = $this->getMBUInt(); $length = $this->getMBUInt();
$element[EN_TYPE] = EN_TYPE_CONTENT; $element[EN_TYPE] = EN_TYPE_CONTENT;
$element[EN_CONTENT] = $this->getOpaque($length); $element[EN_CONTENT] = $this->getOpaque($length);
return $element; return $element;
case WBXML_LITERAL_AC: case self::WBXML_ENTITY:
$element[EN_TYPE] = EN_TYPE_STARTTAG; case self::WBXML_LITERAL:
$element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); case self::WBXML_EXT_I_0:
$element[EN_ATTRIBUTES] = $this->getAttributes(); case self::WBXML_EXT_I_1:
$element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES | EN_FLAGS_CONTENT; case self::WBXML_EXT_I_2:
return $element; case self::WBXML_PI:
case self::WBXML_LITERAL_C:
case self::WBXML_EXT_T_0:
case self::WBXML_EXT_T_1:
case self::WBXML_EXT_T_2:
case self::WBXML_STR_T:
case self::WBXML_LITERAL_A:
case self::WBXML_EXT_0:
case self::WBXML_EXT_1:
case self::WBXML_EXT_2:
case self::WBXML_LITERAL_AC:
throw new WBXMLException("Invalid token :".$byte);
default: default:
if($byte & self::WBXML_WITH_ATTRIBUTES)
throw new WBXMLException("Attributes are not allowed :".$byte);
$element[EN_TYPE] = EN_TYPE_STARTTAG; $element[EN_TYPE] = EN_TYPE_STARTTAG;
$element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f); $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f);
$element[EN_FLAGS] = ($byte & 0x80 ? EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? EN_FLAGS_CONTENT : 0); $element[EN_FLAGS] = ($byte & self::WBXML_WITH_CONTENT ? EN_FLAGS_CONTENT : 0);
if($byte & 0x80)
$element[EN_ATTRIBUTES] = $this->getAttributes();
return $element; return $element;
} }
} }
} }
/**
* Gets attributes
*
* @access private
* @return
*/
private function getAttributes() {
$attributes = array();
$attr = "";
WBXMLDecoder::ResetInWhile("decoderGetAttributes");
while(WBXMLDecoder::InWhile("decoderGetAttributes")) {
$byte = $this->getByte();
if(count($byte) == 0)
break;
switch($byte) {
case WBXML_SWITCH_PAGE:
$this->attrcp = $this->getByte();
break;
case WBXML_END:
if($attr != "")
$attributes += $this->splitAttribute($attr);
return $attributes;
case WBXML_ENTITY:
$entity = $this->getMBUInt();
$attr .= $this->entityToCharset($entity);
return $attr; /* fmbiete's contribution r1534, ZP-324 */
case WBXML_STR_I:
$attr .= $this->getTermStr();
return $attr; /* fmbiete's contribution r1534, ZP-324 */
case WBXML_LITERAL:
if($attr != "")
$attributes += $this->splitAttribute($attr);
$attr = $this->getStringTableEntry($this->getMBUInt());
return $attr; /* fmbiete's contribution r1534, ZP-324 */
case WBXML_EXT_I_0:
case WBXML_EXT_I_1:
case WBXML_EXT_I_2:
$this->getTermStr();
continue;
case WBXML_PI:
case WBXML_LITERAL_C:
// Invalid
return false;
case WBXML_EXT_T_0:
case WBXML_EXT_T_1:
case WBXML_EXT_T_2:
$this->getMBUInt();
continue;
case WBXML_STR_T:
$attr .= $this->getStringTableEntry($this->getMBUInt());
return $attr; /* fmbiete's contribution r1534, ZP-324 */
case WBXML_LITERAL_A:
return false;
case WBXML_EXT_0:
case WBXML_EXT_1:
case WBXML_EXT_2:
continue;
case WBXML_OPAQUE:
$length = $this->getMBUInt();
$attr .= $this->getOpaque($length);
return $attr; /* fmbiete's contribution r1534, ZP-324 */
case WBXML_LITERAL_AC:
return false;
default:
if($byte < 128) {
if($attr != "") {
$attributes += $this->splitAttribute($attr);
$attr = "";
}
}
$attr .= $this->getMapping($this->attrcp, $byte);
break;
}
}
}
/**
* Splits an attribute
*
* @param string $attr attribute to be splitted
*
* @access private
* @return array
*/
private function splitAttribute($attr) {
$attributes = array();
$pos = strpos($attr,chr(61)); // equals sign
if($pos)
$attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1);
else
$attributes[$attr] = null;
return $attributes;
}
/** /**
* Reads from the stream until getting a string terminator * Reads from the stream until getting a string terminator
* *
...@@ -594,17 +413,12 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -594,17 +413,12 @@ class WBXMLDecoder extends WBXMLDefs {
* @return string * @return string
*/ */
private function getTermStr() { private function getTermStr() {
$str = ""; // there is no unlimited "length" for stream_get_line,
while(1) { // so we use a huge value for "length" param (1Gb)
$in = $this->getByte(); // (0 == PHP_SOCK_CHUNK_SIZE (8192))
// internaly php read at most PHP_SOCK_CHUNK_SIZE at a time,
if($in == 0) // so we can use a huge value for "length" without problem
break; return stream_get_line($this->in, 1073741824, "\0");
else
$str .= chr($in);
}
return $str;
} }
/** /**
...@@ -616,32 +430,12 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -616,32 +430,12 @@ class WBXMLDecoder extends WBXMLDefs {
* @return string * @return string
*/ */
private function getOpaque($len) { private function getOpaque($len) {
// TODO check if it's possible to do it other way $d = stream_get_contents($this->in, $len);
// fread stops reading because the following condition is true (from php.net): if ($d === false)
// if the stream is read buffered and it does not represent a plain file, throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): stream_get_contents === false", HTTP_CODE_500, null, LOGLEVEL_WARN);
// at most one read of up to a number of bytes equal to the chunk size $l = strlen($d);
// (usually 8192) is made; depending on the previously buffered data, if ($l !== $len)
// the size of the returned data may be larger than the chunk size. throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): only $l byte read instead of $len", HTTP_CODE_500, null, LOGLEVEL_WARN);
// using only return fread it will return only a part of stream if chunk is smaller
// than $len. Read from stream in a loop until the $len is reached.
$d = "";
$l = 0;
while (1) {
$l = (($len - strlen($d)) > 8192) ? 8192 : ($len - strlen($d));
if ($l > 0) {
$data = fread($this->in, $l);
// Stream ends prematurely on instable connections and big mails
if ($data === false || feof($this->in))
throw new HTTPReturnCodeException(sprintf("WBXMLDecoder->getOpaque() connection unavailable while trying to read %d bytes from stream. Aborting after %d bytes read.", $len, strlen($d)), HTTP_CODE_500, null, LOGLEVEL_WARN);
else {
$d .= $data;
fwrite($this->inLog, $data);
}
}
if (strlen($d) >= $len) break;
}
return $d; return $d;
} }
...@@ -653,10 +447,8 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -653,10 +447,8 @@ class WBXMLDecoder extends WBXMLDefs {
*/ */
private function getByte() { private function getByte() {
$ch = fread($this->in, 1); $ch = fread($this->in, 1);
if(strlen($ch) > 0) { if(strlen($ch) > 0)
fwrite($this->inLog, $ch);
return ord($ch); return ord($ch);
}
else else
return; return;
} }
...@@ -684,24 +476,6 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -684,24 +476,6 @@ class WBXMLDecoder extends WBXMLDefs {
return $uint; return $uint;
} }
/**
* Reads string table from the input stream
*
* @access private
* @return int
*/
private function getStringTable() {
$stringtable = "";
$length = $this->getMBUInt();
if($length > 0) {
$stringtable = fread($this->in, $length);
fwrite($this->inLog, $stringtable);
}
return $stringtable;
}
/** /**
* Returns the mapping for a specified codepage and id * Returns the mapping for a specified codepage and id
* *
...@@ -721,19 +495,4 @@ class WBXMLDecoder extends WBXMLDefs { ...@@ -721,19 +495,4 @@ class WBXMLDecoder extends WBXMLDefs {
return $this->dtd["codes"][$cp][$id]; return $this->dtd["codes"][$cp][$id];
} }
} }
/**
* Reads one byte from the input stream
*
* @access private
* @return void
*/
private function readVersion() {
$ch = $this->getByte();
if($ch != NULL) {
$this->inputBuffer .= chr($ch);
$this->version = $ch;
}
}
} }
...@@ -42,26 +42,7 @@ ...@@ -42,26 +42,7 @@
************************************************/ ************************************************/
define('WBXML_SWITCH_PAGE', 0x00);
define('WBXML_END', 0x01);
define('WBXML_ENTITY', 0x02);
define('WBXML_STR_I', 0x03);
define('WBXML_LITERAL', 0x04);
define('WBXML_EXT_I_0', 0x40);
define('WBXML_EXT_I_1', 0x41);
define('WBXML_EXT_I_2', 0x42);
define('WBXML_PI', 0x43);
define('WBXML_LITERAL_C', 0x44);
define('WBXML_EXT_T_0', 0x80);
define('WBXML_EXT_T_1', 0x81);
define('WBXML_EXT_T_2', 0x82);
define('WBXML_STR_T', 0x83);
define('WBXML_LITERAL_A', 0x84);
define('WBXML_EXT_0', 0xC0);
define('WBXML_EXT_1', 0xC1);
define('WBXML_EXT_2', 0xC2);
define('WBXML_OPAQUE', 0xC3);
define('WBXML_LITERAL_AC', 0xC4);
define('EN_TYPE', 1); define('EN_TYPE', 1);
define('EN_TAG', 2); define('EN_TAG', 2);
...@@ -77,6 +58,31 @@ define('EN_FLAGS_CONTENT', 1); ...@@ -77,6 +58,31 @@ define('EN_FLAGS_CONTENT', 1);
define('EN_FLAGS_ATTRIBUTES', 2); define('EN_FLAGS_ATTRIBUTES', 2);
class WBXMLDefs { class WBXMLDefs {
const WBXML_SWITCH_PAGE = 0x00;
const WBXML_END = 0x01;
const WBXML_ENTITY = 0x02; //not used in ActiveSync
const WBXML_STR_I = 0x03;
const WBXML_LITERAL = 0x04; //not used in ActiveSync
const WBXML_EXT_I_0 = 0x40; //not used in ActiveSync
const WBXML_EXT_I_1 = 0x41; //not used in ActiveSync
const WBXML_EXT_I_2 = 0x42; //not used in ActiveSync
const WBXML_PI = 0x43; //not used in ActiveSync
const WBXML_LITERAL_C = 0x44; //not used in ActiveSync
const WBXML_EXT_T_0 = 0x80; //not used in ActiveSync
const WBXML_EXT_T_1 = 0x81; //not used in ActiveSync
const WBXML_EXT_T_2 = 0x82; //not used in ActiveSync
const WBXML_STR_T = 0x83; //not used in ActiveSync
const WBXML_LITERAL_A = 0x84; //not used in ActiveSync
const WBXML_EXT_0 = 0xC0; //not used in ActiveSync
const WBXML_EXT_1 = 0xC1; //not used in ActiveSync
const WBXML_EXT_2 = 0xC2; //not used in ActiveSync
const WBXML_OPAQUE = 0xC3;
const WBXML_LITERAL_AC = 0xC4; //not used in ActiveSync
const WBXML_WITH_ATTRIBUTES = 0x80; //not used in ActiveSync
const WBXML_WITH_CONTENT = 0x40;
/** /**
* The WBXML DTDs * The WBXML DTDs
*/ */
......
...@@ -45,11 +45,10 @@ ...@@ -45,11 +45,10 @@
class WBXMLEncoder extends WBXMLDefs { class WBXMLEncoder extends WBXMLDefs {
private $_dtd; private $_dtd;
private $_out; private $_out;
private $_outLog;
private $_tagcp; private $_tagcp = 0;
private $_attrcp;
private $log = false;
private $logStack = array(); private $logStack = array();
// We use a delayed output mechanism in which we only output a tag when it actually has something // We use a delayed output mechanism in which we only output a tag when it actually has something
...@@ -64,14 +63,9 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -64,14 +63,9 @@ class WBXMLEncoder extends WBXMLDefs {
private $bodyparts; private $bodyparts;
public function WBXMLEncoder($output, $multipart = false) { public function WBXMLEncoder($output, $multipart = false) {
// make sure WBXML_DEBUG is defined. It should be at this point $this->log = @constant('WBXML_DEBUG') === true;
if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false);
$this->_out = $output; $this->_out = $output;
$this->_outLog = StringStreamWrapper::Open("");
$this->_tagcp = 0;
$this->_attrcp = 0;
// reverse-map the DTD // reverse-map the DTD
foreach($this->dtd["namespaces"] as $nsid => $nsname) { foreach($this->dtd["namespaces"] as $nsid => $nsname) {
...@@ -126,7 +120,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -126,7 +120,6 @@ class WBXMLEncoder extends WBXMLDefs {
if(!$nocontent) { if(!$nocontent) {
$stackelem['tag'] = $tag; $stackelem['tag'] = $tag;
$stackelem['attributes'] = $attributes;
$stackelem['nocontent'] = $nocontent; $stackelem['nocontent'] = $nocontent;
$stackelem['sent'] = false; $stackelem['sent'] = false;
...@@ -136,7 +129,7 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -136,7 +129,7 @@ class WBXMLEncoder extends WBXMLDefs {
// output of an empty tag, and we therefore output the stack here // output of an empty tag, and we therefore output the stack here
} else { } else {
$this->_outputStack(); $this->_outputStack();
$this->_startTag($tag, $attributes, $nocontent); $this->_startTag($tag, $nocontent);
} }
} }
...@@ -225,6 +218,8 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -225,6 +218,8 @@ class WBXMLEncoder extends WBXMLDefs {
* @return void * @return void
*/ */
public function addBodypartStream($bp) { public function addBodypartStream($bp) {
if (!is_resource($bp))
throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a ".gettype($bp)." instead of a stream");
if ($this->multipart) if ($this->multipart)
$this->bodyparts[] = $bp; $this->bodyparts[] = $bp;
} }
...@@ -252,7 +247,7 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -252,7 +247,7 @@ class WBXMLEncoder extends WBXMLDefs {
private function _outputStack() { private function _outputStack() {
for($i=0;$i<count($this->_stack);$i++) { for($i=0;$i<count($this->_stack);$i++) {
if(!$this->_stack[$i]['sent']) { if(!$this->_stack[$i]['sent']) {
$this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']); $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['nocontent']);
$this->_stack[$i]['sent'] = true; $this->_stack[$i]['sent'] = true;
} }
} }
...@@ -264,8 +259,9 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -264,8 +259,9 @@ class WBXMLEncoder extends WBXMLDefs {
* @access private * @access private
* @return * @return
*/ */
private function _startTag($tag, $attributes = false, $nocontent = false) { private function _startTag($tag, $nocontent = false) {
$this->logStartTag($tag, $attributes, $nocontent); if ($this->log)
$this->logStartTag($tag, $nocontent);
$mapping = $this->getMapping($tag); $mapping = $this->getMapping($tag);
...@@ -278,17 +274,11 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -278,17 +274,11 @@ class WBXMLEncoder extends WBXMLDefs {
} }
$code = $mapping["code"]; $code = $mapping["code"];
if(isset($attributes) && is_array($attributes) && count($attributes) > 0) {
$code |= 0x80;
}
if(!isset($nocontent) || !$nocontent) if(!isset($nocontent) || !$nocontent)
$code |= 0x40; $code |= 0x40;
$this->outByte($code); $this->outByte($code);
if($code & 0x80)
$this->outAttributes($attributes);
} }
/** /**
...@@ -299,8 +289,9 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -299,8 +289,9 @@ class WBXMLEncoder extends WBXMLDefs {
* @return * @return
*/ */
private function _content($content) { private function _content($content) {
$this->logContent($content); if ($this->log)
$this->outByte(WBXML_STR_I); $this->logContent($content);
$this->outByte(self::WBXML_STR_I);
$this->outTermStr($content); $this->outTermStr($content);
} }
...@@ -314,7 +305,7 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -314,7 +305,7 @@ class WBXMLEncoder extends WBXMLDefs {
*/ */
private function _contentStream($stream, $asBase64) { private function _contentStream($stream, $asBase64) {
// write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do) // write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do)
$this->outByte(WBXML_STR_I); $this->outByte(self::WBXML_STR_I);
fseek($stream, 0, SEEK_SET); fseek($stream, 0, SEEK_SET);
if ($asBase64) { if ($asBase64) {
$out_filter = stream_filter_append($this->_out, 'convert.base64-encode'); $out_filter = stream_filter_append($this->_out, 'convert.base64-encode');
...@@ -325,14 +316,11 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -325,14 +316,11 @@ class WBXMLEncoder extends WBXMLDefs {
} }
fwrite($this->_out, chr(0)); fwrite($this->_out, chr(0));
// data is out, do some logging if ($this->log) {
$stat = fstat($stream); // data is out, do some logging
$logContent = sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded":"plain"); $stat = fstat($stream);
$this->logContent($logContent); $this->logContent(sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded":"plain"));
}
// write the meta data also to the _outLog stream, WBXML_STR_I was already written by outByte() above
fwrite($this->_outLog, $logContent);
fwrite($this->_outLog, chr(0));
} }
/** /**
...@@ -342,8 +330,9 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -342,8 +330,9 @@ class WBXMLEncoder extends WBXMLDefs {
* @return * @return
*/ */
private function _endTag() { private function _endTag() {
$this->logEndTag(); if ($this->log)
$this->outByte(WBXML_END); $this->logEndTag();
$this->outByte(self::WBXML_END);
} }
/** /**
...@@ -356,7 +345,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -356,7 +345,6 @@ class WBXMLEncoder extends WBXMLDefs {
*/ */
private function outByte($byte) { private function outByte($byte) {
fwrite($this->_out, chr($byte)); fwrite($this->_out, chr($byte));
fwrite($this->_outLog, chr($byte));
} }
/** /**
...@@ -391,28 +379,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -391,28 +379,6 @@ class WBXMLEncoder extends WBXMLDefs {
private function outTermStr($content) { private function outTermStr($content) {
fwrite($this->_out, $content); fwrite($this->_out, $content);
fwrite($this->_out, chr(0)); fwrite($this->_out, chr(0));
// truncate data bigger than 10 KB on outLog
$l = strlen($content);
if ($l > 10240) {
$content = substr($content, 0, 5120) . sprintf("...<data with total %d bytes truncated>...", $l) . substr($content, -5120);
}
fwrite($this->_outLog, $content);
fwrite($this->_outLog, chr(0));
}
/**
* Output attributes
* We don't actually support this, because to do so, we would have
* to build a string table before sending the data (but we can't
* because we're streaming), so we'll just send an END, which just
* terminates the attribute list with 0 attributes.
*
* @access private
* @return
*/
private function outAttributes() {
$this->outByte(WBXML_END);
} }
/** /**
...@@ -424,7 +390,7 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -424,7 +390,7 @@ class WBXMLEncoder extends WBXMLDefs {
* @return * @return
*/ */
private function outSwitchPage($page) { private function outSwitchPage($page) {
$this->outByte(WBXML_SWITCH_PAGE); $this->outByte(self::WBXML_SWITCH_PAGE);
$this->outByte($page); $this->outByte($page);
} }
...@@ -488,16 +454,12 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -488,16 +454,12 @@ class WBXMLEncoder extends WBXMLDefs {
* Logs a StartTag to ZLog * Logs a StartTag to ZLog
* *
* @param $tag * @param $tag
* @param $attr
* @param $nocontent * @param $nocontent
* *
* @access private * @access private
* @return * @return
*/ */
private function logStartTag($tag, $attr, $nocontent) { private function logStartTag($tag, $nocontent) {
if(!WBXML_DEBUG)
return;
$spaces = str_repeat(" ", count($this->logStack)); $spaces = str_repeat(" ", count($this->logStack));
if($nocontent) if($nocontent)
ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag/>"); ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag/>");
...@@ -514,9 +476,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -514,9 +476,6 @@ class WBXMLEncoder extends WBXMLDefs {
* @return * @return
*/ */
private function logEndTag() { private function logEndTag() {
if(!WBXML_DEBUG)
return;
$spaces = str_repeat(" ", count($this->logStack)); $spaces = str_repeat(" ", count($this->logStack));
$tag = array_pop($this->logStack); $tag = array_pop($this->logStack);
ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . "</$tag>"); ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . "</$tag>");
...@@ -531,9 +490,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -531,9 +490,6 @@ class WBXMLEncoder extends WBXMLDefs {
* @return * @return
*/ */
private function logContent($content) { private function logContent($content) {
if(!WBXML_DEBUG)
return;
$spaces = str_repeat(" ", count($this->logStack)); $spaces = str_repeat(" ", count($this->logStack));
ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . $content); ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . $content);
} }
...@@ -551,28 +507,20 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -551,28 +507,20 @@ class WBXMLEncoder extends WBXMLDefs {
$nrBodyparts = $this->getBodypartsCount(); $nrBodyparts = $this->getBodypartsCount();
$blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4;
$data = pack("iii", ($nrBodyparts + 1), $blockstart, $len); fwrite($this->_out, pack("iii", ($nrBodyparts + 1), $blockstart, $len));
ob_start(null, 1048576);
foreach ($this->bodyparts as $bp) { foreach ($this->bodyparts as $bp) {
$blockstart = $blockstart + $len; $blockstart = $blockstart + $len;
$len = fstat($bp); $len = fstat($bp);
$len = (isset($len['size'])) ? $len['size'] : 0; $len = (isset($len['size'])) ? $len['size'] : 0;
$data .= pack("ii", $blockstart, $len); fwrite($this->_out, pack("ii", $blockstart, $len));
} }
fwrite($this->_out, $data);
fwrite($this->_out, $buffer); fwrite($this->_out, $buffer);
fwrite($this->_outLog, $data);
fwrite($this->_outLog, $buffer);
foreach($this->bodyparts as $bp) { foreach($this->bodyparts as $bp) {
while (!feof($bp)) { stream_copy_to_stream($bp, $this->_out);
$out = fread($bp, 4096); fclose($bp);
fwrite($this->_out, $out);
fwrite($this->_outLog, $out);
}
} }
} }
...@@ -583,11 +531,11 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -583,11 +531,11 @@ class WBXMLEncoder extends WBXMLDefs {
* @return void * @return void
*/ */
private function writeLog() { private function writeLog() {
$stat = fstat($this->_outLog); if (ob_get_length() === false) {
if ($stat['size'] < 524288) { $data = "output buffer disabled";
$data = base64_encode(stream_get_contents($this->_outLog, -1,0)); } elseif (ob_get_length() < 524288) {
} $data = base64_encode(ob_get_contents());
else { } else {
$data = "more than 512K of data"; $data = "more than 512K of data";
} }
ZLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: ". $data, false); ZLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: ". $data, false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment