Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/includes/libs/mime/XmlTypeCheck.php
Ðазад
<?php /** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * * @file */ namespace Wikimedia\Mime; use Exception; use XMLReader; /** * XML syntax and type checker. * * Since MediaWiki 1.24.2, this uses XMLReader instead of xml_parse, which gives us * more control over the expansion of XML entities. When passed to the * callback, entities will be fully expanded, but may report the XML is * invalid if expanding the entities are likely to cause a DoS. * * @newable * @since 1.12.0 * @ingroup Mime */ class XmlTypeCheck { /** * @var bool|null Will be set to true or false to indicate whether the file is * well-formed XML. Note that this doesn't check schema validity. */ public $wellFormed = null; /** * @var bool Will be set to true if the optional element filter returned * a match at some point. */ public $filterMatch = false; /** * Will contain the type of filter hit if the optional element filter returned * a match at some point. * @var mixed */ public $filterMatchType = false; /** * @var string Name of the document's root element, including any namespace * as an expanded URL. */ public $rootElement = ''; /** * @var string[] A stack of strings containing the data of each xml element as it's processed. * Append data to the top string of the stack, then pop off the string and process it when the * element is closed. */ protected $elementData = []; /** * @var array A stack of element names and attributes, as we process them. */ protected $elementDataContext = []; /** * @var int Current depth of the data stack. */ protected $stackDepth = 0; /** @var callable|null */ protected $filterCallback; /** * @var array Additional parsing options */ private $parserOptions = [ 'processing_instruction_handler' => null, 'external_dtd_handler' => '', 'dtd_handler' => '', 'require_safe_dtd' => true ]; /** * Allow filtering an XML file. * * Filters should return either true or a string to indicate something * is wrong with the file. $this->filterMatch will store if the * file failed validation (true = failed validation). * $this->filterMatchType will contain the validation error. * $this->wellFormed will contain whether the xml file is well-formed. * * @note If multiple filters are hit, only one of them will have the * result stored in $this->filterMatchType. * * @param string $input a filename or string containing the XML element * @param callable|null $filterCallback (optional) * Function to call to do additional custom validity checks from the * SAX element handler event. This gives you access to the element * namespace, name, attributes, and text contents. * Filter should return a truthy value describing the error. * @param bool $isFile (optional) indicates if the first parameter is a * filename (default, true) or if it is a string (false) * @param array $options list of additional parsing options: * processing_instruction_handler: Callback for xml_set_processing_instruction_handler * external_dtd_handler: Callback for the url of external dtd subset * dtd_handler: Callback given the full text of the <!DOCTYPE declaration. * require_safe_dtd: Only allow non-recursive entities in internal dtd (default true) */ public function __construct( $input, $filterCallback = null, $isFile = true, $options = [] ) { $this->filterCallback = $filterCallback; $this->parserOptions = array_merge( $this->parserOptions, $options ); $this->validateFromInput( $input, $isFile ); } /** * Alternative constructor: from filename * * @param string $fname the filename of an XML document * @param callable|null $filterCallback (optional) * Function to call to do additional custom validity checks from the * SAX element handler event. This gives you access to the element * namespace, name, and attributes, but not to text contents. * Filter should return 'true' to toggle on $this->filterMatch * @return XmlTypeCheck */ public static function newFromFilename( $fname, $filterCallback = null ) { return new self( $fname, $filterCallback, true ); } /** * Alternative constructor: from string * * @param string $string a string containing an XML element * @param callable|null $filterCallback (optional) * Function to call to do additional custom validity checks from the * SAX element handler event. This gives you access to the element * namespace, name, and attributes, but not to text contents. * Filter should return 'true' to toggle on $this->filterMatch * @return XmlTypeCheck */ public static function newFromString( $string, $filterCallback = null ) { return new self( $string, $filterCallback, false ); } /** * Get the root element. Simple accessor to $rootElement * * @return string */ public function getRootElement() { return $this->rootElement; } /** * @param string $xml * @param bool $isFile */ private function validateFromInput( $xml, $isFile ) { $reader = new XMLReader(); if ( $isFile ) { $s = $reader->open( $xml, null, LIBXML_NOERROR | LIBXML_NOWARNING ); } else { $s = $reader->XML( $xml, null, LIBXML_NOERROR | LIBXML_NOWARNING ); } if ( $s !== true ) { // Couldn't open the XML $this->wellFormed = false; } else { // phpcs:ignore Generic.PHP.NoSilencedErrors -- suppress deprecation per T268847 $oldDisable = @libxml_disable_entity_loader( true ); $reader->setParserProperty( XMLReader::SUBST_ENTITIES, true ); try { $this->validate( $reader ); } catch ( Exception $e ) { // Calling this malformed, because we didn't parse the whole // thing. Maybe just an external entity refernce. $this->wellFormed = false; $reader->close(); // phpcs:ignore Generic.PHP.NoSilencedErrors @libxml_disable_entity_loader( $oldDisable ); throw $e; } $reader->close(); // phpcs:ignore Generic.PHP.NoSilencedErrors @libxml_disable_entity_loader( $oldDisable ); } } private function readNext( XMLReader $reader ) { set_error_handler( function ( $line, $file ) { $this->wellFormed = false; return true; } ); $ret = $reader->read(); restore_error_handler(); return $ret; } private function validate( $reader ) { // First, move through anything that isn't an element, and // handle any processing instructions with the callback do { if ( !$this->readNext( $reader ) ) { // Hit the end of the document before any elements $this->wellFormed = false; return; } if ( $reader->nodeType === XMLReader::PI ) { $this->processingInstructionHandler( $reader->name, $reader->value ); } if ( $reader->nodeType === XMLReader::DOC_TYPE ) { $this->dtdHandler( $reader ); } } while ( $reader->nodeType != XMLReader::ELEMENT ); // Process the rest of the document do { switch ( $reader->nodeType ) { case XMLReader::ELEMENT: $name = $this->expandNS( $reader->name, $reader->namespaceURI ); if ( $this->rootElement === '' ) { $this->rootElement = $name; } $empty = $reader->isEmptyElement; $attrs = $this->getAttributesArray( $reader ); $this->elementOpen( $name, $attrs ); if ( $empty ) { $this->elementClose(); } break; case XMLReader::END_ELEMENT: $this->elementClose(); break; case XMLReader::WHITESPACE: case XMLReader::SIGNIFICANT_WHITESPACE: case XMLReader::CDATA: case XMLReader::TEXT: $this->elementData( $reader->value ); break; case XMLReader::ENTITY_REF: // Unexpanded entity (maybe external?), // don't send to the filter (xml_parse didn't) break; case XMLReader::COMMENT: // Don't send to the filter (xml_parse didn't) break; case XMLReader::PI: // Processing instructions can happen after the header too $this->processingInstructionHandler( $reader->name, $reader->value ); break; case XMLReader::DOC_TYPE: // We should never see a doctype after first // element. $this->wellFormed = false; break; default: // One of DOC, ENTITY, END_ENTITY, // NOTATION, or XML_DECLARATION // xml_parse didn't send these to the filter, so we won't. } } while ( $this->readNext( $reader ) ); if ( $this->stackDepth !== 0 ) { $this->wellFormed = false; } elseif ( $this->wellFormed === null ) { $this->wellFormed = true; } } /** * Get all of the attributes for an XMLReader's current node * @param XMLReader $r * @return array of attributes */ private function getAttributesArray( XMLReader $r ) { $attrs = []; while ( $r->moveToNextAttribute() ) { if ( $r->namespaceURI === 'http://www.w3.org/2000/xmlns/' ) { // XMLReader treats xmlns attributes as normal // attributes, while xml_parse doesn't continue; } $name = $this->expandNS( $r->name, $r->namespaceURI ); $attrs[$name] = $r->value; } return $attrs; } /** * @param string $name element or attribute name, maybe with a full or short prefix * @param string $namespaceURI * @return string the name prefixed with namespaceURI */ private function expandNS( $name, $namespaceURI ) { if ( $namespaceURI ) { $parts = explode( ':', $name ); $localname = array_pop( $parts ); return "$namespaceURI:$localname"; } return $name; } /** * @param string $name * @param array $attribs */ private function elementOpen( $name, $attribs ) { $this->elementDataContext[] = [ $name, $attribs ]; $this->elementData[] = ''; $this->stackDepth++; } private function elementClose() { [ $name, $attribs ] = array_pop( $this->elementDataContext ); $data = array_pop( $this->elementData ); $this->stackDepth--; $callbackReturn = false; if ( is_callable( $this->filterCallback ) ) { $callbackReturn = ( $this->filterCallback )( $name, $attribs, $data ); } if ( $callbackReturn ) { // Filter hit! $this->filterMatch = true; $this->filterMatchType = $callbackReturn; } } /** * @param string $data */ private function elementData( $data ) { // Collect any data here, and we'll run the callback in elementClose $this->elementData[ $this->stackDepth - 1 ] .= trim( $data ); } /** * @param string $target * @param string $data */ private function processingInstructionHandler( $target, $data ) { $callbackReturn = false; if ( $this->parserOptions['processing_instruction_handler'] ) { // @phan-suppress-next-line PhanTypeInvalidCallable false positive $callbackReturn = $this->parserOptions['processing_instruction_handler']( $target, $data ); } if ( $callbackReturn ) { // Filter hit! $this->filterMatch = true; $this->filterMatchType = $callbackReturn; } } /** * Handle coming across a <!DOCTYPE declaration. * * @param XMLReader $reader Reader currently pointing at DOCTYPE node. */ private function dtdHandler( XMLReader $reader ) { $externalCallback = $this->parserOptions['external_dtd_handler']; $generalCallback = $this->parserOptions['dtd_handler']; $checkIfSafe = $this->parserOptions['require_safe_dtd']; if ( !$externalCallback && !$generalCallback && !$checkIfSafe ) { return; } $dtd = $reader->readOuterXml(); $callbackReturn = false; if ( $generalCallback ) { $callbackReturn = $generalCallback( $dtd ); } if ( $callbackReturn ) { // Filter hit! $this->filterMatch = true; $this->filterMatchType = $callbackReturn; $callbackReturn = false; } $parsedDTD = $this->parseDTD( $dtd ); if ( $externalCallback && isset( $parsedDTD['type'] ) ) { $callbackReturn = $externalCallback( $parsedDTD['type'], $parsedDTD['publicid'] ?? null, $parsedDTD['systemid'] ?? null ); } if ( $callbackReturn ) { // Filter hit! $this->filterMatch = true; $this->filterMatchType = $callbackReturn; } if ( $checkIfSafe && isset( $parsedDTD['internal'] ) && !$this->checkDTDIsSafe( $parsedDTD['internal'] ) ) { $this->wellFormed = false; } } /** * Check if the internal subset of the DTD is safe. * * We whitelist an extremely restricted subset of DTD features. * * Safe is defined as: * * Only contains entity definitions (e.g. No <!ATLIST ) * * Entity definitions are not "system" entities * * Entity definitions are not "parameter" (i.e. %) entities * * Entity definitions do not reference other entities except & * and quotes. Entity aliases (where the entity contains only * another entity are allowed) * * Entity references aren't overly long (>255 bytes). * * <!ATTLIST svg xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink"> * allowed if matched exactly for compatibility with graphviz * * Comments. * * @param string $internalSubset The internal subset of the DTD * @return bool true if safe. */ private function checkDTDIsSafe( $internalSubset ) { $res = preg_match( '/^(?:\s*<!ENTITY\s+\S+\s+' . '(?:"(?:&[^"%&;]{1,64};|(?:[^"%&]|&|"){0,255})"' . '|\'(?:&[^\'%&;]{1,64};|(?:[^\'%&]|&|'){0,255})\')\s*>' . '|\s*<!--(?:[^-]|-[^-])*-->' . '|\s*<!ATTLIST svg xmlns:xlink CDATA #FIXED ' . '"http:\/\/www.w3.org\/1999\/xlink">)*\s*$/', $internalSubset ); return (bool)$res; } /** * Parse DTD into parts. * * If there is an error parsing the dtd, sets wellFormed to false. * * @param string $dtd * @return array Possibly containing keys publicid, systemid, type and internal. */ private function parseDTD( $dtd ) { $m = []; $res = preg_match( '/^<!DOCTYPE\s*\S+\s*' . '(?:(?P<typepublic>PUBLIC)\s*' . '(?:"(?P<pubquote>[^"]*)"|\'(?P<pubapos>[^\']*)\')' . // public identifer '\s*"(?P<pubsysquote>[^"]*)"|\'(?P<pubsysapos>[^\']*)\'' . // system identifier '|(?P<typesystem>SYSTEM)\s*' . '(?:"(?P<sysquote>[^"]*)"|\'(?P<sysapos>[^\']*)\')' . ')?\s*' . '(?:\[\s*(?P<internal>.*)\])?\s*>$/s', $dtd, $m ); if ( !$res ) { $this->wellFormed = false; return []; } $parsed = []; foreach ( $m as $field => $value ) { if ( $value === '' || is_numeric( $field ) ) { continue; } switch ( $field ) { case 'typepublic': case 'typesystem': $parsed['type'] = $value; break; case 'pubquote': case 'pubapos': $parsed['publicid'] = $value; break; case 'pubsysquote': case 'pubsysapos': case 'sysquote': case 'sysapos': $parsed['systemid'] = $value; break; case 'internal': $parsed['internal'] = $value; break; } } return $parsed; } } /** @deprecated class alias since 1.43 */ class_alias( XmlTypeCheck::class, 'XmlTypeCheck' );
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка