init
This commit is contained in:
22
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/auto-close.yml
vendored
Normal file
22
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/auto-close.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Autocloser
|
||||
on: [issues, pull_request]
|
||||
jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autoclose new issues and PRs
|
||||
uses: roots/issue-closer@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
issue-pattern: "^exact-string-will-never-match$"
|
||||
pr-pattern: "^exact-string-will-never-match$"
|
||||
issue-close-message: |
|
||||
This package is considered feature-complete, and is now in **security-only** maintenance mode, following a [decision by the Technical Steering Committee](https://github.com/laminas/technical-steering-committee/blob/2b55453e172a1b8c9c4c212be7cf7e7a58b9352c/meetings/minutes/2020-08-03-TSC-Minutes.md#vote-on-components-to-mark-as-security-only).
|
||||
If you have a security issue, please [follow our security reporting guidelines](https://getlaminas.org/security/).
|
||||
If you wish to take on the role of maintainer, please [nominate yourself](https://github.com/laminas/technical-steering-committee/issues/new?assignees=&labels=Nomination&template=Maintainer_Nomination.md&title=%5BNOMINATION%5D%5BMAINTAINER%5D%3A+%7Bname+of+person+being+nominated%7D)
|
||||
|
||||
pr-close-message: |
|
||||
This package is considered feature-complete, and is now in **security-only** maintenance mode, following a [decision by the Technical Steering Committee](https://github.com/laminas/technical-steering-committee/blob/2b55453e172a1b8c9c4c212be7cf7e7a58b9352c/meetings/minutes/2020-08-03-TSC-Minutes.md#vote-on-components-to-mark-as-security-only).
|
||||
If you have a security issue, please [follow our security reporting guidelines](https://getlaminas.org/security/).
|
||||
If you wish to take on the role of maintainer, please [nominate yourself](https://github.com/laminas/technical-steering-committee/issues/new?assignees=&labels=Nomination&template=Maintainer_Nomination.md&title=%5BNOMINATION%5D%5BMAINTAINER%5D%3A+%7Bname+of+person+being+nominated%7D)
|
||||
|
||||
33
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/continuous-integration.yml
vendored
Normal file
33
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/continuous-integration.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: "Continuous Integration"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
- 'refs/pull/*'
|
||||
tags:
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
name: Generate job matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Gather CI configuration
|
||||
id: matrix
|
||||
uses: laminas/laminas-ci-matrix-action@v1
|
||||
|
||||
qa:
|
||||
name: QA Checks
|
||||
needs: [matrix]
|
||||
runs-on: ${{ matrix.operatingSystem }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }}
|
||||
steps:
|
||||
- name: ${{ matrix.name }}
|
||||
uses: laminas/laminas-continuous-integration-action@v1
|
||||
with:
|
||||
job: ${{ matrix.job }}
|
||||
71
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/release-on-milestone-closed.yml
vendored
Normal file
71
user/plugins/admin/vendor/laminas/laminas-xml/.github/workflows/release-on-milestone-closed.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# Alternate workflow example.
|
||||
# This one is identical to the one in release-on-milestone.yml, with one change:
|
||||
# the Release step uses the ORGANIZATION_ADMIN_TOKEN instead, to allow it to
|
||||
# trigger a release workflow event. This is useful if you have other actions
|
||||
# that intercept that event.
|
||||
|
||||
name: "Automatic Releases"
|
||||
|
||||
on:
|
||||
milestone:
|
||||
types:
|
||||
- "closed"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: "GIT tag, release & create merge-up PR"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
|
||||
- name: "Release"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:release"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create Merge-Up Pull Request"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:create-merge-up-pull-request"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create and/or Switch to new Release Branch"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Bump Changelog Version On Originating Release Branch"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:bump-changelog"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create new milestones"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:create-milestones"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
1
user/plugins/admin/vendor/laminas/laminas-xml/COPYRIGHT.md
vendored
Normal file
1
user/plugins/admin/vendor/laminas/laminas-xml/COPYRIGHT.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)
|
||||
26
user/plugins/admin/vendor/laminas/laminas-xml/LICENSE.md
vendored
Normal file
26
user/plugins/admin/vendor/laminas/laminas-xml/LICENSE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of Laminas Foundation nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
51
user/plugins/admin/vendor/laminas/laminas-xml/README.md
vendored
Normal file
51
user/plugins/admin/vendor/laminas/laminas-xml/README.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# laminas-xml
|
||||
|
||||
> This package is considered feature-complete, and is now in **security-only** maintenance mode, following a [decision by the Technical Steering Committee](https://github.com/laminas/technical-steering-committee/blob/2b55453e172a1b8c9c4c212be7cf7e7a58b9352c/meetings/minutes/2020-08-03-TSC-Minutes.md#vote-on-components-to-mark-as-security-only).
|
||||
> If you have a security issue, please [follow our security reporting guidelines](https://getlaminas.org/security/).
|
||||
> If you wish to take on the role of maintainer, please [nominate yourself](https://github.com/laminas/technical-steering-committee/issues/new?assignees=&labels=Nomination&template=Maintainer_Nomination.md&title=%5BNOMINATION%5D%5BMAINTAINER%5D%3A+%7Bname+of+person+being+nominated%7D)
|
||||
|
||||
|
||||
[](https://github.com/laminas/laminas-xml/actions?query=workflow%3A"Continuous+Integration")
|
||||
|
||||
An utility component for XML usage and best practices in PHP
|
||||
|
||||
## Installation
|
||||
|
||||
You can install using:
|
||||
|
||||
```bash
|
||||
$ curl -s https://getcomposer.org/installer | php
|
||||
$ php composer.phar install
|
||||
```
|
||||
|
||||
Notice that this library doesn't have any external dependencies, the usage of composer is for autoloading and standard purpose.
|
||||
|
||||
## Laminas\Xml\Security
|
||||
|
||||
This is a security component to prevent [XML eXternal Entity](https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing) (XXE) and [XML Entity Expansion](http://projects.webappsec.org/w/page/13247002/XML%20Entity%20Expansion) (XEE) attacks on XML documents.
|
||||
|
||||
The XXE attack is prevented disabling the load of external entities in the libxml library used by PHP, using the function [libxml_disable_entity_loader](http://www.php.net/manual/en/function.libxml-disable-entity-loader.php).
|
||||
|
||||
The XEE attack is prevented looking inside the XML document for ENTITY usage. If the XML document uses ENTITY the library throw an Exception.
|
||||
|
||||
We have two static methods to scan and load XML document from a string (scan) and from a file (scanFile). You can decide to get a SimpleXMLElement or DOMDocument as result, using the following use cases:
|
||||
|
||||
```php
|
||||
use Laminas\Xml\Security as XmlSecurity;
|
||||
|
||||
$xml = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<results>
|
||||
<result>test</result>
|
||||
</results>
|
||||
XML;
|
||||
|
||||
// SimpleXML use case
|
||||
$simplexml = XmlSecurity::scan($xml);
|
||||
printf ("SimpleXMLElement: %s\n", ($simplexml instanceof \SimpleXMLElement) ? 'yes' : 'no');
|
||||
|
||||
// DOMDocument use case
|
||||
$dom = new \DOMDocument('1.0');
|
||||
$dom = XmlSecurity::scan($xml, $dom);
|
||||
printf ("DOMDocument: %s\n", ($dom instanceof \DOMDocument) ? 'yes' : 'no');
|
||||
```
|
||||
55
user/plugins/admin/vendor/laminas/laminas-xml/composer.json
vendored
Normal file
55
user/plugins/admin/vendor/laminas/laminas-xml/composer.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "laminas/laminas-xml",
|
||||
"description": "Utility library for XML usage, best practices, and security in PHP",
|
||||
"license": "BSD-3-Clause",
|
||||
"keywords": [
|
||||
"laminas",
|
||||
"xml",
|
||||
"security"
|
||||
],
|
||||
"homepage": "https://laminas.dev",
|
||||
"support": {
|
||||
"issues": "https://github.com/laminas/laminas-xml/issues",
|
||||
"source": "https://github.com/laminas/laminas-xml",
|
||||
"rss": "https://github.com/laminas/laminas-xml/releases.atom",
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"forum": "https://discourse.laminas.dev"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3 || ~8.0.0 || ~8.1.0",
|
||||
"ext-dom": "*",
|
||||
"ext-simplexml": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laminas/laminas-coding-standard": "~1.0.0",
|
||||
"squizlabs/php_codesniffer": "3.6.1 as 2.9999999.9999999",
|
||||
"phpunit/phpunit": "^9.5.8",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laminas\\Xml\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"LaminasTest\\Xml\\": "test/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs-check",
|
||||
"@test"
|
||||
],
|
||||
"cs-check": "phpcs",
|
||||
"cs-fix": "phpcbf",
|
||||
"test": "phpunit --colors=always",
|
||||
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
|
||||
},
|
||||
"conflict": {
|
||||
"zendframework/zendxml": "*"
|
||||
}
|
||||
}
|
||||
2285
user/plugins/admin/vendor/laminas/laminas-xml/composer.lock
generated
vendored
Normal file
2285
user/plugins/admin/vendor/laminas/laminas-xml/composer.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/ExceptionInterface.php
vendored
Normal file
7
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/ExceptionInterface.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Laminas\Xml\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
10
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/InvalidArgumentException.php
vendored
Normal file
10
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/InvalidArgumentException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Laminas\Xml\Exception;
|
||||
|
||||
/**
|
||||
* Invalid argument exception
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
10
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/RuntimeException.php
vendored
Normal file
10
user/plugins/admin/vendor/laminas/laminas-xml/src/Exception/RuntimeException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Laminas\Xml\Exception;
|
||||
|
||||
/**
|
||||
* Runtime exception
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
407
user/plugins/admin/vendor/laminas/laminas-xml/src/Security.php
vendored
Normal file
407
user/plugins/admin/vendor/laminas/laminas-xml/src/Security.php
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
namespace Laminas\Xml;
|
||||
|
||||
use DOMDocument;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Security
|
||||
{
|
||||
const ENTITY_DETECT = 'Detected use of ENTITY in XML, disabled to prevent XXE/XEE attacks';
|
||||
|
||||
/**
|
||||
* Heuristic scan to detect entity in XML
|
||||
*
|
||||
* @param string $xml
|
||||
* @throws Exception\RuntimeException If entity expansion or external entity declaration was discovered.
|
||||
*/
|
||||
protected static function heuristicScan($xml)
|
||||
{
|
||||
foreach (self::getEntityComparison($xml) as $compare) {
|
||||
if (strpos($xml, $compare) !== false) {
|
||||
throw new Exception\RuntimeException(self::ENTITY_DETECT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan XML string for potential XXE and XEE attacks
|
||||
*
|
||||
* @param string $xml
|
||||
* @param DomDocument $dom
|
||||
* @param int $libXmlConstants additional libxml constants to pass in
|
||||
* @param callable $callback the callback to use to create the dom element
|
||||
* @throws Exception\RuntimeException
|
||||
* @return SimpleXMLElement|DomDocument|boolean
|
||||
*/
|
||||
private static function scanString($xml, DOMDocument $dom = null, $libXmlConstants, callable $callback)
|
||||
{
|
||||
// If running with PHP-FPM we perform an heuristic scan
|
||||
// We cannot use libxml_disable_entity_loader because of this bug
|
||||
// @see https://bugs.php.net/bug.php?id=64938
|
||||
if (self::isPhpFpm()) {
|
||||
self::heuristicScan($xml);
|
||||
}
|
||||
|
||||
if (null === $dom) {
|
||||
$simpleXml = true;
|
||||
$dom = new DOMDocument();
|
||||
}
|
||||
|
||||
if (! self::isPhpFpm()) {
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
$loadEntities = libxml_disable_entity_loader(true);
|
||||
}
|
||||
$useInternalXmlErrors = libxml_use_internal_errors(true);
|
||||
}
|
||||
|
||||
// Load XML with network access disabled (LIBXML_NONET)
|
||||
// error disabled with @ for PHP-FPM scenario
|
||||
set_error_handler(function ($errno, $errstr) {
|
||||
if (substr_count($errstr, 'DOMDocument::loadXML()') > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, E_WARNING);
|
||||
|
||||
$result = $callback($xml, $dom, LIBXML_NONET | $libXmlConstants);
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
if (! $result) {
|
||||
// Entity load to previous setting
|
||||
if (! self::isPhpFpm()) {
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
libxml_disable_entity_loader($loadEntities);
|
||||
}
|
||||
libxml_use_internal_errors($useInternalXmlErrors);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scan for potential XEE attacks using ENTITY, if not PHP-FPM
|
||||
if (! self::isPhpFpm()) {
|
||||
foreach ($dom->childNodes as $child) {
|
||||
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
|
||||
if ($child->entities->length > 0) {
|
||||
throw new Exception\RuntimeException(self::ENTITY_DETECT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entity load to previous setting
|
||||
if (! self::isPhpFpm()) {
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
libxml_disable_entity_loader($loadEntities);
|
||||
}
|
||||
libxml_use_internal_errors($useInternalXmlErrors);
|
||||
}
|
||||
|
||||
if (isset($simpleXml)) {
|
||||
$result = simplexml_import_dom($dom);
|
||||
if (! $result instanceof SimpleXMLElement) {
|
||||
return false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan XML string for potential XXE and XEE attacks
|
||||
*
|
||||
* @param string $xml
|
||||
* @param DomDocument $dom
|
||||
* @param int $libXmlConstants additional libxml constants to pass in
|
||||
* @throws Exception\RuntimeException
|
||||
* @return SimpleXMLElement|DomDocument|boolean
|
||||
*/
|
||||
public static function scan($xml, DOMDocument $dom = null, $libXmlConstants = 0)
|
||||
{
|
||||
$callback = function ($xml, $dom, $constants) {
|
||||
return $dom->loadXml($xml, $constants);
|
||||
};
|
||||
return self::scanString($xml, $dom, $libXmlConstants, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan HTML string for potential XXE and XEE attacks
|
||||
*
|
||||
* @param string $xml
|
||||
* @param DomDocument $dom
|
||||
* @param int $libXmlConstants additional libxml constants to pass in
|
||||
* @throws Exception\RuntimeException
|
||||
* @return SimpleXMLElement|DomDocument|boolean
|
||||
*/
|
||||
public static function scanHtml($html, DOMDocument $dom = null, $libXmlConstants = 0)
|
||||
{
|
||||
$callback = function ($html, $dom, $constants) {
|
||||
return $dom->loadHtml($html, $constants);
|
||||
};
|
||||
return self::scanString($html, $dom, $libXmlConstants, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan XML file for potential XXE/XEE attacks
|
||||
*
|
||||
* @param string $file
|
||||
* @param DOMDocument $dom
|
||||
* @throws Exception\InvalidArgumentException
|
||||
* @return SimpleXMLElement|DomDocument
|
||||
*/
|
||||
public static function scanFile($file, DOMDocument $dom = null)
|
||||
{
|
||||
if (! file_exists($file)) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
"The file $file specified doesn't exist"
|
||||
);
|
||||
}
|
||||
return self::scan(file_get_contents($file), $dom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if PHP is running with PHP-FPM
|
||||
*
|
||||
* This method is mainly used to determine whether or not heuristic checks
|
||||
* (vs libxml checks) should be made, due to threading issues in libxml;
|
||||
* under php-fpm, threading becomes a concern.
|
||||
*
|
||||
* However, PHP versions 5.6.6+ contain a patch to the
|
||||
* libxml support in PHP that makes the libxml checks viable; in such
|
||||
* versions, this method will return false to enforce those checks, which
|
||||
* are more strict and accurate than the heuristic checks.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isPhpFpm()
|
||||
{
|
||||
$isVulnerableVersion = version_compare(PHP_VERSION, '5.6', 'ge')
|
||||
&& version_compare(PHP_VERSION, '5.6.6', 'lt');
|
||||
|
||||
if (0 === strpos(php_sapi_name(), 'fpm') && $isVulnerableVersion) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine and return the string(s) to use for the <!ENTITY comparison.
|
||||
*
|
||||
* @param string $xml
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function getEntityComparison($xml)
|
||||
{
|
||||
$encodingMap = self::getAsciiEncodingMap();
|
||||
return array_map(function ($encoding) use ($encodingMap) {
|
||||
$generator = isset($encodingMap[$encoding]) ? $encodingMap[$encoding] : $encodingMap['UTF-8'];
|
||||
return $generator('<!ENTITY');
|
||||
}, self::detectXmlEncoding($xml, self::detectStringEncoding($xml)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the string encoding.
|
||||
*
|
||||
* Determines string encoding from either a detected BOM or a
|
||||
* heuristic.
|
||||
*
|
||||
* @param string $xml
|
||||
* @return string File encoding
|
||||
*/
|
||||
protected static function detectStringEncoding($xml)
|
||||
{
|
||||
return self::detectBom($xml) ?: self::detectXmlStringEncoding($xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to match a known BOM.
|
||||
*
|
||||
* Iterates through the return of getBomMap(), comparing the initial bytes
|
||||
* of the provided string to the BOM of each; if a match is determined,
|
||||
* it returns the encoding.
|
||||
*
|
||||
* @param string $string
|
||||
* @return false|string Returns encoding on success.
|
||||
*/
|
||||
protected static function detectBom($string)
|
||||
{
|
||||
foreach (self::getBomMap() as $criteria) {
|
||||
if (0 === strncmp($string, $criteria['bom'], $criteria['length'])) {
|
||||
return $criteria['encoding'];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to detect the string encoding of an XML string.
|
||||
*
|
||||
* @param string $xml
|
||||
* @return string Encoding
|
||||
*/
|
||||
protected static function detectXmlStringEncoding($xml)
|
||||
{
|
||||
foreach (self::getAsciiEncodingMap() as $encoding => $generator) {
|
||||
$prefix = $generator('<' . '?xml');
|
||||
if (0 === strncmp($xml, $prefix, strlen($prefix))) {
|
||||
return $encoding;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return 'UTF-8';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to detect the specified XML encoding.
|
||||
*
|
||||
* Using the file's encoding, determines if an "encoding" attribute is
|
||||
* present and well-formed in the XML declaration; if so, it returns a
|
||||
* list with both the ASCII representation of that declaration and the
|
||||
* original file encoding.
|
||||
*
|
||||
* If not, a list containing only the provided file encoding is returned.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param string $fileEncoding
|
||||
* @return string[] Potential XML encodings
|
||||
*/
|
||||
protected static function detectXmlEncoding($xml, $fileEncoding)
|
||||
{
|
||||
$encodingMap = self::getAsciiEncodingMap();
|
||||
$generator = $encodingMap[$fileEncoding];
|
||||
$encAttr = $generator('encoding="');
|
||||
$quote = $generator('"');
|
||||
$close = $generator('>');
|
||||
|
||||
$closePos = strpos($xml, $close);
|
||||
if (false === $closePos) {
|
||||
return [$fileEncoding];
|
||||
}
|
||||
|
||||
$encPos = strpos($xml, $encAttr);
|
||||
if (false === $encPos
|
||||
|| $encPos > $closePos
|
||||
) {
|
||||
return [$fileEncoding];
|
||||
}
|
||||
|
||||
$encPos += strlen($encAttr);
|
||||
$quotePos = strpos($xml, $quote, $encPos);
|
||||
if (false === $quotePos) {
|
||||
return [$fileEncoding];
|
||||
}
|
||||
|
||||
$encoding = self::substr($xml, $encPos, $quotePos);
|
||||
return [
|
||||
// Following line works because we're only supporting 8-bit safe encodings at this time.
|
||||
str_replace('\0', '', $encoding), // detected encoding
|
||||
$fileEncoding, // file encoding
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of BOM maps.
|
||||
*
|
||||
* Returns a list of common encoding -> BOM maps, along with the character
|
||||
* length to compare against.
|
||||
*
|
||||
* @link https://en.wikipedia.org/wiki/Byte_order_mark
|
||||
* @return array
|
||||
*/
|
||||
protected static function getBomMap()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'encoding' => 'UTF-32BE',
|
||||
'bom' => pack('CCCC', 0x00, 0x00, 0xfe, 0xff),
|
||||
'length' => 4,
|
||||
],
|
||||
[
|
||||
'encoding' => 'UTF-32LE',
|
||||
'bom' => pack('CCCC', 0xff, 0xfe, 0x00, 0x00),
|
||||
'length' => 4,
|
||||
],
|
||||
[
|
||||
'encoding' => 'GB-18030',
|
||||
'bom' => pack('CCCC', 0x84, 0x31, 0x95, 0x33),
|
||||
'length' => 4,
|
||||
],
|
||||
[
|
||||
'encoding' => 'UTF-16BE',
|
||||
'bom' => pack('CC', 0xfe, 0xff),
|
||||
'length' => 2,
|
||||
],
|
||||
[
|
||||
'encoding' => 'UTF-16LE',
|
||||
'bom' => pack('CC', 0xff, 0xfe),
|
||||
'length' => 2,
|
||||
],
|
||||
[
|
||||
'encoding' => 'UTF-8',
|
||||
'bom' => pack('CCC', 0xef, 0xbb, 0xbf),
|
||||
'length' => 3,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of encoding => generator pairs.
|
||||
*
|
||||
* Returns a map of encoding => generator pairs, where the generator is a
|
||||
* callable that accepts a string and returns the appropriate byte order
|
||||
* sequence of that string for the encoding.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getAsciiEncodingMap()
|
||||
{
|
||||
return [
|
||||
'UTF-32BE' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\0\0\0\\1", $ascii);
|
||||
},
|
||||
'UTF-32LE' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\\1\0\0\0", $ascii);
|
||||
},
|
||||
'UTF-32odd1' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\0\\1\0\0", $ascii);
|
||||
},
|
||||
'UTF-32odd2' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\0\0\\1\0", $ascii);
|
||||
},
|
||||
'UTF-16BE' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\0\\1", $ascii);
|
||||
},
|
||||
'UTF-16LE' => function ($ascii) {
|
||||
return preg_replace('/(.)/', "\\1\0", $ascii);
|
||||
},
|
||||
'UTF-8' => function ($ascii) {
|
||||
return $ascii;
|
||||
},
|
||||
'GB-18030' => function ($ascii) {
|
||||
return $ascii;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary-safe substr.
|
||||
*
|
||||
* substr() is not binary-safe; this method loops by character to ensure
|
||||
* multi-byte characters are aggregated correctly.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
* @return string
|
||||
*/
|
||||
protected static function substr($string, $start, $end)
|
||||
{
|
||||
$substr = '';
|
||||
for ($i = $start; $i < $end; $i += 1) {
|
||||
$substr .= $string[$i];
|
||||
}
|
||||
return $substr;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user