miniflux-legacy/vendor/fguillot/picofeed/lib/PicoFeed/Parser/XmlParser.php

230 lines
5.4 KiB
PHP
Raw Normal View History

2014-05-20 20:20:27 +02:00
<?php
namespace PicoFeed\Parser;
2014-05-20 20:20:27 +02:00
use DomDocument;
use SimpleXmlElement;
2015-12-16 01:26:15 +01:00
use Exception;
use ZendXml\Security;
2014-05-20 20:20:27 +02:00
/**
* XML parser class.
2014-05-20 20:20:27 +02:00
*
* Checks for XML eXternal Entity (XXE) and XML Entity Expansion (XEE) attacks on XML documents
*
* @author Frederic Guillot
*/
class XmlParser
{
/**
* Get a SimpleXmlElement instance or return false.
2014-05-20 20:20:27 +02:00
*
* @static
*
* @param string $input XML content
*
2014-05-20 20:20:27 +02:00
* @return mixed
*/
public static function getSimpleXml($input)
{
2015-12-16 01:26:15 +01:00
return self::scan($input);
2014-05-20 20:20:27 +02:00
}
/**
* Get a DomDocument instance or return false.
2014-05-20 20:20:27 +02:00
*
* @static
*
* @param string $input XML content
*
* @return \DOMDocument
2014-05-20 20:20:27 +02:00
*/
public static function getDomDocument($input)
2014-05-20 20:20:27 +02:00
{
2015-03-01 19:56:11 +01:00
if (empty($input)) {
return false;
}
2015-12-16 01:26:15 +01:00
$dom = self::scan($input, new DOMDocument());
2014-05-20 20:20:27 +02:00
// The document is empty, there is probably some parsing errors
if ($dom && $dom->childNodes->length === 0) {
return false;
}
return $dom;
}
2014-05-20 20:20:27 +02:00
2015-12-16 01:26:15 +01:00
/**
* Small wrapper around ZendXml to turn their exceptions into picoFeed
* exceptions
* @param $input the xml to load
* @param $dom pass in a dom document or use null/omit if simpleXml should
2015-12-16 01:26:15 +01:00
* be used
*/
private static function scan($input, $dom = null)
2015-12-16 01:26:15 +01:00
{
try {
return Security::scan($input, $dom);
} catch(\ZendXml\Exception\RuntimeException $e) {
throw new XmlEntityException($e->getMessage());
}
}
/**
* Load HTML document by using a DomDocument instance or return false on failure.
*
* @static
*
* @param string $input XML content
*
* @return \DOMDocument
*/
public static function getHtmlDocument($input)
{
2015-12-16 01:26:15 +01:00
$dom = new DomDocument();
if (empty($input)) {
2015-12-16 01:26:15 +01:00
return $dom;
}
2015-12-16 01:26:15 +01:00
libxml_use_internal_errors(true);
2015-12-16 01:26:15 +01:00
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$dom->loadHTML($input, LIBXML_NONET);
} else {
2015-12-16 01:26:15 +01:00
$dom->loadHTML($input);
2014-05-20 20:20:27 +02:00
}
2015-12-16 01:26:15 +01:00
return $dom;
2014-05-20 20:20:27 +02:00
}
2014-10-19 20:42:31 +02:00
/**
* Convert a HTML document to XML.
2014-10-19 20:42:31 +02:00
*
* @static
*
* @param string $html HTML document
*
2014-10-19 20:42:31 +02:00
* @return string
*/
public static function htmlToXml($html)
2014-10-19 20:42:31 +02:00
{
$dom = self::getHtmlDocument('<?xml version="1.0" encoding="UTF-8">'.$html);
2014-10-19 20:42:31 +02:00
return $dom->saveXML($dom->getElementsByTagName('body')->item(0));
}
2014-05-20 20:20:27 +02:00
/**
* Get XML parser errors.
2014-05-20 20:20:27 +02:00
*
* @static
*
2014-05-20 20:20:27 +02:00
* @return string
*/
public static function getErrors()
{
$errors = array();
foreach (libxml_get_errors() as $error) {
2014-05-20 20:20:27 +02:00
$errors[] = sprintf('XML error: %s (Line: %d - Column: %d - Code: %d)',
$error->message,
$error->line,
$error->column,
$error->code
);
}
return implode(', ', $errors);
}
2014-05-25 14:47:03 +02:00
/**
* Get the encoding from a xml tag.
2014-05-25 14:47:03 +02:00
*
* @static
*
* @param string $data Input data
*
2014-05-25 14:47:03 +02:00
* @return string
*/
public static function getEncodingFromXmlTag($data)
{
$encoding = '';
if (strpos($data, '<?xml') !== false) {
$data = substr($data, 0, strrpos($data, '?>'));
$data = str_replace("'", '"', $data);
$p1 = strpos($data, 'encoding=');
$p2 = strpos($data, '"', $p1 + 10);
2015-01-28 02:13:16 +01:00
if ($p1 !== false && $p2 !== false) {
$encoding = substr($data, $p1 + 10, $p2 - $p1 - 10);
$encoding = strtolower($encoding);
}
2014-05-25 14:47:03 +02:00
}
return $encoding;
}
2014-10-19 20:42:31 +02:00
2015-03-01 19:56:11 +01:00
/**
* Get the charset from a meta tag.
2015-03-01 19:56:11 +01:00
*
* @static
*
* @param string $data Input data
*
2015-03-01 19:56:11 +01:00
* @return string
*/
public static function getEncodingFromMetaTag($data)
{
$encoding = '';
2015-03-26 00:59:41 +01:00
if (preg_match('/<meta.*?charset\s*=\s*["\']?\s*([^"\'\s\/>;]+)/i', $data, $match) === 1) {
$encoding = strtolower($match[1]);
2015-03-01 19:56:11 +01:00
}
return $encoding;
}
2014-10-19 20:42:31 +02:00
/**
* Rewrite XPath query to use namespace-uri and local-name derived from prefix.
*
* @param string $query XPath query
* @param array $ns Prefix to namespace URI mapping
2014-10-19 20:42:31 +02:00
*
2015-07-19 17:19:26 +02:00
* @return string
2014-10-19 20:42:31 +02:00
*/
public static function replaceXPathPrefixWithNamespaceURI($query, array $ns)
{
return preg_replace_callback('/([A-Z0-9]+):([A-Z0-9]+)/iu', function ($matches) use ($ns) {
2015-07-19 17:19:26 +02:00
// don't try to map the special prefix XML
if (strtolower($matches[1]) === 'xml') {
return $matches[0];
}
2014-10-19 20:42:31 +02:00
2015-07-19 17:19:26 +02:00
return '*[namespace-uri()="'.$ns[$matches[1]].'" and local-name()="'.$matches[2].'"]';
},
$query);
2014-10-19 20:42:31 +02:00
}
/**
* Get the result elements of a XPath query.
*
* @param \SimpleXMLElement $xml XML element
* @param string $query XPath query
* @param array $ns Prefix to namespace URI mapping
2014-10-19 20:42:31 +02:00
*
2015-07-19 17:19:26 +02:00
* @return \SimpleXMLElement
2014-10-19 20:42:31 +02:00
*/
2015-07-19 17:19:26 +02:00
public static function getXPathResult(SimpleXMLElement $xml, $query, array $ns = array())
2014-10-19 20:42:31 +02:00
{
if (!empty($ns)) {
2015-07-19 17:19:26 +02:00
$query = static::replaceXPathPrefixWithNamespaceURI($query, $ns);
2014-10-19 20:42:31 +02:00
}
2015-07-19 17:19:26 +02:00
return $xml->xpath($query);
2014-10-19 20:42:31 +02:00
}
2014-05-20 20:20:27 +02:00
}