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 687f8f2456SAndreas Gohr xml_set_object($this->_parser, $this); 697f8f2456SAndreas Gohr xml_set_element_handler($this->_parser, 'tagOpen', 'tagClose'); 707f8f2456SAndreas Gohr xml_set_character_data_handler($this->_parser, 'cdata'); 717f8f2456SAndreas Gohr $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 727f8f2456SAndreas Gohr $final = false; 737f8f2456SAndreas Gohr do { 747f8f2456SAndreas Gohr if (strlen($this->message) <= $chunk_size) { 757f8f2456SAndreas Gohr $final = true; 767f8f2456SAndreas Gohr } 777f8f2456SAndreas Gohr $part = substr($this->message, 0, $chunk_size); 787f8f2456SAndreas Gohr $this->message = substr($this->message, $chunk_size); 797f8f2456SAndreas Gohr if (!xml_parse($this->_parser, $part, $final)) { 807f8f2456SAndreas Gohr return false; 817f8f2456SAndreas Gohr } 827f8f2456SAndreas Gohr if ($final) { 837f8f2456SAndreas Gohr break; 847f8f2456SAndreas Gohr } 857f8f2456SAndreas Gohr } while (true); 867f8f2456SAndreas Gohr xml_parser_free($this->_parser); 877f8f2456SAndreas Gohr 887f8f2456SAndreas Gohr // Grab the error messages, if any 897f8f2456SAndreas Gohr if ($this->messageType === 'fault') { 907f8f2456SAndreas Gohr $this->faultCode = $this->params[0]['faultCode']; 917f8f2456SAndreas Gohr $this->faultString = $this->params[0]['faultString']; 927f8f2456SAndreas Gohr } 937f8f2456SAndreas Gohr return true; 947f8f2456SAndreas Gohr } 957f8f2456SAndreas Gohr 967f8f2456SAndreas Gohr /** 977f8f2456SAndreas Gohr * Opening tag handler 987f8f2456SAndreas Gohr * @param $parser 997f8f2456SAndreas Gohr * @param $tag 1007f8f2456SAndreas Gohr * @param $attr 1017f8f2456SAndreas Gohr */ 1027f8f2456SAndreas Gohr public function tagOpen($parser, $tag, $attr) 1037f8f2456SAndreas Gohr { 1047f8f2456SAndreas Gohr $this->_currentTagContents = ''; 105*64d8abdbSAndreas Gohr $this->_currentTag = $tag; 1067f8f2456SAndreas Gohr switch ($tag) { 1077f8f2456SAndreas Gohr case 'methodCall': 1087f8f2456SAndreas Gohr case 'methodResponse': 1097f8f2456SAndreas Gohr case 'fault': 1107f8f2456SAndreas Gohr $this->messageType = $tag; 1117f8f2456SAndreas Gohr break; 1127f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 1137f8f2456SAndreas Gohr case 'data': // data is to all intents and puposes more interesting than array 1147f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'array'; 1157f8f2456SAndreas Gohr $this->_arraystructs[] = []; 1167f8f2456SAndreas Gohr break; 1177f8f2456SAndreas Gohr case 'struct': 1187f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'struct'; 1197f8f2456SAndreas Gohr $this->_arraystructs[] = []; 1207f8f2456SAndreas Gohr break; 1217f8f2456SAndreas Gohr } 1227f8f2456SAndreas Gohr } 1237f8f2456SAndreas Gohr 1247f8f2456SAndreas Gohr /** 1257f8f2456SAndreas Gohr * Character Data handler 1267f8f2456SAndreas Gohr * @param $parser 1277f8f2456SAndreas Gohr * @param $cdata 1287f8f2456SAndreas Gohr */ 1297f8f2456SAndreas Gohr public function cdata($parser, $cdata) 1307f8f2456SAndreas Gohr { 1317f8f2456SAndreas Gohr $this->_currentTagContents .= $cdata; 1327f8f2456SAndreas Gohr } 1337f8f2456SAndreas Gohr 1347f8f2456SAndreas Gohr /** 1357f8f2456SAndreas Gohr * Closing tag handler 1367f8f2456SAndreas Gohr * @param $parser 1377f8f2456SAndreas Gohr * @param $tag 1387f8f2456SAndreas Gohr */ 1397f8f2456SAndreas Gohr public function tagClose($parser, $tag) 1407f8f2456SAndreas Gohr { 1417f8f2456SAndreas Gohr $valueFlag = false; 1427f8f2456SAndreas Gohr switch ($tag) { 1437f8f2456SAndreas Gohr case 'int': 1447f8f2456SAndreas Gohr case 'i4': 1457f8f2456SAndreas Gohr $value = (int)trim($this->_currentTagContents); 1467f8f2456SAndreas Gohr $valueFlag = true; 1477f8f2456SAndreas Gohr break; 1487f8f2456SAndreas Gohr case 'double': 1497f8f2456SAndreas Gohr $value = (double)trim($this->_currentTagContents); 1507f8f2456SAndreas Gohr $valueFlag = true; 1517f8f2456SAndreas Gohr break; 1527f8f2456SAndreas Gohr case 'string': 1537f8f2456SAndreas Gohr $value = (string)($this->_currentTagContents); 1547f8f2456SAndreas Gohr $valueFlag = true; 1557f8f2456SAndreas Gohr break; 1567f8f2456SAndreas Gohr case 'dateTime.iso8601': 1577f8f2456SAndreas Gohr $value = new Date(trim($this->_currentTagContents)); 1587f8f2456SAndreas Gohr $valueFlag = true; 1597f8f2456SAndreas Gohr break; 1607f8f2456SAndreas Gohr case 'value': 1617f8f2456SAndreas Gohr // "If no type is indicated, the type is string." 1627f8f2456SAndreas Gohr if (trim($this->_currentTagContents) != '') { 1637f8f2456SAndreas Gohr $value = (string)$this->_currentTagContents; 1647f8f2456SAndreas Gohr $valueFlag = true; 1657f8f2456SAndreas Gohr } 1667f8f2456SAndreas Gohr break; 1677f8f2456SAndreas Gohr case 'boolean': 1687f8f2456SAndreas Gohr $value = (boolean)trim($this->_currentTagContents); 1697f8f2456SAndreas Gohr $valueFlag = true; 1707f8f2456SAndreas Gohr break; 1717f8f2456SAndreas Gohr case 'base64': 1727f8f2456SAndreas Gohr $value = base64_decode($this->_currentTagContents); 1737f8f2456SAndreas Gohr $valueFlag = true; 1747f8f2456SAndreas Gohr break; 1757f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 1767f8f2456SAndreas Gohr case 'data': 1777f8f2456SAndreas Gohr case 'struct': 1787f8f2456SAndreas Gohr $value = array_pop($this->_arraystructs); 1797f8f2456SAndreas Gohr array_pop($this->_arraystructstypes); 1807f8f2456SAndreas Gohr $valueFlag = true; 1817f8f2456SAndreas Gohr break; 1827f8f2456SAndreas Gohr case 'member': 1837f8f2456SAndreas Gohr array_pop($this->_currentStructName); 1847f8f2456SAndreas Gohr break; 1857f8f2456SAndreas Gohr case 'name': 1867f8f2456SAndreas Gohr $this->_currentStructName[] = trim($this->_currentTagContents); 1877f8f2456SAndreas Gohr break; 1887f8f2456SAndreas Gohr case 'methodName': 1897f8f2456SAndreas Gohr $this->methodName = trim($this->_currentTagContents); 1907f8f2456SAndreas Gohr break; 1917f8f2456SAndreas Gohr } 1927f8f2456SAndreas Gohr 1937f8f2456SAndreas Gohr if ($valueFlag) { 1947f8f2456SAndreas Gohr if (count($this->_arraystructs) > 0) { 1957f8f2456SAndreas Gohr // Add value to struct or array 1967f8f2456SAndreas Gohr if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') { 1977f8f2456SAndreas Gohr // Add to struct 1987f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; 1997f8f2456SAndreas Gohr } else { 2007f8f2456SAndreas Gohr // Add to array 2017f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; 2027f8f2456SAndreas Gohr } 2037f8f2456SAndreas Gohr } else { 2047f8f2456SAndreas Gohr // Just add as a paramater 2057f8f2456SAndreas Gohr $this->params[] = $value; 2067f8f2456SAndreas Gohr } 2077f8f2456SAndreas Gohr } 2087f8f2456SAndreas Gohr $this->_currentTagContents = ''; 2097f8f2456SAndreas Gohr } 2107f8f2456SAndreas Gohr} 211