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