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