class-IXR-message.php 8.0 KB

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