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