17f8f2456SAndreas Gohr<?php 27f8f2456SAndreas Gohrnamespace IXR\Message; 37f8f2456SAndreas Gohr 47f8f2456SAndreas Gohr 57f8f2456SAndreas Gohruse IXR\DataType\Date; 67f8f2456SAndreas Gohr 77f8f2456SAndreas Gohrclass Message 87f8f2456SAndreas Gohr{ 97f8f2456SAndreas Gohr public $message; 107f8f2456SAndreas Gohr public $messageType; // methodCall / methodResponse / fault 117f8f2456SAndreas Gohr public $faultCode; 127f8f2456SAndreas Gohr public $faultString; 137f8f2456SAndreas Gohr public $methodName; 147f8f2456SAndreas Gohr public $params; 157f8f2456SAndreas Gohr 167f8f2456SAndreas Gohr // Current variable stacks 177f8f2456SAndreas Gohr private $_arraystructs = []; // The stack used to keep track of the current array/struct 187f8f2456SAndreas Gohr private $_arraystructstypes = []; // Stack keeping track of if things are structs or array 197f8f2456SAndreas Gohr private $_currentStructName = []; // A stack as well 207f8f2456SAndreas Gohr private $_param; 217f8f2456SAndreas Gohr private $_value; 227f8f2456SAndreas Gohr private $_currentTag; 237f8f2456SAndreas Gohr private $_currentTagContents; 247f8f2456SAndreas Gohr // The XML parser 257f8f2456SAndreas Gohr private $_parser; 267f8f2456SAndreas Gohr 277f8f2456SAndreas Gohr public function __construct($message) 287f8f2456SAndreas Gohr { 297f8f2456SAndreas Gohr $this->message =& $message; 307f8f2456SAndreas Gohr } 317f8f2456SAndreas Gohr 327f8f2456SAndreas Gohr public function parse() 337f8f2456SAndreas Gohr { 347f8f2456SAndreas Gohr // first remove the XML declaration 357f8f2456SAndreas Gohr // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages 367f8f2456SAndreas Gohr $header = preg_replace('/<\?xml.*?\?' . '>/s', '', substr($this->message, 0, 100), 1); 377f8f2456SAndreas Gohr $this->message = trim(substr_replace($this->message, $header, 0, 100)); 387f8f2456SAndreas Gohr if ('' == $this->message) { 397f8f2456SAndreas Gohr return false; 407f8f2456SAndreas Gohr } 417f8f2456SAndreas Gohr 427f8f2456SAndreas Gohr // Then remove the DOCTYPE 437f8f2456SAndreas Gohr $header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($this->message, 0, 200), 1); 447f8f2456SAndreas Gohr $this->message = trim(substr_replace($this->message, $header, 0, 200)); 457f8f2456SAndreas Gohr if ('' == $this->message) { 467f8f2456SAndreas Gohr return false; 477f8f2456SAndreas Gohr } 487f8f2456SAndreas Gohr 497f8f2456SAndreas Gohr // Check that the root tag is valid 507f8f2456SAndreas Gohr $root_tag = substr($this->message, 0, strcspn(substr($this->message, 0, 20), "> \t\r\n")); 517f8f2456SAndreas Gohr if ('<!DOCTYPE' === strtoupper($root_tag)) { 527f8f2456SAndreas Gohr return false; 537f8f2456SAndreas Gohr } 547f8f2456SAndreas Gohr if (!in_array($root_tag, ['<methodCall', '<methodResponse', '<fault'])) { 557f8f2456SAndreas Gohr return false; 567f8f2456SAndreas Gohr } 577f8f2456SAndreas Gohr 587f8f2456SAndreas Gohr // Bail if there are too many elements to parse 597f8f2456SAndreas Gohr $element_limit = 30000; 607f8f2456SAndreas Gohr if ($element_limit && 2 * $element_limit < substr_count($this->message, '<')) { 617f8f2456SAndreas Gohr return false; 627f8f2456SAndreas Gohr } 637f8f2456SAndreas Gohr 647f8f2456SAndreas Gohr $this->_parser = xml_parser_create(); 657f8f2456SAndreas Gohr // Set XML parser to take the case of tags in to account 667f8f2456SAndreas Gohr xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 677f8f2456SAndreas Gohr // Set XML parser callback functions 68*934f970aSAndreas Gohr xml_set_element_handler($this->_parser, [$this, 'tagOpen'], [$this, 'tagClose']); 69*934f970aSAndreas Gohr xml_set_character_data_handler($this->_parser, [$this, 'cdata']); 707f8f2456SAndreas Gohr $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 717f8f2456SAndreas Gohr $final = false; 727f8f2456SAndreas Gohr do { 737f8f2456SAndreas Gohr if (strlen($this->message) <= $chunk_size) { 747f8f2456SAndreas Gohr $final = true; 757f8f2456SAndreas Gohr } 767f8f2456SAndreas Gohr $part = substr($this->message, 0, $chunk_size); 777f8f2456SAndreas Gohr $this->message = substr($this->message, $chunk_size); 787f8f2456SAndreas Gohr if (!xml_parse($this->_parser, $part, $final)) { 797f8f2456SAndreas Gohr return false; 807f8f2456SAndreas Gohr } 817f8f2456SAndreas Gohr if ($final) { 827f8f2456SAndreas Gohr break; 837f8f2456SAndreas Gohr } 847f8f2456SAndreas Gohr } while (true); 857f8f2456SAndreas Gohr xml_parser_free($this->_parser); 867f8f2456SAndreas Gohr 877f8f2456SAndreas Gohr // Grab the error messages, if any 887f8f2456SAndreas Gohr if ($this->messageType === 'fault') { 897f8f2456SAndreas Gohr $this->faultCode = $this->params[0]['faultCode']; 907f8f2456SAndreas Gohr $this->faultString = $this->params[0]['faultString']; 917f8f2456SAndreas Gohr } 927f8f2456SAndreas Gohr return true; 937f8f2456SAndreas Gohr } 947f8f2456SAndreas Gohr 957f8f2456SAndreas Gohr /** 967f8f2456SAndreas Gohr * Opening tag handler 977f8f2456SAndreas Gohr * @param $parser 987f8f2456SAndreas Gohr * @param $tag 997f8f2456SAndreas Gohr * @param $attr 1007f8f2456SAndreas Gohr */ 1017f8f2456SAndreas Gohr public function tagOpen($parser, $tag, $attr) 1027f8f2456SAndreas Gohr { 1037f8f2456SAndreas Gohr $this->_currentTagContents = ''; 10464d8abdbSAndreas Gohr $this->_currentTag = $tag; 1057f8f2456SAndreas Gohr switch ($tag) { 1067f8f2456SAndreas Gohr case 'methodCall': 1077f8f2456SAndreas Gohr case 'methodResponse': 1087f8f2456SAndreas Gohr case 'fault': 1097f8f2456SAndreas Gohr $this->messageType = $tag; 1107f8f2456SAndreas Gohr break; 1117f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 112*934f970aSAndreas Gohr case 'data': // data is to all intents and purposes more interesting than array 1137f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'array'; 1147f8f2456SAndreas Gohr $this->_arraystructs[] = []; 1157f8f2456SAndreas Gohr break; 1167f8f2456SAndreas Gohr case 'struct': 1177f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'struct'; 1187f8f2456SAndreas Gohr $this->_arraystructs[] = []; 1197f8f2456SAndreas Gohr break; 1207f8f2456SAndreas Gohr } 1217f8f2456SAndreas Gohr } 1227f8f2456SAndreas Gohr 1237f8f2456SAndreas Gohr /** 1247f8f2456SAndreas Gohr * Character Data handler 1257f8f2456SAndreas Gohr * @param $parser 1267f8f2456SAndreas Gohr * @param $cdata 1277f8f2456SAndreas Gohr */ 1287f8f2456SAndreas Gohr public function cdata($parser, $cdata) 1297f8f2456SAndreas Gohr { 1307f8f2456SAndreas Gohr $this->_currentTagContents .= $cdata; 1317f8f2456SAndreas Gohr } 1327f8f2456SAndreas Gohr 1337f8f2456SAndreas Gohr /** 1347f8f2456SAndreas Gohr * Closing tag handler 1357f8f2456SAndreas Gohr * @param $parser 1367f8f2456SAndreas Gohr * @param $tag 1377f8f2456SAndreas Gohr */ 1387f8f2456SAndreas Gohr public function tagClose($parser, $tag) 1397f8f2456SAndreas Gohr { 1407f8f2456SAndreas Gohr $valueFlag = false; 1417f8f2456SAndreas Gohr switch ($tag) { 1427f8f2456SAndreas Gohr case 'int': 1437f8f2456SAndreas Gohr case 'i4': 1447f8f2456SAndreas Gohr $value = (int)trim($this->_currentTagContents); 1457f8f2456SAndreas Gohr $valueFlag = true; 1467f8f2456SAndreas Gohr break; 1477f8f2456SAndreas Gohr case 'double': 1487f8f2456SAndreas Gohr $value = (double)trim($this->_currentTagContents); 1497f8f2456SAndreas Gohr $valueFlag = true; 1507f8f2456SAndreas Gohr break; 1517f8f2456SAndreas Gohr case 'string': 1527f8f2456SAndreas Gohr $value = (string)($this->_currentTagContents); 1537f8f2456SAndreas Gohr $valueFlag = true; 1547f8f2456SAndreas Gohr break; 1557f8f2456SAndreas Gohr case 'dateTime.iso8601': 1567f8f2456SAndreas Gohr $value = new Date(trim($this->_currentTagContents)); 1577f8f2456SAndreas Gohr $valueFlag = true; 1587f8f2456SAndreas Gohr break; 1597f8f2456SAndreas Gohr case 'value': 1607f8f2456SAndreas Gohr // "If no type is indicated, the type is string." 1617f8f2456SAndreas Gohr if (trim($this->_currentTagContents) != '') { 1627f8f2456SAndreas Gohr $value = (string)$this->_currentTagContents; 1637f8f2456SAndreas Gohr $valueFlag = true; 1647f8f2456SAndreas Gohr } 1657f8f2456SAndreas Gohr break; 1667f8f2456SAndreas Gohr case 'boolean': 1677f8f2456SAndreas Gohr $value = (boolean)trim($this->_currentTagContents); 1687f8f2456SAndreas Gohr $valueFlag = true; 1697f8f2456SAndreas Gohr break; 1707f8f2456SAndreas Gohr case 'base64': 1717f8f2456SAndreas Gohr $value = base64_decode($this->_currentTagContents); 1727f8f2456SAndreas Gohr $valueFlag = true; 1737f8f2456SAndreas Gohr break; 1747f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 1757f8f2456SAndreas Gohr case 'data': 1767f8f2456SAndreas Gohr case 'struct': 1777f8f2456SAndreas Gohr $value = array_pop($this->_arraystructs); 1787f8f2456SAndreas Gohr array_pop($this->_arraystructstypes); 1797f8f2456SAndreas Gohr $valueFlag = true; 1807f8f2456SAndreas Gohr break; 1817f8f2456SAndreas Gohr case 'member': 1827f8f2456SAndreas Gohr array_pop($this->_currentStructName); 1837f8f2456SAndreas Gohr break; 1847f8f2456SAndreas Gohr case 'name': 1857f8f2456SAndreas Gohr $this->_currentStructName[] = trim($this->_currentTagContents); 1867f8f2456SAndreas Gohr break; 1877f8f2456SAndreas Gohr case 'methodName': 1887f8f2456SAndreas Gohr $this->methodName = trim($this->_currentTagContents); 1897f8f2456SAndreas Gohr break; 1907f8f2456SAndreas Gohr } 1917f8f2456SAndreas Gohr 1927f8f2456SAndreas Gohr if ($valueFlag) { 1937f8f2456SAndreas Gohr if (count($this->_arraystructs) > 0) { 1947f8f2456SAndreas Gohr // Add value to struct or array 1957f8f2456SAndreas Gohr if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') { 1967f8f2456SAndreas Gohr // Add to struct 1977f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; 1987f8f2456SAndreas Gohr } else { 1997f8f2456SAndreas Gohr // Add to array 2007f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; 2017f8f2456SAndreas Gohr } 2027f8f2456SAndreas Gohr } else { 203*934f970aSAndreas Gohr // Just add as a parameter 2047f8f2456SAndreas Gohr $this->params[] = $value; 2057f8f2456SAndreas Gohr } 2067f8f2456SAndreas Gohr } 2077f8f2456SAndreas Gohr $this->_currentTagContents = ''; 2087f8f2456SAndreas Gohr } 2097f8f2456SAndreas Gohr} 210