1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
17
18use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
19
20/**
21 * Parser for TYPO3's mirrors.xml file.
22 *
23 * Depends on PHP ext/xml which should be available
24 * with PHP 4+. This is the parser used in TYPO3
25 * Core <= 4.3 (without the "collect all data in one
26 * array" behaviour).
27 * Notice: ext/xml has proven to be buggy with entities.
28 * Use at least PHP 5.2.9+ and libxml2 2.7.3+!
29 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
30 */
31class MirrorXmlPushParser extends AbstractMirrorXmlParser
32{
33    /**
34     * @var string
35     */
36    protected $element;
37
38    /**
39     * Class constructor.
40     */
41    public function __construct()
42    {
43        $this->requiredPhpExtensions = 'xml';
44    }
45
46    /**
47     * Create required parser
48     */
49    protected function createParser()
50    {
51        $this->objXml = xml_parser_create();
52        xml_set_object($this->objXml, $this);
53    }
54
55    /**
56     * Method parses a mirror.xml file.
57     *
58     * @param string $file GZIP stream resource
59     * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of XML parser errors
60     */
61    public function parseXml($file)
62    {
63        $this->createParser();
64        if (!is_resource($this->objXml)) {
65            throw new ExtensionManagerException('Unable to create XML parser.', 1342641009);
66        }
67        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
68        $previousValueOfEntityLoader = null;
69        if (PHP_MAJOR_VERSION < 8) {
70            $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
71        }
72        // keep original character case of XML document
73        xml_parser_set_option($this->objXml, XML_OPTION_CASE_FOLDING, false);
74        xml_parser_set_option($this->objXml, XML_OPTION_SKIP_WHITE, false);
75        xml_parser_set_option($this->objXml, XML_OPTION_TARGET_ENCODING, 'utf-8');
76        xml_set_element_handler($this->objXml, [$this, 'startElement'], [$this, 'endElement']);
77        xml_set_character_data_handler($this->objXml, [$this, 'characterData']);
78        if (!($fp = fopen($file, 'r'))) {
79            throw new ExtensionManagerException(sprintf('Unable to open file resource %s.', $file), 1342641010);
80        }
81        while ($data = fread($fp, 4096)) {
82            if (!xml_parse($this->objXml, $data, feof($fp))) {
83                throw new ExtensionManagerException(sprintf('XML error %s in line %u of file resource %s.', xml_error_string(xml_get_error_code($this->objXml)), xml_get_current_line_number($this->objXml), $file), 1342641011);
84            }
85        }
86        if (PHP_MAJOR_VERSION < 8) {
87            libxml_disable_entity_loader($previousValueOfEntityLoader);
88        }
89        xml_parser_free($this->objXml);
90    }
91
92    /**
93     * Method is invoked when parser accesses start tag of an element.
94     *
95     * @param resource $parser parser resource
96     * @param string $elementName element name at parser's current position
97     * @param array $attrs array of an element's attributes if available
98     */
99    protected function startElement($parser, $elementName, $attrs)
100    {
101        switch ($elementName) {
102            default:
103                $this->element = $elementName;
104        }
105    }
106
107    /**
108     * Method is invoked when parser accesses end tag of an element.
109     * Although the first parameter seems unused, it needs to be there for
110     * adherence to the API of xml_set_element_handler
111     *
112     * @see xml_set_element_handler
113     * @param resource $parser parser resource
114     * @param string $elementName element name at parser's current position
115     */
116    protected function endElement($parser, $elementName)
117    {
118        switch ($elementName) {
119            case 'mirror':
120                $this->notify();
121                $this->resetProperties();
122                break;
123            default:
124                $this->element = null;
125        }
126    }
127
128    /**
129     * Method is invoked when parser accesses any character other than elements.
130     * Although the first parameter seems unused, it needs to be there for
131     * adherence to the API of xml_set_character_data_handler
132     *
133     * @see xml_set_character_data_handler
134     * @param resource $parser parser resource
135     * @param string $data an element's value
136     */
137    protected function characterData($parser, $data)
138    {
139        if (isset($this->element)) {
140            switch ($this->element) {
141                case 'title':
142                    $this->title = $data;
143                    break;
144                case 'host':
145                    $this->host = $data;
146                    break;
147                case 'path':
148                    $this->path = $data;
149                    break;
150                case 'country':
151                    $this->country = $data;
152                    break;
153                default:
154                    // Do nothing
155            }
156        }
157    }
158}
159