243 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * IXR_MESSAGE
 | |
|  *
 | |
|  * @package IXR
 | |
|  * @since 1.5.0
 | |
|  *
 | |
|  */
 | |
| class IXR_Message
 | |
| {
 | |
|     var $message     = false;
 | |
|     var $messageType = false;  // methodCall / methodResponse / fault
 | |
|     var $faultCode   = false;
 | |
|     var $faultString = false;
 | |
|     var $methodName  = '';
 | |
|     var $params      = array();
 | |
| 
 | |
|     // Current variable stacks
 | |
|     var $_arraystructs = array();   // The stack used to keep track of the current array/struct
 | |
|     var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
 | |
|     var $_currentStructName = array();  // A stack as well
 | |
|     var $_param;
 | |
|     var $_value;
 | |
|     var $_currentTag;
 | |
|     var $_currentTagContents;
 | |
|     // The XML parser
 | |
|     var $_parser;
 | |
| 
 | |
| 	/**
 | |
| 	 * PHP5 constructor.
 | |
| 	 */
 | |
|     function __construct( $message )
 | |
|     {
 | |
|         $this->message =& $message;
 | |
|     }
 | |
| 
 | |
| 	/**
 | |
| 	 * PHP4 constructor.
 | |
| 	 */
 | |
| 	public function IXR_Message( $message ) {
 | |
| 		self::__construct( $message );
 | |
| 	}
 | |
| 
 | |
|     function parse()
 | |
|     {
 | |
|         if ( ! function_exists( 'xml_parser_create' ) ) {
 | |
|             trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // first remove the XML declaration
 | |
|         // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
 | |
|         $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );
 | |
|         $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );
 | |
|         if ( '' == $this->message ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Then remove the DOCTYPE
 | |
|         $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );
 | |
|         $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );
 | |
|         if ( '' == $this->message ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Check that the root tag is valid
 | |
|         $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );
 | |
|         if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {
 | |
|             return false;
 | |
|         }
 | |
|         if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Bail if there are too many elements to parse
 | |
|         $element_limit = 30000;
 | |
|         if ( function_exists( 'apply_filters' ) ) {
 | |
|             /**
 | |
|              * Filters the number of elements to parse in an XML-RPC response.
 | |
|              *
 | |
|              * @since 4.0.0
 | |
|              *
 | |
|              * @param int $element_limit Default elements limit.
 | |
|              */
 | |
|             $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );
 | |
|         }
 | |
|         if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $this->_parser = xml_parser_create();
 | |
|         // Set XML parser to take the case of tags in to account
 | |
|         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
 | |
|         // Set XML parser callback functions
 | |
|         xml_set_object($this->_parser, $this);
 | |
|         xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
 | |
|         xml_set_character_data_handler($this->_parser, 'cdata');
 | |
| 
 | |
|         // 256Kb, parse in chunks to avoid the RAM usage on very large messages
 | |
|         $chunk_size = 262144;
 | |
| 
 | |
|         /**
 | |
|          * Filters the chunk size that can be used to parse an XML-RPC response message.
 | |
|          *
 | |
|          * @since 4.4.0
 | |
|          *
 | |
|          * @param int $chunk_size Chunk size to parse in bytes.
 | |
|          */
 | |
|         $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );
 | |
| 
 | |
|         $final = false;
 | |
| 
 | |
|         do {
 | |
|             if (strlen($this->message) <= $chunk_size) {
 | |
|                 $final = true;
 | |
|             }
 | |
| 
 | |
|             $part = substr($this->message, 0, $chunk_size);
 | |
|             $this->message = substr($this->message, $chunk_size);
 | |
| 
 | |
|             if (!xml_parse($this->_parser, $part, $final)) {
 | |
|                 xml_parser_free($this->_parser);
 | |
|                 unset($this->_parser);
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             if ($final) {
 | |
|                 break;
 | |
|             }
 | |
|         } while (true);
 | |
| 
 | |
|         xml_parser_free($this->_parser);
 | |
|         unset($this->_parser);
 | |
| 
 | |
|         // Grab the error messages, if any
 | |
|         if ($this->messageType == 'fault') {
 | |
|             $this->faultCode = $this->params[0]['faultCode'];
 | |
|             $this->faultString = $this->params[0]['faultString'];
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function tag_open($parser, $tag, $attr)
 | |
|     {
 | |
|         $this->_currentTagContents = '';
 | |
|         $this->_currentTag = $tag;
 | |
|         switch($tag) {
 | |
|             case 'methodCall':
 | |
|             case 'methodResponse':
 | |
|             case 'fault':
 | |
|                 $this->messageType = $tag;
 | |
|                 break;
 | |
|                 /* Deal with stacks of arrays and structs */
 | |
|             case 'data':    // data is to all intents and puposes more interesting than array
 | |
|                 $this->_arraystructstypes[] = 'array';
 | |
|                 $this->_arraystructs[] = array();
 | |
|                 break;
 | |
|             case 'struct':
 | |
|                 $this->_arraystructstypes[] = 'struct';
 | |
|                 $this->_arraystructs[] = array();
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function cdata($parser, $cdata)
 | |
|     {
 | |
|         $this->_currentTagContents .= $cdata;
 | |
|     }
 | |
| 
 | |
|     function tag_close($parser, $tag)
 | |
|     {
 | |
|         $valueFlag = false;
 | |
|         switch($tag) {
 | |
|             case 'int':
 | |
|             case 'i4':
 | |
|                 $value = (int)trim($this->_currentTagContents);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'double':
 | |
|                 $value = (double)trim($this->_currentTagContents);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'string':
 | |
|                 $value = (string)trim($this->_currentTagContents);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'dateTime.iso8601':
 | |
|                 $value = new IXR_Date(trim($this->_currentTagContents));
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'value':
 | |
|                 // "If no type is indicated, the type is string."
 | |
|                 if (trim($this->_currentTagContents) != '') {
 | |
|                     $value = (string)$this->_currentTagContents;
 | |
|                     $valueFlag = true;
 | |
|                 }
 | |
|                 break;
 | |
|             case 'boolean':
 | |
|                 $value = (boolean)trim($this->_currentTagContents);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'base64':
 | |
|                 $value = base64_decode($this->_currentTagContents);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|                 /* Deal with stacks of arrays and structs */
 | |
|             case 'data':
 | |
|             case 'struct':
 | |
|                 $value = array_pop($this->_arraystructs);
 | |
|                 array_pop($this->_arraystructstypes);
 | |
|                 $valueFlag = true;
 | |
|                 break;
 | |
|             case 'member':
 | |
|                 array_pop($this->_currentStructName);
 | |
|                 break;
 | |
|             case 'name':
 | |
|                 $this->_currentStructName[] = trim($this->_currentTagContents);
 | |
|                 break;
 | |
|             case 'methodName':
 | |
|                 $this->methodName = trim($this->_currentTagContents);
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         if ($valueFlag) {
 | |
|             if (count($this->_arraystructs) > 0) {
 | |
|                 // Add value to struct or array
 | |
|                 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
 | |
|                     // Add to struct
 | |
|                     $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
 | |
|                 } else {
 | |
|                     // Add to array
 | |
|                     $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
 | |
|                 }
 | |
|             } else {
 | |
|                 // Just add as a parameter
 | |
|                 $this->params[] = $value;
 | |
|             }
 | |
|         }
 | |
|         $this->_currentTagContents = '';
 | |
|     }
 | |
| }
 |