1*7f8f2456SAndreas Gohr<?php 2*7f8f2456SAndreas Gohrnamespace IXR\Message; 3*7f8f2456SAndreas Gohr 4*7f8f2456SAndreas Gohr 5*7f8f2456SAndreas Gohruse IXR\DataType\Date; 6*7f8f2456SAndreas Gohr 7*7f8f2456SAndreas Gohrclass Message 8*7f8f2456SAndreas Gohr{ 9*7f8f2456SAndreas Gohr public $message; 10*7f8f2456SAndreas Gohr public $messageType; // methodCall / methodResponse / fault 11*7f8f2456SAndreas Gohr public $faultCode; 12*7f8f2456SAndreas Gohr public $faultString; 13*7f8f2456SAndreas Gohr public $methodName; 14*7f8f2456SAndreas Gohr public $params; 15*7f8f2456SAndreas Gohr 16*7f8f2456SAndreas Gohr // Current variable stacks 17*7f8f2456SAndreas Gohr private $_arraystructs = []; // The stack used to keep track of the current array/struct 18*7f8f2456SAndreas Gohr private $_arraystructstypes = []; // Stack keeping track of if things are structs or array 19*7f8f2456SAndreas Gohr private $_currentStructName = []; // A stack as well 20*7f8f2456SAndreas Gohr private $_param; 21*7f8f2456SAndreas Gohr private $_value; 22*7f8f2456SAndreas Gohr private $_currentTag; 23*7f8f2456SAndreas Gohr private $_currentTagContents; 24*7f8f2456SAndreas Gohr // The XML parser 25*7f8f2456SAndreas Gohr private $_parser; 26*7f8f2456SAndreas Gohr 27*7f8f2456SAndreas Gohr public function __construct($message) 28*7f8f2456SAndreas Gohr { 29*7f8f2456SAndreas Gohr $this->message =& $message; 30*7f8f2456SAndreas Gohr } 31*7f8f2456SAndreas Gohr 32*7f8f2456SAndreas Gohr public function parse() 33*7f8f2456SAndreas Gohr { 34*7f8f2456SAndreas Gohr // first remove the XML declaration 35*7f8f2456SAndreas Gohr // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages 36*7f8f2456SAndreas Gohr $header = preg_replace('/<\?xml.*?\?' . '>/s', '', substr($this->message, 0, 100), 1); 37*7f8f2456SAndreas Gohr $this->message = trim(substr_replace($this->message, $header, 0, 100)); 38*7f8f2456SAndreas Gohr if ('' == $this->message) { 39*7f8f2456SAndreas Gohr return false; 40*7f8f2456SAndreas Gohr } 41*7f8f2456SAndreas Gohr 42*7f8f2456SAndreas Gohr // Then remove the DOCTYPE 43*7f8f2456SAndreas Gohr $header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($this->message, 0, 200), 1); 44*7f8f2456SAndreas Gohr $this->message = trim(substr_replace($this->message, $header, 0, 200)); 45*7f8f2456SAndreas Gohr if ('' == $this->message) { 46*7f8f2456SAndreas Gohr return false; 47*7f8f2456SAndreas Gohr } 48*7f8f2456SAndreas Gohr 49*7f8f2456SAndreas Gohr // Check that the root tag is valid 50*7f8f2456SAndreas Gohr $root_tag = substr($this->message, 0, strcspn(substr($this->message, 0, 20), "> \t\r\n")); 51*7f8f2456SAndreas Gohr if ('<!DOCTYPE' === strtoupper($root_tag)) { 52*7f8f2456SAndreas Gohr return false; 53*7f8f2456SAndreas Gohr } 54*7f8f2456SAndreas Gohr if (!in_array($root_tag, ['<methodCall', '<methodResponse', '<fault'])) { 55*7f8f2456SAndreas Gohr return false; 56*7f8f2456SAndreas Gohr } 57*7f8f2456SAndreas Gohr 58*7f8f2456SAndreas Gohr // Bail if there are too many elements to parse 59*7f8f2456SAndreas Gohr $element_limit = 30000; 60*7f8f2456SAndreas Gohr if ($element_limit && 2 * $element_limit < substr_count($this->message, '<')) { 61*7f8f2456SAndreas Gohr return false; 62*7f8f2456SAndreas Gohr } 63*7f8f2456SAndreas Gohr 64*7f8f2456SAndreas Gohr $this->_parser = xml_parser_create(); 65*7f8f2456SAndreas Gohr // Set XML parser to take the case of tags in to account 66*7f8f2456SAndreas Gohr xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 67*7f8f2456SAndreas Gohr // Set XML parser callback functions 68*7f8f2456SAndreas Gohr xml_set_object($this->_parser, $this); 69*7f8f2456SAndreas Gohr xml_set_element_handler($this->_parser, 'tagOpen', 'tagClose'); 70*7f8f2456SAndreas Gohr xml_set_character_data_handler($this->_parser, 'cdata'); 71*7f8f2456SAndreas Gohr $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 72*7f8f2456SAndreas Gohr $final = false; 73*7f8f2456SAndreas Gohr do { 74*7f8f2456SAndreas Gohr if (strlen($this->message) <= $chunk_size) { 75*7f8f2456SAndreas Gohr $final = true; 76*7f8f2456SAndreas Gohr } 77*7f8f2456SAndreas Gohr $part = substr($this->message, 0, $chunk_size); 78*7f8f2456SAndreas Gohr $this->message = substr($this->message, $chunk_size); 79*7f8f2456SAndreas Gohr if (!xml_parse($this->_parser, $part, $final)) { 80*7f8f2456SAndreas Gohr return false; 81*7f8f2456SAndreas Gohr } 82*7f8f2456SAndreas Gohr if ($final) { 83*7f8f2456SAndreas Gohr break; 84*7f8f2456SAndreas Gohr } 85*7f8f2456SAndreas Gohr } while (true); 86*7f8f2456SAndreas Gohr xml_parser_free($this->_parser); 87*7f8f2456SAndreas Gohr 88*7f8f2456SAndreas Gohr // Grab the error messages, if any 89*7f8f2456SAndreas Gohr if ($this->messageType === 'fault') { 90*7f8f2456SAndreas Gohr $this->faultCode = $this->params[0]['faultCode']; 91*7f8f2456SAndreas Gohr $this->faultString = $this->params[0]['faultString']; 92*7f8f2456SAndreas Gohr } 93*7f8f2456SAndreas Gohr return true; 94*7f8f2456SAndreas Gohr } 95*7f8f2456SAndreas Gohr 96*7f8f2456SAndreas Gohr /** 97*7f8f2456SAndreas Gohr * Opening tag handler 98*7f8f2456SAndreas Gohr * @param $parser 99*7f8f2456SAndreas Gohr * @param $tag 100*7f8f2456SAndreas Gohr * @param $attr 101*7f8f2456SAndreas Gohr */ 102*7f8f2456SAndreas Gohr public function tagOpen($parser, $tag, $attr) 103*7f8f2456SAndreas Gohr { 104*7f8f2456SAndreas Gohr $this->_currentTagContents = ''; 105*7f8f2456SAndreas Gohr $this->currentTag = $tag; 106*7f8f2456SAndreas Gohr switch ($tag) { 107*7f8f2456SAndreas Gohr case 'methodCall': 108*7f8f2456SAndreas Gohr case 'methodResponse': 109*7f8f2456SAndreas Gohr case 'fault': 110*7f8f2456SAndreas Gohr $this->messageType = $tag; 111*7f8f2456SAndreas Gohr break; 112*7f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 113*7f8f2456SAndreas Gohr case 'data': // data is to all intents and puposes more interesting than array 114*7f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'array'; 115*7f8f2456SAndreas Gohr $this->_arraystructs[] = []; 116*7f8f2456SAndreas Gohr break; 117*7f8f2456SAndreas Gohr case 'struct': 118*7f8f2456SAndreas Gohr $this->_arraystructstypes[] = 'struct'; 119*7f8f2456SAndreas Gohr $this->_arraystructs[] = []; 120*7f8f2456SAndreas Gohr break; 121*7f8f2456SAndreas Gohr } 122*7f8f2456SAndreas Gohr } 123*7f8f2456SAndreas Gohr 124*7f8f2456SAndreas Gohr /** 125*7f8f2456SAndreas Gohr * Character Data handler 126*7f8f2456SAndreas Gohr * @param $parser 127*7f8f2456SAndreas Gohr * @param $cdata 128*7f8f2456SAndreas Gohr */ 129*7f8f2456SAndreas Gohr public function cdata($parser, $cdata) 130*7f8f2456SAndreas Gohr { 131*7f8f2456SAndreas Gohr $this->_currentTagContents .= $cdata; 132*7f8f2456SAndreas Gohr } 133*7f8f2456SAndreas Gohr 134*7f8f2456SAndreas Gohr /** 135*7f8f2456SAndreas Gohr * Closing tag handler 136*7f8f2456SAndreas Gohr * @param $parser 137*7f8f2456SAndreas Gohr * @param $tag 138*7f8f2456SAndreas Gohr */ 139*7f8f2456SAndreas Gohr public function tagClose($parser, $tag) 140*7f8f2456SAndreas Gohr { 141*7f8f2456SAndreas Gohr $valueFlag = false; 142*7f8f2456SAndreas Gohr switch ($tag) { 143*7f8f2456SAndreas Gohr case 'int': 144*7f8f2456SAndreas Gohr case 'i4': 145*7f8f2456SAndreas Gohr $value = (int)trim($this->_currentTagContents); 146*7f8f2456SAndreas Gohr $valueFlag = true; 147*7f8f2456SAndreas Gohr break; 148*7f8f2456SAndreas Gohr case 'double': 149*7f8f2456SAndreas Gohr $value = (double)trim($this->_currentTagContents); 150*7f8f2456SAndreas Gohr $valueFlag = true; 151*7f8f2456SAndreas Gohr break; 152*7f8f2456SAndreas Gohr case 'string': 153*7f8f2456SAndreas Gohr $value = (string)($this->_currentTagContents); 154*7f8f2456SAndreas Gohr $valueFlag = true; 155*7f8f2456SAndreas Gohr break; 156*7f8f2456SAndreas Gohr case 'dateTime.iso8601': 157*7f8f2456SAndreas Gohr $value = new Date(trim($this->_currentTagContents)); 158*7f8f2456SAndreas Gohr $valueFlag = true; 159*7f8f2456SAndreas Gohr break; 160*7f8f2456SAndreas Gohr case 'value': 161*7f8f2456SAndreas Gohr // "If no type is indicated, the type is string." 162*7f8f2456SAndreas Gohr if (trim($this->_currentTagContents) != '') { 163*7f8f2456SAndreas Gohr $value = (string)$this->_currentTagContents; 164*7f8f2456SAndreas Gohr $valueFlag = true; 165*7f8f2456SAndreas Gohr } 166*7f8f2456SAndreas Gohr break; 167*7f8f2456SAndreas Gohr case 'boolean': 168*7f8f2456SAndreas Gohr $value = (boolean)trim($this->_currentTagContents); 169*7f8f2456SAndreas Gohr $valueFlag = true; 170*7f8f2456SAndreas Gohr break; 171*7f8f2456SAndreas Gohr case 'base64': 172*7f8f2456SAndreas Gohr $value = base64_decode($this->_currentTagContents); 173*7f8f2456SAndreas Gohr $valueFlag = true; 174*7f8f2456SAndreas Gohr break; 175*7f8f2456SAndreas Gohr /* Deal with stacks of arrays and structs */ 176*7f8f2456SAndreas Gohr case 'data': 177*7f8f2456SAndreas Gohr case 'struct': 178*7f8f2456SAndreas Gohr $value = array_pop($this->_arraystructs); 179*7f8f2456SAndreas Gohr array_pop($this->_arraystructstypes); 180*7f8f2456SAndreas Gohr $valueFlag = true; 181*7f8f2456SAndreas Gohr break; 182*7f8f2456SAndreas Gohr case 'member': 183*7f8f2456SAndreas Gohr array_pop($this->_currentStructName); 184*7f8f2456SAndreas Gohr break; 185*7f8f2456SAndreas Gohr case 'name': 186*7f8f2456SAndreas Gohr $this->_currentStructName[] = trim($this->_currentTagContents); 187*7f8f2456SAndreas Gohr break; 188*7f8f2456SAndreas Gohr case 'methodName': 189*7f8f2456SAndreas Gohr $this->methodName = trim($this->_currentTagContents); 190*7f8f2456SAndreas Gohr break; 191*7f8f2456SAndreas Gohr } 192*7f8f2456SAndreas Gohr 193*7f8f2456SAndreas Gohr if ($valueFlag) { 194*7f8f2456SAndreas Gohr if (count($this->_arraystructs) > 0) { 195*7f8f2456SAndreas Gohr // Add value to struct or array 196*7f8f2456SAndreas Gohr if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') { 197*7f8f2456SAndreas Gohr // Add to struct 198*7f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; 199*7f8f2456SAndreas Gohr } else { 200*7f8f2456SAndreas Gohr // Add to array 201*7f8f2456SAndreas Gohr $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; 202*7f8f2456SAndreas Gohr } 203*7f8f2456SAndreas Gohr } else { 204*7f8f2456SAndreas Gohr // Just add as a paramater 205*7f8f2456SAndreas Gohr $this->params[] = $value; 206*7f8f2456SAndreas Gohr } 207*7f8f2456SAndreas Gohr } 208*7f8f2456SAndreas Gohr $this->_currentTagContents = ''; 209*7f8f2456SAndreas Gohr } 210*7f8f2456SAndreas Gohr} 211